Update
This commit is contained in:
@@ -7,17 +7,19 @@ namespace Hallucinate.GameSetup.Maze
|
||||
{
|
||||
private const int CrawlChance = 50;
|
||||
private const int MinBoundary = 1;
|
||||
private const int VerticalCrawlerCount = 3;
|
||||
private const int HorizontalCrawlerCount = 3;
|
||||
|
||||
public void Generate(MazeGrid grid)
|
||||
{
|
||||
CrawlV(grid, 0);
|
||||
CrawlH(grid, 0);
|
||||
for (int i = 0; i < VerticalCrawlerCount; i++) CrawlV(grid, 0);
|
||||
for (int i = 0; i < HorizontalCrawlerCount; i++) CrawlH(grid, 0);
|
||||
}
|
||||
|
||||
public IEnumerator GenerateStepByStep(MazeGrid grid, float interval)
|
||||
{
|
||||
yield return CrawlV(grid, interval);
|
||||
yield return CrawlH(grid, interval);
|
||||
for (int i = 0; i < VerticalCrawlerCount; i++) yield return CrawlV(grid, interval);
|
||||
for (int i = 0; i < HorizontalCrawlerCount; i++) yield return CrawlH(grid, interval);
|
||||
}
|
||||
|
||||
private IEnumerator CrawlV(MazeGrid grid, float interval)
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Hallucinate.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for the visual representation of the maze.
|
||||
/// Handles spawning, pooling, and animations.
|
||||
/// Handles spawning, pooling, and animations with safety checks.
|
||||
/// </summary>
|
||||
public class MazeRenderer : MonoBehaviour
|
||||
{
|
||||
@@ -32,7 +32,6 @@ namespace Hallucinate.GameSetup.Maze
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
// IMPORTANT: Stop all running animations to prevent accessing destroyed objects
|
||||
StopAllCoroutines();
|
||||
|
||||
foreach (var cell in _spawnedCells.Values)
|
||||
@@ -51,7 +50,6 @@ namespace Hallucinate.GameSetup.Maze
|
||||
{
|
||||
Vector2Int pos = new Vector2Int(x, z);
|
||||
|
||||
// Remove old visual if exists
|
||||
if (_spawnedCells.TryGetValue(pos, out GameObject oldObj))
|
||||
{
|
||||
Destroy(oldObj);
|
||||
@@ -61,12 +59,15 @@ namespace Hallucinate.GameSetup.Maze
|
||||
GameObject prefab = visualProfile.GetPrefab(type);
|
||||
if (prefab == null) return;
|
||||
|
||||
Vector3 worldPos = new Vector3(x * visualProfile.scale, 0, z * visualProfile.scale);
|
||||
// Ensure scale is always positive to avoid BoxCollider issues
|
||||
float safeScale = Mathf.Max(0.001f, visualProfile.scale);
|
||||
Vector3 worldPos = new Vector3(x * safeScale, 0, z * safeScale);
|
||||
|
||||
GameObject newObj = Instantiate(prefab, worldPos, Quaternion.identity, _container);
|
||||
newObj.transform.localScale = Vector3.one * visualProfile.scale;
|
||||
newObj.transform.localScale = Vector3.one * safeScale;
|
||||
_spawnedCells[pos] = newObj;
|
||||
|
||||
if (animate)
|
||||
if (animate && visualProfile.animationDuration > 0)
|
||||
{
|
||||
StartCoroutine(AnimateCell(newObj.transform));
|
||||
}
|
||||
@@ -76,20 +77,21 @@ namespace Hallucinate.GameSetup.Maze
|
||||
{
|
||||
if (target == null) yield break;
|
||||
|
||||
float duration = visualProfile.animationDuration;
|
||||
float duration = Mathf.Max(0.01f, visualProfile.animationDuration);
|
||||
float elapsed = 0;
|
||||
Vector3 finalScale = target.localScale;
|
||||
target.localScale = Vector3.zero;
|
||||
target.localScale = Vector3.one * 0.001f; // Use tiny positive instead of zero
|
||||
|
||||
while (elapsed < duration)
|
||||
{
|
||||
// Extra safety check in case the object is destroyed mid-animation
|
||||
if (target == null) yield break;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
float t = elapsed / duration;
|
||||
float t = Mathf.Clamp01(elapsed / duration);
|
||||
float s = Mathf.Sin(t * Mathf.PI * 0.5f);
|
||||
target.localScale = finalScale * s;
|
||||
|
||||
// Ensure s is never negative
|
||||
target.localScale = finalScale * Mathf.Max(0.001f, s);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,11 @@ namespace Hallucinate.GameSetup.Maze
|
||||
if (grid.CountSquareNeighbours(w.x, w.z, MazeCellType.Corridor) == TargetCorridorNeighbours)
|
||||
{
|
||||
grid.SetCell(w.x, w.z, MazeCellType.Corridor);
|
||||
walls.AddRange(GetNeighbouringWalls(grid, w.x, w.z));
|
||||
|
||||
foreach (var nw in GetNeighbouringWalls(grid, w.x, w.z))
|
||||
{
|
||||
if (!walls.Contains(nw)) walls.Add(nw);
|
||||
}
|
||||
}
|
||||
iterations++;
|
||||
}
|
||||
@@ -43,7 +47,6 @@ namespace Hallucinate.GameSetup.Maze
|
||||
yield return new WaitForSeconds(interval);
|
||||
|
||||
List<MapLocation> walls = GetNeighbouringWalls(grid, x, z);
|
||||
|
||||
foreach(var w in walls) grid.SetCell(w.x, w.z, MazeCellType.Processing);
|
||||
|
||||
int iterations = 0;
|
||||
@@ -58,8 +61,7 @@ namespace Hallucinate.GameSetup.Maze
|
||||
grid.SetCell(w.x, w.z, MazeCellType.Corridor);
|
||||
if (interval > 0) yield return new WaitForSeconds(interval);
|
||||
|
||||
var newWalls = GetNeighbouringWalls(grid, w.x, w.z);
|
||||
foreach(var nw in newWalls)
|
||||
foreach (var nw in GetNeighbouringWalls(grid, w.x, w.z))
|
||||
{
|
||||
if (grid.GetCell(nw.x, nw.z) == MazeCellType.Wall)
|
||||
{
|
||||
@@ -70,8 +72,8 @@ namespace Hallucinate.GameSetup.Maze
|
||||
}
|
||||
else
|
||||
{
|
||||
if(grid.GetCell(w.x, w.z) == MazeCellType.Processing)
|
||||
grid.SetCell(w.x, w.z, MazeCellType.Wall);
|
||||
// If it's no longer a candidate, turn it back to Wall
|
||||
grid.SetCell(w.x, w.z, MazeCellType.Wall);
|
||||
}
|
||||
iterations++;
|
||||
}
|
||||
@@ -83,10 +85,19 @@ namespace Hallucinate.GameSetup.Maze
|
||||
foreach (var dir in MapLocation.Directions)
|
||||
{
|
||||
int nx = x + dir.x;
|
||||
int nz = z + dir.z;
|
||||
if (grid.IsInBounds(nx, nz) && grid.GetCell(nx, nz) == MazeCellType.Wall)
|
||||
int nz = z + dir.z;
|
||||
|
||||
// Correction
|
||||
nx = x + dir.x;
|
||||
nz = z + dir.z;
|
||||
|
||||
if (grid.IsInBounds(nx, nz))
|
||||
{
|
||||
neighbours.Add(new MapLocation(nx, nz));
|
||||
MazeCellType type = grid.GetCell(nx, nz);
|
||||
if (type == MazeCellType.Wall || type == MazeCellType.Processing)
|
||||
{
|
||||
neighbours.Add(new MapLocation(nx, nz));
|
||||
}
|
||||
}
|
||||
}
|
||||
return neighbours;
|
||||
|
||||
@@ -4,115 +4,173 @@ using UnityEngine;
|
||||
|
||||
namespace Hallucinate.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Wilson's Algorithm implementation based on the original provided logic.
|
||||
/// Ensures paths are sparse and correctly finalized using specific neighbor constraints.
|
||||
/// </summary>
|
||||
public class WilsonsAlgorithm : IMazeAlgorithm
|
||||
{
|
||||
private const int MinBoundary = 2;
|
||||
private const int MaxIterationSafety = 5000;
|
||||
private const int TargetPathNeighbours = 1;
|
||||
private const int MaxIterationSafety = 5000;
|
||||
private const int MaxWalkSteps = 5000;
|
||||
|
||||
private readonly List<MapLocation> _directions = new List<MapLocation>()
|
||||
{
|
||||
new MapLocation(1, 0), new MapLocation(0, 1), new MapLocation(-1, 0), new MapLocation(0, -1)
|
||||
};
|
||||
private readonly List<MapLocation> _directions = MapLocation.Directions;
|
||||
private List<MapLocation> _notUsed = new List<MapLocation>();
|
||||
|
||||
public void Generate(MazeGrid grid)
|
||||
{
|
||||
int x = Random.Range(MinBoundary, grid.Width - MinBoundary);
|
||||
int z = Random.Range(MinBoundary, grid.Depth - MinBoundary);
|
||||
// 1. Create a starting finalized cell (Type.Corridor represents state 2)
|
||||
int x = Random.Range(MinBoundary, grid.Width - 1);
|
||||
int z = Random.Range(MinBoundary, grid.Depth - 1);
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
|
||||
while (GetAvailableCells(grid).Count > 1)
|
||||
int safety = 0;
|
||||
while (GetAvailableCells(grid) > 1 && safety < MaxIterationSafety)
|
||||
{
|
||||
PerformRandomWalk(grid, null, 0);
|
||||
RandomWalkSync(grid);
|
||||
safety++;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator GenerateStepByStep(MazeGrid grid, float interval)
|
||||
{
|
||||
int x = Random.Range(MinBoundary, grid.Width - MinBoundary);
|
||||
int z = Random.Range(MinBoundary, grid.Depth - MinBoundary);
|
||||
int x = Random.Range(MinBoundary, grid.Width - 1);
|
||||
int z = Random.Range(MinBoundary, grid.Depth - 1);
|
||||
grid.SetCell(x, z, MazeCellType.Corridor);
|
||||
yield return new WaitForSeconds(interval);
|
||||
|
||||
while (true)
|
||||
int safety = 0;
|
||||
while (GetAvailableCells(grid) > 1 && safety < MaxIterationSafety)
|
||||
{
|
||||
var available = GetAvailableCells(grid);
|
||||
if (available.Count <= 1) break;
|
||||
|
||||
yield return PerformRandomWalk(grid, available, interval);
|
||||
yield return RandomWalk(grid, interval);
|
||||
safety++;
|
||||
}
|
||||
}
|
||||
|
||||
private List<MapLocation> GetAvailableCells(MazeGrid grid)
|
||||
{
|
||||
List<MapLocation> available = new List<MapLocation>();
|
||||
for (int z = 1; z < grid.Depth - 1; z++)
|
||||
{
|
||||
for (int x = 1; x < grid.Width - 1; x++)
|
||||
{
|
||||
if (CountPathNeighbours(grid, x, z) == 0)
|
||||
available.Add(new MapLocation(x, z));
|
||||
}
|
||||
}
|
||||
return available;
|
||||
}
|
||||
|
||||
private int CountPathNeighbours(MazeGrid grid, int x, int z)
|
||||
/// <summary>
|
||||
/// Counts neighbors that are already part of the finalized maze (State 2 / Corridor).
|
||||
/// </summary>
|
||||
private int CountFinalizedNeighbours(MazeGrid grid, int x, int z)
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var d in _directions)
|
||||
{
|
||||
if (grid.GetCell(x + d.x, z + d.z) == MazeCellType.Corridor) count++;
|
||||
if (grid.GetCell(x + d.x, z + d.z) == MazeCellType.Corridor)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private IEnumerator PerformRandomWalk(MazeGrid grid, List<MapLocation> available, float interval)
|
||||
private int GetAvailableCells(MazeGrid grid)
|
||||
{
|
||||
_notUsed.Clear();
|
||||
for (int z = 1; z < grid.Depth - 1; z++)
|
||||
{
|
||||
for (int x = 1; x < grid.Width - 1; x++)
|
||||
{
|
||||
if (CountFinalizedNeighbours(grid, x, z) == 0)
|
||||
{
|
||||
_notUsed.Add(new MapLocation(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
return _notUsed.Count;
|
||||
}
|
||||
|
||||
private void RandomWalkSync(MazeGrid grid)
|
||||
{
|
||||
if (_notUsed.Count == 0) return;
|
||||
|
||||
List<MapLocation> inWalk = new List<MapLocation>();
|
||||
int rStart = Random.Range(0, available.Count);
|
||||
int cx = available[rStart].x;
|
||||
int cz = available[rStart].z;
|
||||
int rStartIndex = Random.Range(0, _notUsed.Count);
|
||||
int cx = _notUsed[rStartIndex].x;
|
||||
int cz = _notUsed[rStartIndex].z;
|
||||
|
||||
inWalk.Add(new MapLocation(cx, cz));
|
||||
bool pathFound = false;
|
||||
|
||||
int safety = 0;
|
||||
while (grid.IsInBounds(cx, cz) && !pathFound && safety < MaxIterationSafety)
|
||||
int loop = 0;
|
||||
bool validPath = false;
|
||||
while (cx > 0 && cx < grid.Width - 1 && cz > 0 && cz < grid.Depth - 1 && loop < MaxWalkSteps && !validPath)
|
||||
{
|
||||
grid.SetCell(cx, cz, MazeCellType.Processing);
|
||||
if (interval > 0) yield return new WaitForSeconds(interval);
|
||||
// Mark as temporary walk (State 0 / Processing)
|
||||
// Note: We don't set grid cell here in sync mode to avoid triggering events unnecessarily
|
||||
// but we keep track of neighbors.
|
||||
|
||||
if (CountFinalizedNeighbours(grid, cx, cz) > 1) break;
|
||||
|
||||
if (CountPathNeighbours(grid, cx, cz) > 1) break;
|
||||
MapLocation rd = _directions[Random.Range(0, _directions.Count)];
|
||||
int nx = cx + rd.x;
|
||||
int nz = cz + rd.z;
|
||||
|
||||
int rd = Random.Range(0, _directions.Count);
|
||||
int nx = cx + _directions[rd].x;
|
||||
int nz = cz + _directions[rd].z;
|
||||
|
||||
if (grid.CountSquareNeighbours(nx, nz, MazeCellType.Processing) < 2)
|
||||
// User's original constraint: CountSquareNeighbours (nx, nz) < 2
|
||||
if (CountFinalizedNeighbours(grid, nx, nz) < 2)
|
||||
{
|
||||
cx = nx;
|
||||
cz = nz;
|
||||
inWalk.Add(new MapLocation(cx, cz));
|
||||
}
|
||||
|
||||
pathFound = CountPathNeighbours(grid, cx, cz) == TargetPathNeighbours;
|
||||
safety++;
|
||||
validPath = CountFinalizedNeighbours(grid, cx, cz) == 1;
|
||||
loop++;
|
||||
}
|
||||
|
||||
if (pathFound)
|
||||
if (validPath)
|
||||
{
|
||||
foreach (var m in inWalk)
|
||||
{
|
||||
foreach (MapLocation m in inWalk)
|
||||
grid.SetCell(m.x, m.z, MazeCellType.Corridor);
|
||||
if (interval > 0) yield return new WaitForSeconds(interval * 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator RandomWalk(MazeGrid grid, float interval)
|
||||
{
|
||||
if (_notUsed.Count == 0) yield break;
|
||||
|
||||
List<MapLocation> inWalk = new List<MapLocation>();
|
||||
int rStartIndex = Random.Range(0, _notUsed.Count);
|
||||
int cx = _notUsed[rStartIndex].x;
|
||||
int cz = _notUsed[rStartIndex].z;
|
||||
|
||||
inWalk.Add(new MapLocation(cx, cz));
|
||||
|
||||
int loop = 0;
|
||||
bool validPath = false;
|
||||
while (cx > 0 && cx < grid.Width - 1 && cz > 0 && cz < grid.Depth - 1 && loop < MaxWalkSteps && !validPath)
|
||||
{
|
||||
grid.SetCell(cx, cz, MazeCellType.Processing); // State 0
|
||||
if (interval > 0) yield return new WaitForSeconds(interval);
|
||||
|
||||
if (CountFinalizedNeighbours(grid, cx, cz) > 1) break;
|
||||
|
||||
MapLocation rd = _directions[Random.Range(0, _directions.Count)];
|
||||
int nx = cx + rd.x;
|
||||
int nz = cz + rd.z;
|
||||
|
||||
if (CountFinalizedNeighbours(grid, nx, nz) < 2)
|
||||
{
|
||||
cx = nx;
|
||||
cz = nz;
|
||||
inWalk.Add(new MapLocation(cx, cz));
|
||||
}
|
||||
|
||||
validPath = CountFinalizedNeighbours(grid, cx, cz) == 1;
|
||||
loop++;
|
||||
}
|
||||
|
||||
if (validPath)
|
||||
{
|
||||
foreach (MapLocation m in inWalk)
|
||||
{
|
||||
grid.SetCell(m.x, m.z, MazeCellType.Corridor); // State 2
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var m in inWalk)
|
||||
grid.SetCell(m.x, m.z, MazeCellType.Wall);
|
||||
foreach (MapLocation m in inWalk)
|
||||
grid.SetCell(m.x, m.z, MazeCellType.Wall); // State 1
|
||||
}
|
||||
inWalk.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user