This commit is contained in:
emDuy
2026-06-26 03:10:15 +07:00
38 changed files with 95310 additions and 69123 deletions

View File

@@ -16,13 +16,13 @@ namespace Hallucinate.GameSetup.Maze
for (int i = 0; i < HorizontalCrawlerCount; i++) CrawlH(grid, 0);
}
public IEnumerator GenerateStepByStep(MazeGrid grid, float interval)
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
{
for (int i = 0; i < VerticalCrawlerCount; i++) yield return CrawlV(grid, interval);
for (int i = 0; i < HorizontalCrawlerCount; i++) yield return CrawlH(grid, interval);
for (int i = 0; i < VerticalCrawlerCount; i++) yield return CrawlV(grid, cellsPerFrame);
for (int i = 0; i < HorizontalCrawlerCount; i++) yield return CrawlH(grid, cellsPerFrame);
}
private IEnumerator CrawlV(MazeGrid grid, float interval)
private IEnumerator CrawlV(MazeGrid grid, int cellsPerFrame)
{
bool done = false;
int x = Random.Range(MinBoundary, grid.Width - MinBoundary);
@@ -31,7 +31,12 @@ namespace Hallucinate.GameSetup.Maze
while (!done)
{
grid.SetCell(x, z, MazeCellType.Processing);
if (interval > 0) yield return new WaitForSeconds(interval);
MazeManager.cellsProcessedThisFrame++;
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
{
MazeManager.cellsProcessedThisFrame = 0;
yield return null;
}
grid.SetCell(x, z, MazeCellType.Corridor);
@@ -48,7 +53,7 @@ namespace Hallucinate.GameSetup.Maze
}
}
private IEnumerator CrawlH(MazeGrid grid, float interval)
private IEnumerator CrawlH(MazeGrid grid, int cellsPerFrame)
{
bool done = false;
int x = MinBoundary;
@@ -57,7 +62,12 @@ namespace Hallucinate.GameSetup.Maze
while (!done)
{
grid.SetCell(x, z, MazeCellType.Processing);
if (interval > 0) yield return new WaitForSeconds(interval);
MazeManager.cellsProcessedThisFrame++;
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
{
MazeManager.cellsProcessedThisFrame = 0;
yield return null;
}
grid.SetCell(x, z, MazeCellType.Corridor);

View File

@@ -14,6 +14,6 @@ namespace Hallucinate.GameSetup.Maze
/// <summary>
/// Generates the maze step-by-step for visualization.
/// </summary>
System.Collections.IEnumerator GenerateStepByStep(MazeGrid grid, float interval);
System.Collections.IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame);
}
}

View File

