update
This commit is contained in:
@@ -15,7 +15,8 @@ namespace Hallucinate.GameSetup.Maze
|
||||
Start, // Entry point
|
||||
End, // Exit point
|
||||
StairsUp,
|
||||
StairsDown
|
||||
StairsDown,
|
||||
Room
|
||||
}
|
||||
public enum PieceType
|
||||
{
|
||||
|
||||
@@ -61,6 +61,18 @@ namespace Hallucinate.GameSetup.Maze
|
||||
[Required]
|
||||
[SerializeField] private MazeRenderer mazeRenderer;
|
||||
|
||||
[BoxGroup("Rooms (Phase 2)")]
|
||||
public bool generateRooms = true;
|
||||
[BoxGroup("Rooms (Phase 2)")]
|
||||
[ShowIf(nameof(generateRooms))]
|
||||
public int numberOfRooms = 2;
|
||||
[BoxGroup("Rooms (Phase 2)")]
|
||||
[ShowIf(nameof(generateRooms))]
|
||||
public Vector2Int minRoomSize = new Vector2Int(2, 2);
|
||||
[BoxGroup("Rooms (Phase 2)")]
|
||||
[ShowIf(nameof(generateRooms))]
|
||||
public Vector2Int maxRoomSize = new Vector2Int(4, 4);
|
||||
|
||||
[BoxGroup("References")]
|
||||
[Required]
|
||||
[SerializeField] private Transform mazeContainer;
|
||||
@@ -165,6 +177,8 @@ namespace Hallucinate.GameSetup.Maze
|
||||
mazes[i] = new MazeGrid(width, depth);
|
||||
mazes[i].Level = i;
|
||||
|
||||
CarveRooms(mazes[i]);
|
||||
|
||||
IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm);
|
||||
algorithmForFloor.Generate(mazes[i]);
|
||||
}
|
||||
@@ -203,6 +217,7 @@ namespace Hallucinate.GameSetup.Maze
|
||||
}
|
||||
};
|
||||
|
||||
CarveRooms(mazes[i]);
|
||||
mazeRenderer.Initialize(mazes[i], mazeContainer, i == 0);
|
||||
|
||||
IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm);
|
||||
@@ -262,6 +277,42 @@ namespace Hallucinate.GameSetup.Maze
|
||||
}
|
||||
}
|
||||
|
||||
private void CarveRooms(MazeGrid grid)
|
||||
{
|
||||
if (!generateRooms) return;
|
||||
|
||||
for (int i = 0; i < numberOfRooms; i++)
|
||||
{
|
||||
int w = Random.Range(minRoomSize.x, maxRoomSize.x + 1);
|
||||
int d = Random.Range(minRoomSize.y, maxRoomSize.y + 1);
|
||||
|
||||
int startX = Random.Range(1, width - w - 1);
|
||||
int startZ = Random.Range(1, depth - d - 1);
|
||||
|
||||
for (int x = startX; x < startX + w; x++)
|
||||
{
|
||||
for (int z = startZ; z < startZ + d; z++)
|
||||
{
|
||||
grid.SetCell(x, z, MazeCellType.Room);
|
||||
}
|
||||
}
|
||||
|
||||
// Carve guaranteed door to seed pathfinding
|
||||
if (Random.value > 0.5f)
|
||||
{
|
||||
int doorX = Random.Range(startX, startX + w);
|
||||
int doorZ = Random.value > 0.5f ? startZ + d : startZ - 1;
|
||||
grid.SetCell(doorX, doorZ, MazeCellType.Corridor);
|
||||
}
|
||||
else
|
||||
{
|
||||
int doorX = Random.value > 0.5f ? startX + w : startX - 1;
|
||||
int doorZ = Random.Range(startZ, startZ + d);
|
||||
grid.SetCell(doorX, doorZ, MazeCellType.Corridor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShuffleList<T>(List<T> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
|
||||
@@ -139,48 +139,143 @@ namespace Hallucinate.GameSetup.Maze
|
||||
_spawnedCells.Remove(posKey);
|
||||
}
|
||||
|
||||
GameObject prefab = null;
|
||||
Quaternion rotation = Quaternion.identity;
|
||||
if (type == MazeCellType.Wall) return;
|
||||
|
||||
if (type == MazeCellType.Corridor || type == MazeCellType.Processing)
|
||||
float logicalSpacing = visualProfile.nodeSpacing; // Distances between Nodes
|
||||
float halfSpacing = logicalSpacing / 2f;
|
||||
float safeScale = Mathf.Max(0.001f, visualProfile.scale);
|
||||
float spacingScale = logicalSpacing * safeScale;
|
||||
|
||||
float yOffset = grid.Level * floorHeight;
|
||||
Vector3 localPos = new Vector3(x * spacingScale, yOffset, z * spacingScale);
|
||||
|
||||
GameObject cellParent = new GameObject($"Cell_{x}_{grid.Level}_{z}");
|
||||
cellParent.transform.SetParent(_container);
|
||||
cellParent.transform.localPosition = localPos;
|
||||
|
||||
bool spawnedAnything = false;
|
||||
|
||||
if (type == MazeCellType.Corridor || type == MazeCellType.Processing || type == MazeCellType.StairsUp || type == MazeCellType.StairsDown)
|
||||
{
|
||||
(prefab, rotation) = GetCorridorPrefabAndRotation(grid, x, z);
|
||||
// 1. Spawn Node (Intersection, Corner, T, DeadEnd, or Stairs)
|
||||
GameObject nodePrefab;
|
||||
Quaternion nodeRot;
|
||||
Vector3 nodeOffset = Vector3.zero;
|
||||
|
||||
if (type == MazeCellType.StairsUp || type == MazeCellType.StairsDown)
|
||||
{
|
||||
nodePrefab = visualProfile.GetPrefab(type);
|
||||
nodeRot = Quaternion.Euler(0, visualProfile.stairsOffset, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
(nodePrefab, nodeRot, nodeOffset) = GetNodePrefabAndRotation(grid, x, z);
|
||||
}
|
||||
|
||||
if (nodePrefab != null)
|
||||
{
|
||||
GameObject node = Instantiate(nodePrefab, cellParent.transform);
|
||||
node.transform.localPosition = nodeOffset * safeScale;
|
||||
node.transform.localRotation = nodeRot;
|
||||
node.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
|
||||
// 2. Spawn Edge X (Right path)
|
||||
if (IsPath(grid, x + 1, z))
|
||||
{
|
||||
GameObject edgePrefab = visualProfile.corridorStraight;
|
||||
if (edgePrefab != null)
|
||||
{
|
||||
GameObject edgeX = Instantiate(edgePrefab, cellParent.transform);
|
||||
edgeX.transform.localPosition = new Vector3(halfSpacing * safeScale, 0, 0); // half units offset right
|
||||
edgeX.transform.localRotation = Quaternion.Euler(0, 90f, 0); // pointing along X
|
||||
edgeX.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Spawn Edge Z (Top path)
|
||||
if (IsPath(grid, x, z + 1))
|
||||
{
|
||||
GameObject edgePrefab = visualProfile.corridorStraight;
|
||||
if (edgePrefab != null)
|
||||
{
|
||||
GameObject edgeZ = Instantiate(edgePrefab, cellParent.transform);
|
||||
edgeZ.transform.localPosition = new Vector3(0, 0, halfSpacing * safeScale); // half units offset forward
|
||||
edgeZ.transform.localRotation = Quaternion.identity; // pointing along Z
|
||||
edgeZ.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == MazeCellType.Room)
|
||||
{
|
||||
// Spawn Floor
|
||||
if (visualProfile.roomFloorPrefab != null)
|
||||
{
|
||||
GameObject floor = Instantiate(visualProfile.roomFloorPrefab, cellParent.transform);
|
||||
floor.transform.localPosition = Vector3.zero;
|
||||
floor.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
|
||||
// Spawn Ceiling
|
||||
if (visualProfile.roomCeilingPrefab != null)
|
||||
{
|
||||
GameObject ceiling = Instantiate(visualProfile.roomCeilingPrefab, cellParent.transform);
|
||||
ceiling.transform.localPosition = new Vector3(0, floorHeight, 0);
|
||||
ceiling.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
|
||||
// Spawn Room Edges (Walls or Doors)
|
||||
MazeCellType top = grid.IsInBounds(x, z + 1) ? grid.GetCell(x, z + 1) : MazeCellType.Wall;
|
||||
SpawnRoomEdge(cellParent, top, new Vector3(0, 0, halfSpacing * safeScale), 0f, safeScale);
|
||||
|
||||
MazeCellType right = grid.IsInBounds(x + 1, z) ? grid.GetCell(x + 1, z) : MazeCellType.Wall;
|
||||
SpawnRoomEdge(cellParent, right, new Vector3(halfSpacing * safeScale, 0, 0), 90f, safeScale);
|
||||
|
||||
MazeCellType bottom = grid.IsInBounds(x, z - 1) ? grid.GetCell(x, z - 1) : MazeCellType.Wall;
|
||||
SpawnRoomEdge(cellParent, bottom, new Vector3(0, 0, -halfSpacing * safeScale), 180f, safeScale);
|
||||
|
||||
MazeCellType left = grid.IsInBounds(x - 1, z) ? grid.GetCell(x - 1, z) : MazeCellType.Wall;
|
||||
SpawnRoomEdge(cellParent, left, new Vector3(-halfSpacing * safeScale, 0, 0), 270f, safeScale);
|
||||
|
||||
spawnedAnything = true; // Always true if it reaches here
|
||||
}
|
||||
else
|
||||
{
|
||||
prefab = visualProfile.GetPrefab(type);
|
||||
|
||||
if (type == MazeCellType.StairsUp || type == MazeCellType.StairsDown)
|
||||
// Non-corridor logic (Start, End, etc)
|
||||
GameObject prefab = visualProfile.GetPrefab(type);
|
||||
if (prefab != null)
|
||||
{
|
||||
rotation = Quaternion.Euler(0, visualProfile.stairsOffset, 0);
|
||||
GameObject obj = Instantiate(prefab, cellParent.transform);
|
||||
obj.transform.localPosition = Vector3.zero;
|
||||
obj.transform.localRotation = Quaternion.identity;
|
||||
obj.transform.localScale = Vector3.one * safeScale;
|
||||
spawnedAnything = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (prefab == null) return;
|
||||
if (!spawnedAnything)
|
||||
{
|
||||
DestroyImmediate(cellParent);
|
||||
return;
|
||||
}
|
||||
|
||||
float safeScale = Mathf.Max(0.001f, visualProfile.scale);
|
||||
float modelScaleMultiplier = 1f;
|
||||
|
||||
float yOffset = grid.Level * floorHeight;
|
||||
Vector3 localPos = new Vector3(x * safeScale, yOffset, z * safeScale);
|
||||
|
||||
GameObject newObj = Instantiate(prefab, _container);
|
||||
|
||||
newObj.transform.localPosition = localPos;
|
||||
newObj.transform.localRotation = rotation;
|
||||
newObj.transform.localScale = Vector3.one * safeScale * modelScaleMultiplier;
|
||||
_spawnedCells[posKey] = newObj;
|
||||
_spawnedCells[posKey] = cellParent;
|
||||
|
||||
if (animate && visualProfile.animationDuration > 0)
|
||||
{
|
||||
StartCoroutine(AnimateCell(newObj.transform));
|
||||
StartCoroutine(AnimateCell(cellParent.transform));
|
||||
}
|
||||
}
|
||||
|
||||
// =================================================================================
|
||||
// THUẬT TOÁN BITMASK AUTO-TILING
|
||||
// =================================================================================
|
||||
private (GameObject, Quaternion) GetCorridorPrefabAndRotation(MazeGrid grid, int x, int z)
|
||||
private (GameObject, Quaternion, Vector3) GetNodePrefabAndRotation(MazeGrid grid, int x, int z)
|
||||
{
|
||||
bool top = IsPath(grid, x, z + 1);
|
||||
bool right = IsPath(grid, x + 1, z);
|
||||
@@ -195,24 +290,32 @@ namespace Hallucinate.GameSetup.Maze
|
||||
|
||||
GameObject prefabToSpawn = null;
|
||||
float yRotation = 0f;
|
||||
Vector3 offset = Vector3.zero;
|
||||
|
||||
// Push dead ends to the boundary where edges end
|
||||
float endOffset = visualProfile.deadEndShift;
|
||||
|
||||
switch (mask)
|
||||
{
|
||||
case 1:
|
||||
prefabToSpawn = visualProfile.corridorDeadEnd;
|
||||
yRotation = 180f;
|
||||
yRotation = 0f;
|
||||
offset = new Vector3(0, 0, endOffset);
|
||||
break;
|
||||
case 2:
|
||||
prefabToSpawn = visualProfile.corridorDeadEnd;
|
||||
yRotation = 270f;
|
||||
yRotation = 90f;
|
||||
offset = new Vector3(endOffset, 0, 0);
|
||||
break;
|
||||
case 4:
|
||||
prefabToSpawn = visualProfile.corridorDeadEnd;
|
||||
yRotation = 0f;
|
||||
yRotation = 180f;
|
||||
offset = new Vector3(0, 0, -endOffset);
|
||||
break;
|
||||
case 8:
|
||||
prefabToSpawn = visualProfile.corridorDeadEnd;
|
||||
yRotation = 90f;
|
||||
yRotation = 270f;
|
||||
offset = new Vector3(-endOffset, 0, 0);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
@@ -243,19 +346,19 @@ namespace Hallucinate.GameSetup.Maze
|
||||
|
||||
case 11:
|
||||
prefabToSpawn = visualProfile.corridorTJunction;
|
||||
yRotation = 180f;
|
||||
yRotation = 0f;
|
||||
break;
|
||||
case 7:
|
||||
prefabToSpawn = visualProfile.corridorTJunction;
|
||||
yRotation = 270f;
|
||||
yRotation = 90f;
|
||||
break;
|
||||
case 14:
|
||||
prefabToSpawn = visualProfile.corridorTJunction;
|
||||
yRotation = 0f;
|
||||
yRotation = 180f;
|
||||
break;
|
||||
case 13:
|
||||
prefabToSpawn = visualProfile.corridorTJunction;
|
||||
yRotation = 90f;
|
||||
yRotation = 270f;
|
||||
break;
|
||||
|
||||
case 15:
|
||||
@@ -276,7 +379,32 @@ namespace Hallucinate.GameSetup.Maze
|
||||
|
||||
if (prefabToSpawn == null) prefabToSpawn = visualProfile.corridorPrefab;
|
||||
|
||||
return (prefabToSpawn, Quaternion.Euler(0, finalRotation, 0));
|
||||
return (prefabToSpawn, Quaternion.Euler(0, finalRotation, 0), offset);
|
||||
}
|
||||
|
||||
private void SpawnRoomEdge(GameObject parent, MazeCellType neighborType, Vector3 offset, float yRot, float safeScale)
|
||||
{
|
||||
if (neighborType == MazeCellType.Room)
|
||||
return; // Open space to another room cell
|
||||
|
||||
GameObject prefabToSpawn = null;
|
||||
|
||||
if (neighborType == MazeCellType.Corridor || neighborType == MazeCellType.Processing || neighborType == MazeCellType.Start || neighborType == MazeCellType.End)
|
||||
{
|
||||
prefabToSpawn = visualProfile.roomDoorwayPrefab;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefabToSpawn = visualProfile.roomWallPrefab;
|
||||
}
|
||||
|
||||
if (prefabToSpawn != null)
|
||||
{
|
||||
GameObject edge = Instantiate(prefabToSpawn, parent.transform);
|
||||
edge.transform.localPosition = offset;
|
||||
edge.transform.localRotation = Quaternion.Euler(0, yRot, 0);
|
||||
edge.transform.localScale = Vector3.one * safeScale;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPath(MazeGrid grid, int x, int z)
|
||||
|
||||
@@ -66,10 +66,28 @@ namespace Hallucinate.GameSetup.Maze
|
||||
[Required]
|
||||
public GameObject corridorDeadEnd;
|
||||
|
||||
[BoxGroup("Room Pieces (Phase 2)")]
|
||||
public GameObject roomFloorPrefab;
|
||||
[BoxGroup("Room Pieces (Phase 2)")]
|
||||
public GameObject roomWallPrefab;
|
||||
[BoxGroup("Room Pieces (Phase 2)")]
|
||||
public GameObject roomCeilingPrefab;
|
||||
[BoxGroup("Room Pieces (Phase 2)")]
|
||||
public GameObject roomDoorwayPrefab;
|
||||
|
||||
[BoxGroup("Visualization")]
|
||||
[MinValue(0.001f)]
|
||||
public float scale = 0.167f;
|
||||
|
||||
[BoxGroup("Visualization")]
|
||||
[MinValue(1f)]
|
||||
[InfoBox("The physical distance between each grid cell. Default is 6 based on 3x3 intersections and 3x2 halls.")]
|
||||
public float nodeSpacing = 6f;
|
||||
|
||||
[BoxGroup("Visualization")]
|
||||
[InfoBox("How far to push dead-end caps from the center so they touch the hallway edge. Default is 1.0.")]
|
||||
public float deadEndShift = 1.0f;
|
||||
|
||||
[BoxGroup("Visualization")]
|
||||
[MinValue(0f)]
|
||||
public float animationDuration = 0.25f;
|
||||
|
||||
@@ -109,8 +109,8 @@ namespace Hallucinate.GameSetup.Maze
|
||||
|
||||
private void GenerateRecursive(MazeGrid grid, int x, int z)
|
||||
{
|
||||
// Boundary and Noise check
|
||||
if (!grid.IsInBounds(x, z)) return;
|
||||
if (grid.GetCell(x, z) != MazeCellType.Wall) return;
|
||||
if (GetNoiseAt(x, z, grid.Width) < CorridorThreshold) return;
|
||||
if (grid.GetCell(x, z) == MazeCellType.Corridor) return;
|
||||
|
||||
@@ -130,6 +130,7 @@ namespace Hallucinate.GameSetup.Maze
|
||||
private IEnumerator GenerateRecursiveStepByStep(MazeGrid grid, int x, int z, int cellsPerFrame)
|
||||
{
|
||||
if (!grid.IsInBounds(x, z)) yield break;
|
||||
if (grid.GetCell(x, z) != MazeCellType.Wall) yield break;
|
||||
if (GetNoiseAt(x, z, grid.Width) < CorridorThreshold) yield break;
|
||||
if (grid.GetCell(x, z) == MazeCellType.Corridor) yield break;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Hallucinate.GameSetup.Maze
|
||||
|
||||
private void GenerateRecursive(MazeGrid grid, int x, int z)
|
||||
{
|
||||
if (grid.GetCell(x, z) != MazeCellType.Wall) return;
|
||||
if (grid.CountSquareNeighbours(x, z, MazeCellType.Corridor) >= DeadEndNeighbourThreshold) return;
|
||||
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
@@ -45,6 +46,7 @@ namespace Hallucinate.GameSetup.Maze
|
||||
|
||||
private IEnumerator GenerateRecursiveStepByStep(MazeGrid grid, int x, int z, int cellsPerFrame)
|
||||
{
|
||||
if (grid.GetCell(x, z) != MazeCellType.Wall) yield break;
|
||||
if (grid.CountSquareNeighbours(x, z, MazeCellType.Corridor) >= DeadEndNeighbourThreshold) yield break;
|
||||
|
||||
grid.SetCell(x, z, MazeCellType.Processing);
|
||||
|
||||
Reference in New Issue
Block a user