update
This commit is contained in:
3
Assets/Scripts/Baba_yaga/GameSetup/MazeRework.meta
Normal file
3
Assets/Scripts/Baba_yaga/GameSetup/MazeRework.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3ecc59dbb0ad4f43bc0c3137bef0d686
|
||||
timeCreated: 1783084450
|
||||
@@ -0,0 +1,54 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.MazeRework
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration asset containing parameters for the reworked maze generation.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "MazeReworkConfig", menuName = "BABA_YAGA/GameSetup/MazeRework")]
|
||||
public class MazeReworkConfig : ScriptableObject
|
||||
{
|
||||
[Header("Grid Dimensions")]
|
||||
[Tooltip("Width of the maze grid. Best if odd to align with wall-corridor-wall patterning.")]
|
||||
[Min(5)]
|
||||
public int width = 21;
|
||||
|
||||
[Tooltip("Depth of the maze grid. Best if odd to align with wall-corridor-wall patterning.")]
|
||||
[Min(5)]
|
||||
public int depth = 21;
|
||||
|
||||
[Header("Seeding")]
|
||||
[Tooltip("If true, a random seed will be generated at runtime.")]
|
||||
public bool useRandomSeed = true;
|
||||
|
||||
[Tooltip("Seed used when useRandomSeed is false.")]
|
||||
public int seed = 1337;
|
||||
|
||||
[Header("Starting Location")]
|
||||
[Tooltip("Starting coordinates for the generator (usually odd numbers like 1, 1).")]
|
||||
public Vector2Int startLocation = new Vector2Int(1, 1);
|
||||
|
||||
[Header("Rooms Configuration")]
|
||||
[Tooltip("Whether to place open rooms in the maze before generating corridors.")]
|
||||
public bool generateRooms = true;
|
||||
|
||||
[Tooltip("Number of rooms to attempt to generate.")]
|
||||
[Min(0)]
|
||||
public int roomCount = 3;
|
||||
|
||||
[Tooltip("Minimum room size (width and height).")]
|
||||
public Vector2Int minRoomSize = new Vector2Int(3, 3);
|
||||
|
||||
[Tooltip("Maximum room size (width and height).")]
|
||||
public Vector2Int maxRoomSize = new Vector2Int(5, 5);
|
||||
|
||||
[Header("Maze Density & Loops")]
|
||||
[Tooltip("Probability (0 to 1) of carving extra doors for rooms to allow multiple entry points.")]
|
||||
[Range(0f, 1f)]
|
||||
public float extraRoomDoorChance = 0.3f;
|
||||
|
||||
[Tooltip("Probability (0 to 1) of removing dead-ends or carving random extra walls to introduce loops/alternative paths.")]
|
||||
[Range(0f, 1f)]
|
||||
public float loopChance = 0.1f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 30892b1102feb6841a9288ddb11ef50d
|
||||
@@ -0,0 +1,514 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.MazeRework
|
||||
{
|
||||
/// <summary>
|
||||
/// Logical cell types for the reworked maze.
|
||||
/// </summary>
|
||||
public enum MazeReworkCellType
|
||||
{
|
||||
Wall,
|
||||
Corridor,
|
||||
Room,
|
||||
Start,
|
||||
End,
|
||||
StairsUp,
|
||||
StairsDown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Standalone generator class that generates an x * y maze using a MazeReworkConfig object.
|
||||
/// Returns a 2D array of MazeReworkCellType with no legacy dependencies.
|
||||
/// </summary>
|
||||
public class MazeReworkGenerator
|
||||
{
|
||||
public struct Room
|
||||
{
|
||||
public int x;
|
||||
public int z;
|
||||
public int width;
|
||||
public int depth;
|
||||
public bool isConnected;
|
||||
|
||||
public bool Overlaps(Room other, int padding)
|
||||
{
|
||||
return x - padding < other.x + other.width &&
|
||||
x + width + padding > other.x &&
|
||||
z - padding < other.z + other.depth &&
|
||||
z + depth + padding > other.z;
|
||||
}
|
||||
|
||||
public Vector2Int GetCenter()
|
||||
{
|
||||
return new Vector2Int(x + width / 2, z + depth / 2);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly MazeReworkConfig _config;
|
||||
|
||||
public MazeReworkGenerator(MazeReworkConfig config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a 2D layout of the maze of size config.width by config.depth using the default config seed.
|
||||
/// </summary>
|
||||
public MazeReworkCellType[,] Generate()
|
||||
{
|
||||
if (_config == null) return null;
|
||||
int seed = _config.useRandomSeed ? System.DateTime.Now.Millisecond + System.Threading.Thread.CurrentThread.ManagedThreadId : _config.seed;
|
||||
return Generate(seed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a 2D layout of the maze of size config.width by config.depth using a specific seed.
|
||||
/// </summary>
|
||||
public MazeReworkCellType[,] Generate(int seed)
|
||||
{
|
||||
if (_config == null) return null;
|
||||
|
||||
int width = _config.width;
|
||||
int depth = _config.depth;
|
||||
var grid = new MazeReworkCellType[width, depth];
|
||||
|
||||
// 1. Initialize all cells as Wall
|
||||
for (int z = 0; z < depth; z++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
grid[x, z] = MazeReworkCellType.Wall;
|
||||
}
|
||||
}
|
||||
|
||||
System.Random rng = new System.Random(seed);
|
||||
|
||||
// 2. Generate Rooms
|
||||
List<Room> rooms = GenerateRooms(grid, width, depth, rng);
|
||||
|
||||
// 3. Setup Visited tracking array
|
||||
bool[,] visited = new bool[width, depth];
|
||||
InitializeVisitedMap(grid, visited, rooms, width, depth);
|
||||
|
||||
// 4. Select starting coordinates
|
||||
int startX = _config.startLocation.x;
|
||||
int startZ = _config.startLocation.y;
|
||||
EnsureValidOddCoordinates(width, depth, ref startX, ref startZ);
|
||||
|
||||
// 5. Carve Corridors recursively using Step-by-2 Recursive Backtracking
|
||||
visited[startX, startZ] = true;
|
||||
grid[startX, startZ] = MazeReworkCellType.Corridor;
|
||||
CarveFrom(startX, startZ, grid, visited, rooms, rng, width, depth);
|
||||
|
||||
// 6. Hunt & Kill scan to connect isolated rooms/corridors
|
||||
EnsureAllConnected(grid, visited, rooms, rng, width, depth);
|
||||
|
||||
// 7. Carve optional loops/alternative paths
|
||||
CarveLoops(grid, rng, width, depth);
|
||||
|
||||
// 8. Place Start & End points
|
||||
PlaceStartAndEnd(grid, rooms, width, depth);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
#region Internal Generation Logic
|
||||
|
||||
private List<Room> GenerateRooms(MazeReworkCellType[,] grid, int width, int depth, System.Random rng)
|
||||
{
|
||||
List<Room> rooms = new List<Room>();
|
||||
if (!_config.generateRooms) return rooms;
|
||||
|
||||
for (int i = 0; i < _config.roomCount; i++)
|
||||
{
|
||||
int minW = Mathf.Max(3, _config.minRoomSize.x);
|
||||
int maxW = Mathf.Max(minW, _config.maxRoomSize.x);
|
||||
int minH = Mathf.Max(3, _config.minRoomSize.y);
|
||||
int maxH = Mathf.Max(minH, _config.maxRoomSize.y);
|
||||
|
||||
int rw = 2 * (rng.Next(minW / 2, maxW / 2 + 1)) + 1;
|
||||
int rh = 2 * (rng.Next(minH / 2, maxH / 2 + 1)) + 1;
|
||||
|
||||
int maxX = (width - rw - 1) / 2;
|
||||
int maxZ = (depth - rh - 1) / 2;
|
||||
|
||||
if (maxX < 1 || maxZ < 1) continue;
|
||||
|
||||
int rx = 2 * rng.Next(0, maxX) + 1;
|
||||
int rz = 2 * rng.Next(0, maxZ) + 1;
|
||||
|
||||
Room newRoom = new Room { x = rx, z = rz, width = rw, depth = rh, isConnected = false };
|
||||
|
||||
bool overlaps = false;
|
||||
foreach (var r in rooms)
|
||||
{
|
||||
if (newRoom.Overlaps(r, 1))
|
||||
{
|
||||
overlaps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!overlaps)
|
||||
{
|
||||
rooms.Add(newRoom);
|
||||
// Carve room cells
|
||||
for (int z = rz; z < rz + rh; z++)
|
||||
{
|
||||
for (int x = rx; x < rx + rw; x++)
|
||||
{
|
||||
grid[x, z] = MazeReworkCellType.Room;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
private void InitializeVisitedMap(MazeReworkCellType[,] grid, bool[,] visited, List<Room> rooms, int width, int depth)
|
||||
{
|
||||
foreach (var r in rooms)
|
||||
{
|
||||
for (int z = r.z; z < r.z + r.depth; z++)
|
||||
{
|
||||
for (int x = r.x; x < r.x + r.width; x++)
|
||||
{
|
||||
visited[x, z] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CarveFrom(int cx, int cz, MazeReworkCellType[,] grid, bool[,] visited, List<Room> rooms, System.Random rng, int width, int depth)
|
||||
{
|
||||
Vector2Int[] dirs = new Vector2Int[4]
|
||||
{
|
||||
new Vector2Int(2, 0),
|
||||
new Vector2Int(-2, 0),
|
||||
new Vector2Int(0, 2),
|
||||
new Vector2Int(0, -2)
|
||||
};
|
||||
|
||||
for (int i = 3; i > 0; i--)
|
||||
{
|
||||
int j = rng.Next(i + 1);
|
||||
var temp = dirs[i];
|
||||
dirs[i] = dirs[j];
|
||||
dirs[j] = temp;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int nx = cx + dirs[i].x;
|
||||
int nz = cz + dirs[i].y;
|
||||
int wx = cx + dirs[i].x / 2;
|
||||
int wz = cz + dirs[i].y / 2;
|
||||
|
||||
if (nx > 0 && nx < width - 1 && nz > 0 && nz < depth - 1)
|
||||
{
|
||||
if (!visited[nx, nz])
|
||||
{
|
||||
grid[wx, wz] = MazeReworkCellType.Corridor;
|
||||
grid[nx, nz] = MazeReworkCellType.Corridor;
|
||||
visited[wx, wz] = true;
|
||||
visited[nx, nz] = true;
|
||||
CarveFrom(nx, nz, grid, visited, rooms, rng, width, depth);
|
||||
}
|
||||
else if (grid[nx, nz] == MazeReworkCellType.Room)
|
||||
{
|
||||
int roomIndex = FindRoomIndex(rooms, nx, nz);
|
||||
if (roomIndex >= 0)
|
||||
{
|
||||
var room = rooms[roomIndex];
|
||||
if (!room.isConnected)
|
||||
{
|
||||
grid[wx, wz] = MazeReworkCellType.Corridor;
|
||||
visited[wx, wz] = true;
|
||||
room.isConnected = true;
|
||||
rooms[roomIndex] = room;
|
||||
}
|
||||
else if (rng.NextDouble() < _config.extraRoomDoorChance)
|
||||
{
|
||||
if (grid[wx, wz] == MazeReworkCellType.Wall)
|
||||
{
|
||||
grid[wx, wz] = MazeReworkCellType.Corridor;
|
||||
visited[wx, wz] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureAllConnected(MazeReworkCellType[,] grid, bool[,] visited, List<Room> rooms, System.Random rng, int width, int depth)
|
||||
{
|
||||
for (int z = 1; z < depth - 1; z += 2)
|
||||
{
|
||||
for (int x = 1; x < width - 1; x += 2)
|
||||
{
|
||||
if (!visited[x, z])
|
||||
{
|
||||
Vector2Int[] dirs = new Vector2Int[4]
|
||||
{
|
||||
new Vector2Int(2, 0),
|
||||
new Vector2Int(-2, 0),
|
||||
new Vector2Int(0, 2),
|
||||
new Vector2Int(0, -2)
|
||||
};
|
||||
|
||||
for (int i = 3; i > 0; i--)
|
||||
{
|
||||
int j = rng.Next(i + 1);
|
||||
var temp = dirs[i];
|
||||
dirs[i] = dirs[j];
|
||||
dirs[j] = temp;
|
||||
}
|
||||
|
||||
bool connected = false;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int nx = x + dirs[i].x;
|
||||
int nz = z + dirs[i].y;
|
||||
int wx = x + dirs[i].x / 2;
|
||||
int wz = z + dirs[i].y / 2;
|
||||
|
||||
if (nx > 0 && nx < width - 1 && nz > 0 && nz < depth - 1)
|
||||
{
|
||||
if (visited[nx, nz])
|
||||
{
|
||||
grid[wx, wz] = MazeReworkCellType.Corridor;
|
||||
grid[x, z] = MazeReworkCellType.Corridor;
|
||||
visited[wx, wz] = true;
|
||||
visited[x, z] = true;
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (connected)
|
||||
{
|
||||
CarveFrom(x, z, grid, visited, rooms, rng, width, depth);
|
||||
}
|
||||
else
|
||||
{
|
||||
CarveFrom(x, z, grid, visited, rooms, rng, width, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < rooms.Count; i++)
|
||||
{
|
||||
var room = rooms[i];
|
||||
if (!room.isConnected)
|
||||
{
|
||||
ForceConnectRoom(grid, room, visited, rng, width, depth);
|
||||
room.isConnected = true;
|
||||
rooms[i] = room;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ForceConnectRoom(MazeReworkCellType[,] grid, Room room, bool[,] visited, System.Random rng, int width, int depth)
|
||||
{
|
||||
int[] sides = new int[] { 0, 1, 2, 3 };
|
||||
for (int i = 3; i > 0; i--)
|
||||
{
|
||||
int j = rng.Next(i + 1);
|
||||
int temp = sides[i];
|
||||
sides[i] = sides[j];
|
||||
sides[j] = temp;
|
||||
}
|
||||
|
||||
foreach (int side in sides)
|
||||
{
|
||||
if (side == 0)
|
||||
{
|
||||
int z = room.z + room.depth - 1;
|
||||
if (z + 2 < depth - 1)
|
||||
{
|
||||
int x = room.x + 2 * rng.Next(0, room.width / 2);
|
||||
grid[x, z + 1] = MazeReworkCellType.Corridor;
|
||||
visited[x, z + 1] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (side == 1)
|
||||
{
|
||||
int z = room.z;
|
||||
if (z - 2 > 0)
|
||||
{
|
||||
int x = room.x + 2 * rng.Next(0, room.width / 2);
|
||||
grid[x, z - 1] = MazeReworkCellType.Corridor;
|
||||
visited[x, z - 1] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (side == 2)
|
||||
{
|
||||
int x = room.x;
|
||||
if (x - 2 > 0)
|
||||
{
|
||||
int z = room.z + 2 * rng.Next(0, room.depth / 2);
|
||||
grid[x - 1, z] = MazeReworkCellType.Corridor;
|
||||
visited[x - 1, z] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (side == 3)
|
||||
{
|
||||
int x = room.x + room.width - 1;
|
||||
if (x + 2 < width - 1)
|
||||
{
|
||||
int z = room.z + 2 * rng.Next(0, room.depth / 2);
|
||||
grid[x + 1, z] = MazeReworkCellType.Corridor;
|
||||
visited[x + 1, z] = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CarveLoops(MazeReworkCellType[,] grid, System.Random rng, int width, int depth)
|
||||
{
|
||||
if (_config.loopChance <= 0f) return;
|
||||
|
||||
for (int z = 1; z < depth - 1; z++)
|
||||
{
|
||||
for (int x = 1; x < width - 1; x++)
|
||||
{
|
||||
bool isHorizontalWall = (x % 2 == 0 && z % 2 != 0);
|
||||
bool isVerticalWall = (x % 2 != 0 && z % 2 == 0);
|
||||
|
||||
if (!isHorizontalWall && !isVerticalWall) continue;
|
||||
|
||||
if (grid[x, z] == MazeReworkCellType.Wall)
|
||||
{
|
||||
if (isHorizontalWall)
|
||||
{
|
||||
if (IsPathCell(grid[x - 1, z]) && IsPathCell(grid[x + 1, z]))
|
||||
{
|
||||
if (rng.NextDouble() < _config.loopChance)
|
||||
{
|
||||
grid[x, z] = MazeReworkCellType.Corridor;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsPathCell(grid[x, z - 1]) && IsPathCell(grid[x, z + 1]))
|
||||
{
|
||||
if (rng.NextDouble() < _config.loopChance)
|
||||
{
|
||||
grid[x, z] = MazeReworkCellType.Corridor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaceStartAndEnd(MazeReworkCellType[,] grid, List<Room> rooms, int width, int depth)
|
||||
{
|
||||
if (rooms != null && rooms.Count > 0)
|
||||
{
|
||||
var startRoom = rooms[0];
|
||||
var startPt = startRoom.GetCenter();
|
||||
grid[startPt.x, startPt.y] = MazeReworkCellType.Start;
|
||||
|
||||
var endRoom = rooms[rooms.Count - 1];
|
||||
var endPt = endRoom.GetCenter();
|
||||
if (endPt == startPt && rooms.Count > 1)
|
||||
{
|
||||
endRoom = rooms[1];
|
||||
endPt = endRoom.GetCenter();
|
||||
}
|
||||
grid[endPt.x, endPt.y] = MazeReworkCellType.End;
|
||||
}
|
||||
else
|
||||
{
|
||||
int sx = _config.startLocation.x;
|
||||
int sz = _config.startLocation.y;
|
||||
EnsureValidOddCoordinates(width, depth, ref sx, ref sz);
|
||||
grid[sx, sz] = MazeReworkCellType.Start;
|
||||
|
||||
int ex = width - 2;
|
||||
int ez = depth - 2;
|
||||
EnsureValidOddCoordinates(width, depth, ref ex, ref ez);
|
||||
|
||||
if (grid[ex, ez] == MazeReworkCellType.Wall)
|
||||
{
|
||||
bool found = false;
|
||||
for (int r = 1; r < Mathf.Max(width, depth) && !found; r++)
|
||||
{
|
||||
for (int dx = -r; dx <= r && !found; dx++)
|
||||
{
|
||||
for (int dz = -r; dz <= r && !found; dz++)
|
||||
{
|
||||
int tx = ex + dx;
|
||||
int tz = ez + dz;
|
||||
if (tx >= 0 && tx < width && tz >= 0 && tz < depth && IsPathCell(grid[tx, tz]))
|
||||
{
|
||||
ex = tx;
|
||||
ez = tz;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ex != sx || ez != sz)
|
||||
{
|
||||
grid[ex, ez] = MazeReworkCellType.End;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Functions
|
||||
|
||||
private void EnsureValidOddCoordinates(int width, int depth, ref int x, ref int z)
|
||||
{
|
||||
if (x < 1) x = 1;
|
||||
if (x >= width - 1) x = width - 2;
|
||||
if (x % 2 == 0) x--;
|
||||
|
||||
if (z < 1) z = 1;
|
||||
if (z >= depth - 1) z = depth - 2;
|
||||
if (z % 2 == 0) z--;
|
||||
}
|
||||
|
||||
private int FindRoomIndex(List<Room> rooms, int x, int z)
|
||||
{
|
||||
for (int i = 0; i < rooms.Count; i++)
|
||||
{
|
||||
var r = rooms[i];
|
||||
if (x >= r.x && x < r.x + r.width && z >= r.z && z < r.z + r.depth)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private bool IsPathCell(MazeReworkCellType type)
|
||||
{
|
||||
return type == MazeReworkCellType.Corridor ||
|
||||
type == MazeReworkCellType.Room ||
|
||||
type == MazeReworkCellType.Start ||
|
||||
type == MazeReworkCellType.End ||
|
||||
type == MazeReworkCellType.StairsUp ||
|
||||
type == MazeReworkCellType.StairsDown;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 389ebe530f56f834cafe0199c9e75c90
|
||||
@@ -0,0 +1,188 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Sirenix.OdinInspector;
|
||||
|
||||
namespace Baba_yaga.GameSetup.MazeRework
|
||||
{
|
||||
/// <summary>
|
||||
/// Manager script that coordinates generating the 2D maze grid layout and spawning prefabs.
|
||||
/// Completely independent of the legacy maze system and renderer.
|
||||
/// </summary>
|
||||
public class MazeReworkManager : MonoBehaviour
|
||||
{
|
||||
[Header("Configuration")]
|
||||
[SerializeField] private MazeReworkConfig config;
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField] private MazeReworkSpawner spawner;
|
||||
[SerializeField] private Transform mazeContainer;
|
||||
|
||||
[Header("Multi-floor Settings")]
|
||||
[Tooltip("Number of floors to generate.")]
|
||||
[Min(1)]
|
||||
[SerializeField] private int floorCount = 1;
|
||||
|
||||
[Tooltip("Physical height difference between adjacent floors.")]
|
||||
[SerializeField] private float floorHeight = 4.0f;
|
||||
|
||||
[Tooltip("Number of staircase connections to generate between adjacent floors.")]
|
||||
[Min(0)]
|
||||
[SerializeField] private int connectionsPerFloor = 2;
|
||||
|
||||
private List<MazeReworkCellType[,]> _grids = new List<MazeReworkCellType[,]>();
|
||||
|
||||
private void Start()
|
||||
{
|
||||
GenerateAndSpawn();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.R))
|
||||
{
|
||||
GenerateAndSpawn();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all previously spawned maze elements inside the target container.
|
||||
/// Works in both Play Mode and Edit Mode (deep cleanup).
|
||||
/// </summary>
|
||||
[Button("Clear Maze", ButtonSizes.Large)]
|
||||
[ContextMenu("Clear Maze")]
|
||||
public void ClearMaze()
|
||||
{
|
||||
if (spawner != null)
|
||||
{
|
||||
spawner.Clear();
|
||||
}
|
||||
_grids.Clear();
|
||||
|
||||
// Perform deep hierarchy scan in the container to destroy untracked objects
|
||||
Transform container = mazeContainer != null ? mazeContainer : transform;
|
||||
for (int i = container.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = container.GetChild(i).gameObject;
|
||||
if (Application.isPlaying)
|
||||
Destroy(child);
|
||||
else
|
||||
DestroyImmediate(child);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates the maze grid data using the rework generator and initializes the spawner.
|
||||
/// </summary>
|
||||
[Button("Generate Maze", ButtonSizes.Large)]
|
||||
[ContextMenu("Generate Maze")]
|
||||
public void GenerateAndSpawn()
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
Debug.LogError("MazeReworkManager: Config is not assigned!", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spawner == null)
|
||||
{
|
||||
Debug.LogError("MazeReworkManager: Spawner is not assigned!", this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mazeContainer == null)
|
||||
{
|
||||
mazeContainer = this.transform;
|
||||
}
|
||||
|
||||
// Clear any previously spawned maze objects
|
||||
ClearMaze();
|
||||
|
||||
var generator = new MazeReworkGenerator(config);
|
||||
|
||||
// Generate each floor using the configuration
|
||||
for (int i = 0; i < floorCount; i++)
|
||||
{
|
||||
int seed = config.useRandomSeed ? Random.Range(0, 1000000) : config.seed + i * 1000;
|
||||
var grid = generator.Generate(seed);
|
||||
_grids.Add(grid);
|
||||
}
|
||||
|
||||
// Connect floors with staircases if multiple floors are present
|
||||
if (floorCount > 1)
|
||||
{
|
||||
GenerateConnections();
|
||||
}
|
||||
|
||||
// Spawn prefabs for all floors
|
||||
for (int i = 0; i < floorCount; i++)
|
||||
{
|
||||
float yOffset = i * floorHeight;
|
||||
spawner.Spawn(_grids[i], yOffset, mazeContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateConnections()
|
||||
{
|
||||
int seed = config.useRandomSeed ? System.DateTime.Now.Millisecond : config.seed;
|
||||
System.Random rng = new System.Random(seed);
|
||||
|
||||
for (int i = 0; i < floorCount - 1; i++)
|
||||
{
|
||||
var currentFloor = _grids[i];
|
||||
var nextFloor = _grids[i + 1];
|
||||
|
||||
List<Vector2Int> possibleConnections = new List<Vector2Int>();
|
||||
|
||||
int width = config.width;
|
||||
int depth = config.depth;
|
||||
|
||||
for (int z = 1; z < depth - 1; z++)
|
||||
{
|
||||
for (int x = 1; x < width - 1; x++)
|
||||
{
|
||||
// A staircase requires path cells (corridors or rooms) to exist at the exact same x, z on both floors
|
||||
bool isCurrentFloorPath = IsPathCell(currentFloor[x, z]);
|
||||
bool isNextFloorPath = IsPathCell(nextFloor[x, z]);
|
||||
|
||||
if (isCurrentFloorPath && isNextFloorPath)
|
||||
{
|
||||
possibleConnections.Add(new Vector2Int(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle connections
|
||||
for (int k = possibleConnections.Count - 1; k > 0; k--)
|
||||
{
|
||||
int idx = rng.Next(k + 1);
|
||||
var temp = possibleConnections[k];
|
||||
possibleConnections[k] = possibleConnections[idx];
|
||||
possibleConnections[idx] = temp;
|
||||
}
|
||||
|
||||
int connectionsMade = 0;
|
||||
foreach (Vector2Int pos in possibleConnections)
|
||||
{
|
||||
if (connectionsMade >= connectionsPerFloor) break;
|
||||
|
||||
int x = pos.x;
|
||||
int z = pos.y;
|
||||
|
||||
// Set stair cells (stairs up on lower floor, stairs down on upper floor)
|
||||
currentFloor[x, z] = MazeReworkCellType.StairsUp;
|
||||
nextFloor[x, z] = MazeReworkCellType.StairsDown;
|
||||
|
||||
connectionsMade++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPathCell(MazeReworkCellType type)
|
||||
{
|
||||
return type == MazeReworkCellType.Corridor ||
|
||||
type == MazeReworkCellType.Room ||
|
||||
type == MazeReworkCellType.Start ||
|
||||
type == MazeReworkCellType.End;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e30aa4eff6d79394ea1a19fe77e97b1e
|
||||
@@ -0,0 +1,171 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.MazeRework
|
||||
{
|
||||
/// <summary>
|
||||
/// Spawns the modular prefabs (U-Turn "U", turn "L", Hall "I", HallT "⊢", HallDoubleRoom "+")
|
||||
/// based purely on neighbor connectivity. Wall cells spawn nothing.
|
||||
/// </summary>
|
||||
public class MazeReworkSpawner : MonoBehaviour
|
||||
{
|
||||
[Header("Modular Hallway Prefabs")]
|
||||
[Tooltip("Prefab for Hall 'I' (Straight connection - opposite paths).")]
|
||||
public GameObject hallPrefab;
|
||||
[Tooltip("Prefab for HallDoubleRoom '+' (Crossroad connection - 4-way intersection).")]
|
||||
public GameObject hallDoubleRoomPrefab;
|
||||
[Tooltip("Prefab for HallT '⊢' (T-Junction connection - 3-way intersection).")]
|
||||
public GameObject hallTPrefab;
|
||||
[Tooltip("Prefab for turn 'L' (Corner connection - 2 adjacent paths).")]
|
||||
public GameObject turnPrefab;
|
||||
[Tooltip("Prefab for U-Turn 'U' (Dead end - 1 path).")]
|
||||
public GameObject uTurnPrefab;
|
||||
|
||||
[Header("Rotation Offsets (Degrees)")]
|
||||
[Tooltip("Rotation offset added to Hall 'I' prefab.")]
|
||||
public float hallRotationOffset = 0f;
|
||||
[Tooltip("Rotation offset added to HallDoubleRoom '+' prefab.")]
|
||||
public float hallDoubleRoomRotationOffset = 0f;
|
||||
[Tooltip("Rotation offset added to HallT '⊢' prefab.")]
|
||||
public float hallTRotationOffset = 0f;
|
||||
[Tooltip("Rotation offset added to turn 'L' prefab.")]
|
||||
public float turnRotationOffset = 0f;
|
||||
[Tooltip("Rotation offset added to U-Turn 'U' prefab.")]
|
||||
public float uTurnRotationOffset = 0f;
|
||||
|
||||
[Header("Spacing Settings")]
|
||||
[Tooltip("Physical distance between each grid cell center.")]
|
||||
public float spacing = 3.0f;
|
||||
|
||||
private readonly List<GameObject> _spawnedObjects = new List<GameObject>();
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ValidatePrefabs();
|
||||
}
|
||||
|
||||
private void ValidatePrefabs()
|
||||
{
|
||||
if (hallPrefab == null || hallDoubleRoomPrefab == null || hallTPrefab == null || turnPrefab == null || uTurnPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("<b>[MazeReworkSpawner]</b> One or more modular hallway prefabs are not assigned in the Inspector! Please assign your Hall, HallDoubleRoom, HallT, turn, and U-Turn prefabs.", this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all spawned assets.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var obj in _spawnedObjects)
|
||||
{
|
||||
if (obj == null) continue;
|
||||
if (Application.isPlaying)
|
||||
Destroy(obj);
|
||||
else
|
||||
DestroyImmediate(obj);
|
||||
}
|
||||
_spawnedObjects.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the modular prefabs based on the generated 2D grid array.
|
||||
/// </summary>
|
||||
public void Spawn(MazeReworkCellType[,] grid, float yOffset, Transform container)
|
||||
{
|
||||
if (grid == null) return;
|
||||
ValidatePrefabs();
|
||||
|
||||
int width = grid.GetLength(0);
|
||||
int depth = grid.GetLength(1);
|
||||
|
||||
for (int z = 0; z < depth; z++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
MazeReworkCellType type = grid[x, z];
|
||||
|
||||
// Wall cells spawn nothing
|
||||
if (type == MazeReworkCellType.Wall)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Evaluate connectivity of path cells to select and rotate the modular hallway prefab
|
||||
(GameObject modularPrefab, float yRotation) = GetModularPrefabAndRotation(grid, x, z, width, depth);
|
||||
if (modularPrefab != null)
|
||||
{
|
||||
SpawnPrefab(modularPrefab, x, yOffset, z, yRotation, container, $"{type}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnPrefab(GameObject prefab, int x, float y, int z, float yRotation, Transform container, string namePrefix)
|
||||
{
|
||||
Vector3 localPosition = new Vector3(x * spacing, y, z * spacing);
|
||||
GameObject spawnedObj = Instantiate(prefab, container != null ? container : transform);
|
||||
spawnedObj.transform.localPosition = localPosition;
|
||||
spawnedObj.transform.localRotation = Quaternion.Euler(0f, yRotation, 0f);
|
||||
spawnedObj.name = $"{namePrefix}_{x}_{z}";
|
||||
_spawnedObjects.Add(spawnedObj);
|
||||
}
|
||||
|
||||
private (GameObject, float) GetModularPrefabAndRotation(MazeReworkCellType[,] grid, int x, int z, int width, int depth)
|
||||
{
|
||||
bool top = IsPath(grid, x, z + 1, width, depth);
|
||||
bool right = IsPath(grid, x + 1, z, width, depth);
|
||||
bool bottom = IsPath(grid, x, z - 1, width, depth);
|
||||
bool left = IsPath(grid, x - 1, z, width, depth);
|
||||
|
||||
int mask = 0;
|
||||
if (top) mask += 1;
|
||||
if (right) mask += 2;
|
||||
if (bottom) mask += 4;
|
||||
if (left) mask += 8;
|
||||
|
||||
switch (mask)
|
||||
{
|
||||
// --- 1 open connection: U-Turn "U" ---
|
||||
case 1: return (uTurnPrefab, 0f + uTurnRotationOffset); // Open to Top
|
||||
case 2: return (uTurnPrefab, 90f + uTurnRotationOffset); // Open to Right
|
||||
case 4: return (uTurnPrefab, 180f + uTurnRotationOffset); // Open to Bottom
|
||||
case 8: return (uTurnPrefab, 270f + uTurnRotationOffset); // Open to Left
|
||||
|
||||
// --- 2 opposite open connections: Hall "I" ---
|
||||
case 5: return (hallPrefab, 0f + hallRotationOffset); // Open to Top & Bottom
|
||||
case 10: return (hallPrefab, 90f + hallRotationOffset); // Open to Left & Right
|
||||
|
||||
// --- 2 adjacent open connections: turn "L" ---
|
||||
case 3: return (turnPrefab, 0f + turnRotationOffset); // Open to Top & Right
|
||||
case 6: return (turnPrefab, 90f + turnRotationOffset); // Open to Right & Bottom
|
||||
case 12: return (turnPrefab, 180f + turnRotationOffset); // Open to Bottom & Left
|
||||
case 9: return (turnPrefab, 270f + turnRotationOffset); // Open to Left & Top
|
||||
|
||||
// --- 3 open connections: HallT "⊢" ---
|
||||
case 11: return (hallTPrefab, 0f + hallTRotationOffset); // Open to Top, Right, Left (no Bottom)
|
||||
case 7: return (hallTPrefab, 90f + hallTRotationOffset); // Open to Top, Right, Bottom (no Left)
|
||||
case 14: return (hallTPrefab, 180f + hallTRotationOffset); // Open to Right, Bottom, Left (no Top)
|
||||
case 13: return (hallTPrefab, 270f + hallTRotationOffset); // Open to Bottom, Left, Top (no Right)
|
||||
|
||||
// --- 4 open connections: HallDoubleRoom "+" ---
|
||||
case 15: return (hallDoubleRoomPrefab, 0f + hallDoubleRoomRotationOffset); // Open in all 4 directions
|
||||
|
||||
// Fallback for isolated cells (0 connections)
|
||||
default: return (uTurnPrefab, 0f + uTurnRotationOffset);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPath(MazeReworkCellType[,] grid, int x, int z, int width, int depth)
|
||||
{
|
||||
if (x < 0 || x >= width || z < 0 || z >= depth) return false;
|
||||
MazeReworkCellType type = grid[x, z];
|
||||
return type == MazeReworkCellType.Corridor ||
|
||||
type == MazeReworkCellType.Room ||
|
||||
type == MazeReworkCellType.Start ||
|
||||
type == MazeReworkCellType.End ||
|
||||
type == MazeReworkCellType.StairsUp ||
|
||||
type == MazeReworkCellType.StairsDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d9d9bca23a6e664fa3f01d404f31beb
|
||||
Reference in New Issue
Block a user