@@ -15,7 +15,8 @@ namespace Hallucinate.GameSetup.Maze
Start, // Entry point
End, // Exit point
StairsUp,
StairsDown
StairsDown,
Room
}
public enum PieceType
{

View File

@@ -37,18 +37,42 @@ namespace Hallucinate.GameSetup.Maze
[PropertyRange(5, 200)]
[SerializeField] private int depth = 30;
[BoxGroup("Debug")]
[SerializeField] private bool debugMode = true;
[BoxGroup("Animation")]
[SerializeField] private bool animateGeneration = true;
[BoxGroup("Debug")]
[ShowIf(nameof(debugMode))]
[PropertyRange(0.001f, 0.5f)]
[SerializeField] private float visualizationInterval = 0.05f;
[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;
@@ -74,6 +98,7 @@ namespace Hallucinate.GameSetup.Maze
private MazeGrid _grid;
private Coroutine _generationCoroutine;
private HashSet<Vector3Int> _modifiedCells = new HashSet<Vector3Int>();
private void Start()
{
@@ -88,6 +113,25 @@ namespace Hallucinate.GameSetup.Maze
}
}
[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()
@@ -109,24 +153,88 @@ namespace Hallucinate.GameSetup.Maze
mazes = new MazeGrid[1];
}
if (_generationCoroutine != null)
ClearMaze();
mazeRenderer.currentAnimationType = cellAnimationType;
if (animateGeneration)
{
StopCoroutine(_generationCoroutine);
_generationCoroutine = StartCoroutine(GenerateMazeRoutine());
}
else
{
GenerateMazeInstant();
}
}
mazeRenderer.Clear();
private void GenerateMazeInstant()
{
_modifiedCells.Clear();
completionPercentage = 0f;
// 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
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++)
{
@@ -167,35 +275,44 @@ namespace Hallucinate.GameSetup.Maze
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);
private void CarveRooms(MazeGrid grid)
{
if (!generateRooms) return;
IMazeAlgorithm algorithm = GetAlgorithm(selectedAlgorithm);
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);
if (debugMode)
int startX = Random.Range(1, width - w - 1);
int startZ = Random.Range(1, depth - d - 1);
for (int x = startX; x < startX + w; x++)
{
_generationCoroutine = StartCoroutine(algorithm.GenerateStepByStep(_grid, visualizationInterval));
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
{
algorithm.Generate(_grid);
int doorX = Random.value > 0.5f ? startX + w : startX - 1;
int doorZ = Random.Range(startZ, startZ + d);
grid.SetCell(doorX, doorZ, MazeCellType.Corridor);
}
_grid = new MazeGrid(width, depth);
mazeRenderer.Initialize(_grid, mazeContainer);
}
}
private void ShuffleList<T>(List<T> list)
{
for (int i = 0; i < list.Count; i++)

View File

@@ -22,6 +22,11 @@ namespace Hallucinate.GameSetup.Maze
public float Scale => visualProfile != null ? visualProfile.scale : 1f;
public enum CellAnimationType { None, ScaleUp, DropDown, SpinIn }
[HideInInspector]
public CellAnimationType currentAnimationType = CellAnimationType.ScaleUp;
[ShowInInspector]
[ReadOnly]
[BoxGroup("Runtime")]
@@ -134,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);
@@ -190,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:
@@ -238,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:
@@ -271,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)
@@ -294,10 +427,27 @@ namespace Hallucinate.GameSetup.Maze
{
if (target == null) yield break;
if (currentAnimationType == CellAnimationType.None) yield break;
float duration = Mathf.Max(0.01f, visualProfile.animationDuration);
float elapsed = 0;
Vector3 finalScale = target.localScale;
target.localScale = Vector3.one * 0.001f;
Vector3 finalPosition = target.localPosition;
Quaternion finalRotation = target.localRotation;
if (currentAnimationType == CellAnimationType.ScaleUp)
{
target.localScale = Vector3.one * 0.001f;
}
else if (currentAnimationType == CellAnimationType.DropDown)
{
target.localPosition = finalPosition + Vector3.up * 5f;
}
else if (currentAnimationType == CellAnimationType.SpinIn)
{
target.localScale = Vector3.one * 0.001f;
target.localRotation = finalRotation * Quaternion.Euler(0, 180, 0);
}
while (elapsed < duration)
{
@@ -305,16 +455,32 @@ namespace Hallucinate.GameSetup.Maze
elapsed += Time.deltaTime;
float t = Mathf.Clamp01(elapsed / duration);
float s = Mathf.Sin(t * Mathf.PI * 0.5f);
target.localScale = finalScale * Mathf.Max(0.001f, s);
if (currentAnimationType == CellAnimationType.ScaleUp)
{
float s = Mathf.Sin(t * Mathf.PI * 0.5f);
target.localScale = finalScale * Mathf.Max(0.001f, s);
}
else if (currentAnimationType == CellAnimationType.DropDown)
{
float s = Mathf.Sin(t * Mathf.PI * 0.5f);
target.localPosition = Vector3.Lerp(finalPosition + Vector3.up * 5f, finalPosition, s);
}
else if (currentAnimationType == CellAnimationType.SpinIn)
{
float s = Mathf.Sin(t * Mathf.PI * 0.5f);
target.localScale = finalScale * Mathf.Max(0.001f, s);
target.localRotation = Quaternion.Lerp(finalRotation * Quaternion.Euler(0, 180, 0), finalRotation, s);
}
yield return null;
}
if (target != null)
{
// Scale
target.localScale = finalScale;
target.localPosition = finalPosition;
target.localRotation = finalRotation;
}
}
}

View File

@@ -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;

View File

