Organize custom scripts under Assets/Baba_yaga and merge Opsive folders to Assets root
This commit is contained in:
361
Assets/Baba_yaga/GameSetup/Maze/MazeManager.cs
Normal file
361
Assets/Baba_yaga/GameSetup/Maze/MazeManager.cs
Normal file
@@ -0,0 +1,361 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Baba_yaga.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Central controller for the Maze system.
|
||||
/// Manages algorithm selection, debug speed, and regeneration.
|
||||
/// </summary>
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
public class MazeManager : MonoBehaviour
|
||||
{
|
||||
[UnityEngine.Scripting.APIUpdating.MovedFrom(true, sourceNamespace: "Hallucinate.GameSetup.Maze", sourceAssembly: "Opsive.UltimateCharacterController")]
|
||||
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("Animation")]
|
||||
[SerializeField] private bool animateGeneration = true;
|
||||
|
||||
[BoxGroup("Animation")]
|
||||
[ShowIf(nameof(animateGeneration))]
|
||||
[PropertyRange(1, 500)]
|
||||
[LabelText("Generation Speed (Cells/Frame)")]
|
||||
[SerializeField] private int generationSpeed = 50;
|
||||
|
||||
public static int cellsProcessedThisFrame;
|
||||
|
||||
[BoxGroup("Animation")]
|
||||
[ShowIf(nameof(animateGeneration))]
|
||||
public MazeRenderer.CellAnimationType cellAnimationType = MazeRenderer.CellAnimationType.ScaleUp;
|
||||
|
||||
[BoxGroup("Progress")]
|
||||
[ProgressBar(0, 100)]
|
||||
[ShowInInspector, ReadOnly]
|
||||
private float completionPercentage;
|
||||
|
||||
[BoxGroup("References")]
|
||||
[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;
|
||||
|
||||
[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 HashSet<Vector3Int> _modifiedCells = new HashSet<Vector3Int>();
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Regenerate();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.R))
|
||||
{
|
||||
Regenerate();
|
||||
}
|
||||
}
|
||||
|
||||
[ContextMenu("Clear Maze")]
|
||||
[Button("Clear Maze", ButtonSizes.Large)]
|
||||
public void ClearMaze()
|
||||
{
|
||||
if (_generationCoroutine != null)
|
||||
{
|
||||
StopCoroutine(_generationCoroutine);
|
||||
_generationCoroutine = null;
|
||||
}
|
||||
|
||||
if (mazeRenderer != null)
|
||||
{
|
||||
mazeRenderer.Clear();
|
||||
}
|
||||
|
||||
completionPercentage = 0f;
|
||||
_modifiedCells?.Clear();
|
||||
}
|
||||
|
||||
[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];
|
||||
}
|
||||
|
||||
ClearMaze();
|
||||
|
||||
mazeRenderer.currentAnimationType = cellAnimationType;
|
||||
|
||||
if (animateGeneration)
|
||||
{
|
||||
_generationCoroutine = StartCoroutine(GenerateMazeRoutine());
|
||||
}
|
||||
else
|
||||
{
|
||||
GenerateMazeInstant();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateMazeInstant()
|
||||
{
|
||||
_modifiedCells.Clear();
|
||||
completionPercentage = 0f;
|
||||
|
||||
for (int i = 0; i < mazes.Length; i++)
|
||||
{
|
||||
mazes[i] = new MazeGrid(width, depth);
|
||||
mazes[i].Level = i;
|
||||
|
||||
CarveRooms(mazes[i]);
|
||||
|
||||
IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm);
|
||||
algorithmForFloor.Generate(mazes[i]);
|
||||
}
|
||||
|
||||
GenerateConnections();
|
||||
|
||||
for (int i = 0; i < mazes.Length; i++)
|
||||
{
|
||||
mazeRenderer.Initialize(mazes[i], mazeContainer, i == 0);
|
||||
}
|
||||
if (mazes.Length > 0) _grid = mazes[0];
|
||||
|
||||
completionPercentage = 100f;
|
||||
}
|
||||
|
||||
private IEnumerator GenerateMazeRoutine()
|
||||
{
|
||||
_modifiedCells.Clear();
|
||||
completionPercentage = 0f;
|
||||
|
||||
for (int i = 0; i < mazes.Length; i++)
|
||||
{
|
||||
mazes[i] = new MazeGrid(width, depth);
|
||||
mazes[i].Level = i;
|
||||
int floorIndex = i;
|
||||
|
||||
mazes[i].OnCellChanged += (x, z, type) =>
|
||||
{
|
||||
if (type != MazeCellType.Wall)
|
||||
{
|
||||
_modifiedCells.Add(new Vector3Int(x, floorIndex, z));
|
||||
int totalCells = width * depth * mazes.Length;
|
||||
// Approximate the progress to reach roughly 100% since algorithms don't visit all cells
|
||||
float fillRatio = 0.6f;
|
||||
completionPercentage = Mathf.Clamp((_modifiedCells.Count / ((float)totalCells * fillRatio)) * 100f, 0, 99f);
|
||||
}
|
||||
};
|
||||
|
||||
CarveRooms(mazes[i]);
|
||||
mazeRenderer.Initialize(mazes[i], mazeContainer, i == 0);
|
||||
|
||||
IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm);
|
||||
cellsProcessedThisFrame = 0;
|
||||
yield return StartCoroutine(algorithmForFloor.GenerateStepByStep(mazes[i], generationSpeed));
|
||||
}
|
||||
|
||||
GenerateConnections();
|
||||
|
||||
if (mazes.Length > 0) _grid = mazes[0];
|
||||
|
||||
completionPercentage = 100f;
|
||||
_generationCoroutine = null;
|
||||
}
|
||||
|
||||
private void GenerateConnections()
|
||||
{
|
||||
// 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<Vector2Int> possibleConnections = new List<Vector2Int>();
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
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<MazeRenderer>();
|
||||
}
|
||||
|
||||
if (mazeContainer == null)
|
||||
{
|
||||
mazeContainer = transform;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasAtLeastOneFloor(MazeGrid[] floors)
|
||||
{
|
||||
return floors != null && floors.Length > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user