using System.Collections; using System.Collections.Generic; using Sirenix.OdinInspector; using UnityEngine; namespace Hallucinate.GameSetup.Maze { /// /// Central controller for the Maze system. /// Manages algorithm selection, debug speed, and regeneration. /// public class MazeManager : MonoBehaviour { public enum AlgorithmType { Recursive, Wilsons, Prims, Crawler, NoiseRecursive } [BoxGroup("Generation")] [InfoBox("Set the array size to control how many maze floors are generated. Runtime grid data is rebuilt when Regenerate runs.")] [ValidateInput(nameof(HasAtLeastOneFloor), "Maze Manager needs at least one floor.")] public MazeGrid[] mazes; [BoxGroup("Generation")] [MinValue(0.001f)] public float floorHeight = 3.5f; [BoxGroup("Generation")] [MinValue(0)] public int connectionsPerFloor = 2; [BoxGroup("Generation")] [SerializeField] private AlgorithmType selectedAlgorithm; [BoxGroup("Generation/Grid Size")] [PropertyRange(5, 200)] [SerializeField] private int width = 30; [BoxGroup("Generation/Grid Size")] [PropertyRange(5, 200)] [SerializeField] private int depth = 30; [BoxGroup("Debug")] [SerializeField] private bool debugMode = true; [BoxGroup("Debug")] [ShowIf(nameof(debugMode))] [PropertyRange(0.001f, 0.5f)] [SerializeField] private float visualizationInterval = 0.05f; [BoxGroup("References")] [Required] [SerializeField] private MazeRenderer mazeRenderer; [BoxGroup("References")] [Required] [SerializeField] private Transform mazeContainer; [FoldoutGroup("Manhole Prefabs")] public GameObject straightManHoleLadder; [FoldoutGroup("Manhole Prefabs")] public GameObject straightManHoleUp; [FoldoutGroup("Manhole Prefabs")] public GameObject deadendManHoleLadder; [FoldoutGroup("Manhole Prefabs")] public GameObject deadendManHoleUp; [ShowInInspector] [ReadOnly] [BoxGroup("Runtime")] private int FloorCount => mazes?.Length ?? 0; [ShowInInspector] [ReadOnly] [BoxGroup("Runtime")] private string CurrentGrid => _grid == null ? "None" : $"{_grid.Width}x{_grid.Depth}, Level {_grid.Level}"; private MazeGrid _grid; private Coroutine _generationCoroutine; private void Start() { Regenerate(); } private void Update() { if (Input.GetKeyDown(KeyCode.R)) { Regenerate(); } } [ContextMenu("Regenerate")] [Button("Regenerate Maze", ButtonSizes.Large)] public void Regenerate() { if (mazeRenderer == null) { Debug.LogError("MazeManager needs a MazeRenderer reference before regenerating.", this); return; } if (mazeContainer == null) { Debug.LogError("MazeManager needs a maze container reference before regenerating.", this); return; } if (mazes == null || mazes.Length == 0) { mazes = new MazeGrid[1]; } if (_generationCoroutine != null) { StopCoroutine(_generationCoroutine); } mazeRenderer.Clear(); // Step 1: Initialize all maze floors for (int i = 0; i < mazes.Length; i++) { mazes[i] = new MazeGrid(width, depth); mazes[i].Level = i; // Generate each floor using the selected algorithm IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm); algorithmForFloor.Generate(mazes[i]); } // Step 2: Create connections between adjacent floors for (int i = 0; i < mazes.Length - 1; i++) { MazeGrid currentFloor = mazes[i]; MazeGrid nextFloor = mazes[i + 1]; List possibleConnections = new List(); for (int z = 0; z < depth; z++) { for (int x = 0; x < width; x++) { // Check if both floors have a corridor at this position bool isCurrentFloorPath = currentFloor.GetCell(x, z) == MazeCellType.Corridor; bool isNextFloorPath = nextFloor.GetCell(x, z) == MazeCellType.Corridor; if (isCurrentFloorPath && isNextFloorPath) { possibleConnections.Add(new Vector2Int(x, z)); } } } ShuffleList(possibleConnections); int connectionsMade = 0; foreach (Vector2Int pos in possibleConnections) { if (connectionsMade >= connectionsPerFloor) break; int x = pos.x; int z = pos.y; // Set stair cells currentFloor.SetCell(x, z, MazeCellType.StairsUp); nextFloor.SetCell(x, z, MazeCellType.StairsDown); connectionsMade++; } } // Step 3: Render all floors if (mazes.Length > 0) { for (int i = 0; i < mazes.Length; i++) { mazeRenderer.Initialize(mazes[i], mazeContainer, i == 0); } _grid = mazes[0]; } else { // _grid = new MazeGrid(width, depth); // mazeRenderer.Initialize(_grid, mazeContainer); IMazeAlgorithm algorithm = GetAlgorithm(selectedAlgorithm); if (debugMode) { _generationCoroutine = StartCoroutine(algorithm.GenerateStepByStep(_grid, visualizationInterval)); } else { algorithm.Generate(_grid); } _grid = new MazeGrid(width, depth); mazeRenderer.Initialize(_grid, mazeContainer); } } private void ShuffleList(List list) { for (int i = 0; i < list.Count; i++) { T temp = list[i]; int randomIndex = Random.Range(i, list.Count); list[i] = list[randomIndex]; list[randomIndex] = temp; } } private IMazeAlgorithm GetAlgorithm(AlgorithmType type) { return type switch { AlgorithmType.Recursive => new RecursiveAlgorithm(), AlgorithmType.Wilsons => new WilsonsAlgorithm(), AlgorithmType.Prims => new PrimsAlgorithm(), AlgorithmType.Crawler => new CrawlerAlgorithm(), AlgorithmType.NoiseRecursive => new NoiseRecursiveGenerator(), _ => new RecursiveAlgorithm() }; } [Button("Find Scene References")] private void FindSceneReferences() { if (mazeRenderer == null) { mazeRenderer = GetComponentInChildren(); } if (mazeContainer == null) { mazeContainer = transform; } } private bool HasAtLeastOneFloor(MazeGrid[] floors) { return floors != null && floors.Length > 0; } } }