@@ -48,7 +48,7 @@ namespace Hallucinate.GameSetup.Maze
}
}
public IEnumerator GenerateStepByStep(MazeGrid grid, float interval)
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
{
InitializeNoise(grid);
@@ -75,7 +75,7 @@ namespace Hallucinate.GameSetup.Maze
}
}
yield return GenerateRecursiveStepByStep(grid, 5, 5, interval);
yield return GenerateRecursiveStepByStep(grid, 5, 5, cellsPerFrame);
}
private void InitializeNoise(MazeGrid grid)
@@ -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;
@@ -127,16 +127,22 @@ namespace Hallucinate.GameSetup.Maze
}
}
private IEnumerator GenerateRecursiveStepByStep(MazeGrid grid, int x, int z, float interval)
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;
if (grid.CountSquareNeighbours(x, z, MazeCellType.Corridor) >= DeadEndNeighbourThreshold) yield break;
grid.SetCell(x, z, MazeCellType.Processing);
if (interval > 0) yield return new WaitForSeconds(interval);
MazeManager.cellsProcessedThisFrame++;
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
{
MazeManager.cellsProcessedThisFrame = 0;
yield return null;
}
grid.SetCell(x, z, MazeCellType.Corridor);
@@ -145,7 +151,7 @@ namespace Hallucinate.GameSetup.Maze
foreach (var dir in shuffledDirs)
{
yield return GenerateRecursiveStepByStep(grid, x + dir.x, z + dir.z, interval);
yield return GenerateRecursiveStepByStep(grid, x + dir.x, z + dir.z, cellsPerFrame);
}
}
}

View File

@@ -39,12 +39,12 @@ namespace Hallucinate.GameSetup.Maze
}
}
public IEnumerator GenerateStepByStep(MazeGrid grid, float interval)
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
{
int x = InitialX;
int z = InitialZ;
grid.SetCell(x, z, MazeCellType.Corridor);
yield return new WaitForSeconds(interval);
yield return null;
List<MapLocation> walls = GetNeighbouringWalls(grid, x, z);
foreach(var w in walls) grid.SetCell(w.x, w.z, MazeCellType.Processing);
@@ -59,7 +59,12 @@ namespace Hallucinate.GameSetup.Maze
if (grid.CountSquareNeighbours(w.x, w.z, MazeCellType.Corridor) == TargetCorridorNeighbours)
{
grid.SetCell(w.x, w.z, MazeCellType.Corridor);
if (interval > 0) yield return new WaitForSeconds(interval);
MazeManager.cellsProcessedThisFrame++;
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
{
MazeManager.cellsProcessedThisFrame = 0;
yield return null;
}
foreach (var nw in GetNeighbouringWalls(grid, w.x, w.z))
{

View File

@@ -18,13 +18,14 @@ namespace Hallucinate.GameSetup.Maze
GenerateRecursive(grid, StartX, StartZ);
}
public IEnumerator GenerateStepByStep(MazeGrid grid, float interval)
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
{
yield return GenerateRecursiveStepByStep(grid, StartX, StartZ, interval);
yield return GenerateRecursiveStepByStep(grid, StartX, StartZ, cellsPerFrame);
}
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);
@@ -43,12 +44,18 @@ namespace Hallucinate.GameSetup.Maze
}
}
private IEnumerator GenerateRecursiveStepByStep(MazeGrid grid, int x, int z, float interval)
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);
if (interval > 0) yield return new WaitForSeconds(interval);
MazeManager.cellsProcessedThisFrame++;
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
{
MazeManager.cellsProcessedThisFrame = 0;
yield return null;
}
grid.SetCell(x, z, MazeCellType.Corridor);
@@ -61,7 +68,7 @@ namespace Hallucinate.GameSetup.Maze
int nz = z + dir.z;
if (grid.IsInBounds(nx, nz))
{
yield return GenerateRecursiveStepByStep(grid, nx, nz, interval);
yield return GenerateRecursiveStepByStep(grid, nx, nz, cellsPerFrame);
}
}
}

View File

@@ -32,17 +32,17 @@ namespace Hallucinate.GameSetup.Maze
}
}
public IEnumerator GenerateStepByStep(MazeGrid grid, float interval)
public IEnumerator GenerateStepByStep(MazeGrid grid, int cellsPerFrame)
{
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);
yield return null;
int safety = 0;
while (GetAvailableCells(grid) > 1 && safety < MaxIterationSafety)
{
yield return RandomWalk(grid, interval);
yield return RandomWalk(grid, cellsPerFrame);
safety++;
}
}
@@ -123,7 +123,7 @@ namespace Hallucinate.GameSetup.Maze
}
}
private IEnumerator RandomWalk(MazeGrid grid, float interval)
private IEnumerator RandomWalk(MazeGrid grid, int cellsPerFrame)
{
if (_notUsed.Count == 0) yield break;
@@ -139,7 +139,12 @@ namespace Hallucinate.GameSetup.Maze
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);
MazeManager.cellsProcessedThisFrame++;
if (MazeManager.cellsProcessedThisFrame >= cellsPerFrame)
{
MazeManager.cellsProcessedThisFrame = 0;
yield return null;
}
if (CountFinalizedNeighbours(grid, cx, cz) > 1) break;

View File

@@ -53,6 +53,46 @@ namespace Hallucinate.UI
DontDestroyOnLoad(gameObject);
}
private async void Start()
{
// Auto-connect if we bypass the UI and start directly in the Main Scene
if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "Main Scene")
{
Debug.Log("[BasicSpawner] Auto-starting Fusion in AutoHostOrClient mode for testing...");
if (_isStarting) return;
_isStarting = true;
try
{
await EnsureRunnerExists();
var sceneManager = gameObject.GetComponent<NetworkSceneManagerDefault>();
if (sceneManager == null) sceneManager = gameObject.AddComponent<NetworkSceneManagerDefault>();
var result = await _runner.StartGame(new StartGameArgs()
{
GameMode = GameMode.Shared,
SessionName = "QuickTestRoom", // Hardcoded session for instant testing
SceneManager = sceneManager,
Scene = SceneRef.FromIndex(UnityEngine.SceneManagement.SceneManager.GetActiveScene().buildIndex)
});
if (result.Ok)
{
Debug.Log("[BasicSpawner] Auto Connect SUCCESS!");
}
else
{
Debug.LogError($"[BasicSpawner] Auto Connect FAILED: {result.ShutdownReason}");
}
}
finally
{
_isStarting = false;
}
}
}
public PlayerProfile LocalPlayerProfile { get; private set; }
public void SetLocalPlayerProfile(PlayerProfile _profile)
{
@@ -91,8 +131,13 @@ namespace Hallucinate.UI
if (this == null) return; // BasicSpawner itself might be destroyed
Debug.Log("[BasicSpawner] Creating new NetworkRunner component.");
_runner = gameObject.AddComponent<NetworkRunner>();
_runner = gameObject.GetComponent<NetworkRunner>();
if (_runner == null)
{
Debug.Log("[BasicSpawner] Creating new NetworkRunner component.");
_runner = gameObject.AddComponent<NetworkRunner>();
}
_runner.ProvideInput = true;
_runner.AddCallbacks(this);
}
@@ -244,9 +289,13 @@ namespace Hallucinate.UI
public void OnPlayerJoined(NetworkRunner runner, PlayerRef player)
{
Debug.Log($"[BasicSpawner] PlayerJoined: {player.PlayerId}");
// In Shared Mode, there is no Server. Each client is responsible for spawning their own player.
if (player == runner.LocalPlayer)
{
SendLocalMetaData(player);
SpawnPlayer(runner, player);
}
}
@@ -405,19 +454,43 @@ namespace Hallucinate.UI
{
foreach (var player in runner.ActivePlayers)
{
Vector2 spawnPosition = (player == runner.LocalPlayer) ? new Vector2(-8, 0) : new Vector2(8, 0);
var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
// Give the client State Authority so they can move themselves
// networkPlayerObject.AssignStateAuthority(player);
_spawnedCharacters.Add(player, networkPlayerObject);
if (!_spawnedCharacters.ContainsKey(player))
{
SpawnPlayer(runner, player);
}
}
}
/*if (currentSceneName == "Main Scene")
{
UIManager.Instance?.OnGameStarted();
/
BNM098TYU78I98IU7Y6T57U8I9I8U7Y6T57U8I7Y6T5Y67U8IU7Y6T57U8IU7Y6E4XDER45ESZXSDCER45EDSXZSDCEFR45TTRFGHJUIYTRW
}*/
}
private void SpawnPlayer(NetworkRunner runner, PlayerRef player)
{
Debug.Log($"[BasicSpawner] Spawning Player {player.PlayerId} at {Time.time}");
Vector3 spawnPosition = (player == runner.LocalPlayer) ? new Vector3(-8, 2, 0) : new Vector3(8, 2, 0);
var networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player);
// In Shared Mode, runner.Spawn automatically grants State Authority to the caller.
// We just need to assign Input Authority.
networkPlayerObject.AssignInputAuthority(player);
_spawnedCharacters[player] = networkPlayerObject;
}
private void OnGUI()
{
if (_runner != null && _runner.IsRunning)
{
GUI.color = Color.green;
GUI.Label(new Rect(10, 10, 300, 30), $"[Network] Session: {_runner.SessionInfo?.Name}");
GUI.Label(new Rect(10, 30, 300, 30), $"[Network] Players in Room: {_runner.ActivePlayers.Count()}");
GUI.Label(new Rect(10, 50, 300, 30), $"[Network] Am I Server?: {_runner.IsServer}");
}
}
public void OnSceneLoadStart(NetworkRunner runner) { }
}