2026-04-21 23:28:49 +07:00
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
2026-06-09 02:05:00 +07:00
|
|
|
using Sirenix.OdinInspector;
|
2026-04-21 23:28:49 +07:00
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace Hallucinate.GameSetup.Maze
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Responsible for the visual representation of the maze.
|
2026-04-22 00:29:09 +07:00
|
|
|
/// Handles spawning, pooling, and animations with safety checks.
|
2026-04-21 23:28:49 +07:00
|
|
|
/// </summary>
|
|
|
|
|
public class MazeRenderer : MonoBehaviour
|
|
|
|
|
{
|
2026-06-09 02:05:00 +07:00
|
|
|
[BoxGroup("Visuals")]
|
|
|
|
|
[Required]
|
|
|
|
|
[InlineEditor]
|
2026-05-01 20:50:08 +07:00
|
|
|
[SerializeField] private MazeVisualProfile visualProfile;
|
2026-06-09 02:05:00 +07:00
|
|
|
|
|
|
|
|
[BoxGroup("Visuals")]
|
|
|
|
|
[MinValue(0.001f)]
|
2026-05-06 01:47:40 +07:00
|
|
|
public float floorHeight = 3.5f;
|
2026-05-01 20:50:08 +07:00
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
public float Scale => visualProfile != null ? visualProfile.scale : 1f;
|
|
|
|
|
|
2026-06-26 00:21:29 +07:00
|
|
|
public enum CellAnimationType { None, ScaleUp, DropDown, SpinIn }
|
|
|
|
|
|
|
|
|
|
[HideInInspector]
|
|
|
|
|
public CellAnimationType currentAnimationType = CellAnimationType.ScaleUp;
|
|
|
|
|
|
2026-06-09 02:05:00 +07:00
|
|
|
[ShowInInspector]
|
|
|
|
|
[ReadOnly]
|
|
|
|
|
[BoxGroup("Runtime")]
|
|
|
|
|
private int SpawnedCellCount => _spawnedCells.Count;
|
|
|
|
|
|
|
|
|
|
[ShowInInspector]
|
|
|
|
|
[ReadOnly]
|
|
|
|
|
[BoxGroup("Runtime")]
|
|
|
|
|
private int RenderedFloorCount => _grids.Count;
|
|
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
private readonly Dictionary<Vector3Int, GameObject> _spawnedCells = new Dictionary<Vector3Int, GameObject>();
|
2026-05-01 20:50:08 +07:00
|
|
|
private Transform _container;
|
2026-05-06 01:47:40 +07:00
|
|
|
private List<MazeGrid> _grids = new List<MazeGrid>();
|
2026-05-01 20:50:08 +07:00
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
public void Initialize(MazeGrid grid, Transform container, bool clearExisting = true)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-06-09 02:05:00 +07:00
|
|
|
if (visualProfile == null)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError("MazeRenderer needs a MazeVisualProfile before it can render.", this);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (grid == null)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError("MazeRenderer received a null MazeGrid.", this);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
if (clearExisting)
|
|
|
|
|
{
|
|
|
|
|
Clear();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
_container = container;
|
2026-05-06 01:47:40 +07:00
|
|
|
if (!_grids.Contains(grid))
|
|
|
|
|
{
|
|
|
|
|
_grids.Add(grid);
|
|
|
|
|
grid.OnCellChanged += (x, z, type) => HandleCellChanged(grid, x, z, type);
|
|
|
|
|
}
|
2026-05-01 20:50:08 +07:00
|
|
|
|
|
|
|
|
// Initial render
|
|
|
|
|
for (int z = 0; z < grid.Depth; z++)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-05-01 20:50:08 +07:00
|
|
|
for (int x = 0; x < grid.Width; x++)
|
|
|
|
|
{
|
2026-05-06 01:47:40 +07:00
|
|
|
UpdateCellVisual(grid, x, z, grid.GetCell(x, z), false);
|
2026-05-01 20:50:08 +07:00
|
|
|
}
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
public void Clear()
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-05-01 20:50:08 +07:00
|
|
|
StopAllCoroutines();
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
foreach (var cell in _spawnedCells.Values)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-06-09 02:05:00 +07:00
|
|
|
if (cell == null) continue;
|
|
|
|
|
|
|
|
|
|
if (Application.isPlaying)
|
|
|
|
|
Destroy(cell);
|
|
|
|
|
else
|
|
|
|
|
DestroyImmediate(cell);
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
2026-05-01 20:50:08 +07:00
|
|
|
|
|
|
|
|
_spawnedCells.Clear();
|
2026-05-06 01:47:40 +07:00
|
|
|
|
|
|
|
|
foreach (var grid in _grids)
|
|
|
|
|
{
|
|
|
|
|
// Note: We can't easily unsubscribe because the lambda captures 'grid'.
|
|
|
|
|
// In a production environment, we should use a proper event handler method.
|
|
|
|
|
}
|
|
|
|
|
_grids.Clear();
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
|
2026-06-09 02:05:00 +07:00
|
|
|
[Button("Clear Spawned Maze")]
|
|
|
|
|
private void ClearFromInspector()
|
|
|
|
|
{
|
|
|
|
|
Clear();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
private void HandleCellChanged(MazeGrid grid, int x, int z, MazeCellType type)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-05-06 01:47:40 +07:00
|
|
|
UpdateCellVisual(grid, x, z, type, true);
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
UpdateNeighborVisual(grid, x + 1, z);
|
|
|
|
|
UpdateNeighborVisual(grid, x - 1, z);
|
|
|
|
|
UpdateNeighborVisual(grid, x, z + 1);
|
|
|
|
|
UpdateNeighborVisual(grid, x, z - 1);
|
2026-05-01 20:50:08 +07:00
|
|
|
}
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
private void UpdateNeighborVisual(MazeGrid grid, int x, int z)
|
2026-04-21 23:28:49 +07:00
|
|
|
{
|
2026-05-06 01:47:40 +07:00
|
|
|
if (grid != null && grid.IsInBounds(x, z))
|
2026-05-01 20:50:08 +07:00
|
|
|
{
|
2026-05-06 01:47:40 +07:00
|
|
|
if (IsPath(grid, x, z))
|
2026-05-01 20:50:08 +07:00
|
|
|
{
|
2026-05-06 01:47:40 +07:00
|
|
|
MazeCellType type = grid.GetCell(x, z);
|
|
|
|
|
UpdateCellVisual(grid, x, z, type, false);
|
2026-05-01 20:50:08 +07:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-01 02:48:43 +07:00
|
|
|
}
|
2026-05-01 20:50:08 +07:00
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
private void UpdateCellVisual(MazeGrid grid, int x, int z, MazeCellType type, bool animate)
|
2026-05-01 02:48:43 +07:00
|
|
|
{
|
2026-05-06 01:47:40 +07:00
|
|
|
Vector3Int posKey = new Vector3Int(x, grid.Level, z);
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
if (_spawnedCells.TryGetValue(posKey, out GameObject oldObj))
|
2026-05-01 20:50:08 +07:00
|
|
|
{
|
2026-05-08 13:01:04 +07:00
|
|
|
if (oldObj != null) DestroyImmediate(oldObj);
|
2026-05-06 01:47:40 +07:00
|
|
|
_spawnedCells.Remove(posKey);
|
2026-05-01 20:50:08 +07:00
|
|
|
}
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-06-26 02:04:50 +07:00
|
|
|
if (type == MazeCellType.Wall) return;
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-06-26 02:04:50 +07:00
|
|
|
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)
|
2026-05-01 20:50:08 +07:00
|
|
|
{
|
2026-06-26 02:04:50 +07:00
|
|
|
// 1. Spawn Node (Intersection, Corner, T, DeadEnd, or Stairs)
|
|
|
|
|
GameObject nodePrefab;
|
|
|
|
|
Quaternion nodeRot;
|
|
|
|
|
Vector3 nodeOffset = Vector3.zero;
|
|
|
|
|
|
2026-05-08 13:01:04 +07:00
|
|
|
if (type == MazeCellType.StairsUp || type == MazeCellType.StairsDown)
|
|
|
|
|
{
|
2026-06-26 02:04:50 +07:00
|
|
|
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;
|
|
|
|
|
}
|
2026-05-08 13:01:04 +07:00
|
|
|
}
|
2026-05-01 20:50:08 +07:00
|
|
|
}
|
2026-06-26 02:04:50 +07:00
|
|
|
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;
|
|
|
|
|
}
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-06-26 02:04:50 +07:00
|
|
|
// 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;
|
|
|
|
|
}
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-06-26 02:04:50 +07:00
|
|
|
// 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);
|
2026-04-21 23:28:49 +07:00
|
|
|
|
2026-06-26 02:04:50 +07:00
|
|
|
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);
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-06-26 02:04:50 +07:00
|
|
|
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);
|
2026-05-02 20:32:27 +07:00
|
|
|
|
2026-06-26 02:04:50 +07:00
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
// Non-corridor logic (Start, End, etc)
|
|
|
|
|
GameObject prefab = visualProfile.GetPrefab(type);
|
|
|
|
|
if (prefab != null)
|
|
|
|
|
{
|
|
|
|
|
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 (!spawnedAnything)
|
|
|
|
|
{
|
|
|
|
|
DestroyImmediate(cellParent);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_spawnedCells[posKey] = cellParent;
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
if (animate && visualProfile.animationDuration > 0)
|
|
|
|
|
{
|
2026-06-26 02:04:50 +07:00
|
|
|
StartCoroutine(AnimateCell(cellParent.transform));
|
2026-05-01 20:50:08 +07:00
|
|
|
}
|
2026-05-01 02:48:43 +07:00
|
|
|
}
|
|
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
// =================================================================================
|
|
|
|
|
// THUẬT TOÁN BITMASK AUTO-TILING
|
|
|
|
|
// =================================================================================
|
2026-06-26 02:04:50 +07:00
|
|
|
private (GameObject, Quaternion, Vector3) GetNodePrefabAndRotation(MazeGrid grid, int x, int z)
|
2026-05-01 20:50:08 +07:00
|
|
|
{
|
2026-05-06 01:47:40 +07:00
|
|
|
bool top = IsPath(grid, x, z + 1);
|
|
|
|
|
bool right = IsPath(grid, x + 1, z);
|
|
|
|
|
bool bottom = IsPath(grid, x, z - 1);
|
|
|
|
|
bool left = IsPath(grid, x - 1, z);
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
int mask = 0;
|
|
|
|
|
if (top) mask += 1;
|
|
|
|
|
if (right) mask += 2;
|
|
|
|
|
if (bottom) mask += 4;
|
|
|
|
|
if (left) mask += 8;
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
GameObject prefabToSpawn = null;
|
|
|
|
|
float yRotation = 0f;
|
2026-06-26 02:04:50 +07:00
|
|
|
Vector3 offset = Vector3.zero;
|
|
|
|
|
|
|
|
|
|
// Push dead ends to the boundary where edges end
|
|
|
|
|
float endOffset = visualProfile.deadEndShift;
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
switch (mask)
|
|
|
|
|
{
|
|
|
|
|
case 1:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorDeadEnd;
|
2026-06-26 02:04:50 +07:00
|
|
|
yRotation = 0f;
|
|
|
|
|
offset = new Vector3(0, 0, endOffset);
|
2026-05-01 20:50:08 +07:00
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorDeadEnd;
|
2026-06-26 02:04:50 +07:00
|
|
|
yRotation = 90f;
|
|
|
|
|
offset = new Vector3(endOffset, 0, 0);
|
2026-05-01 20:50:08 +07:00
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorDeadEnd;
|
2026-06-26 02:04:50 +07:00
|
|
|
yRotation = 180f;
|
|
|
|
|
offset = new Vector3(0, 0, -endOffset);
|
2026-05-01 20:50:08 +07:00
|
|
|
break;
|
|
|
|
|
case 8:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorDeadEnd;
|
2026-06-26 02:04:50 +07:00
|
|
|
yRotation = 270f;
|
|
|
|
|
offset = new Vector3(-endOffset, 0, 0);
|
2026-05-01 20:50:08 +07:00
|
|
|
break;
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
case 5:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorStraight;
|
|
|
|
|
yRotation = 0f;
|
|
|
|
|
break;
|
|
|
|
|
case 10:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorStraight;
|
|
|
|
|
yRotation = 90f;
|
|
|
|
|
break;
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
case 3:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorCorner;
|
|
|
|
|
yRotation = 0f;
|
|
|
|
|
break;
|
|
|
|
|
case 6:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorCorner;
|
|
|
|
|
yRotation = 90f;
|
|
|
|
|
break;
|
|
|
|
|
case 12:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorCorner;
|
|
|
|
|
yRotation = 180f;
|
|
|
|
|
break;
|
|
|
|
|
case 9:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorCorner;
|
|
|
|
|
yRotation = 270f;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 11:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorTJunction;
|
2026-06-26 02:04:50 +07:00
|
|
|
yRotation = 0f;
|
2026-05-01 20:50:08 +07:00
|
|
|
break;
|
|
|
|
|
case 7:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorTJunction;
|
2026-06-26 02:04:50 +07:00
|
|
|
yRotation = 90f;
|
2026-05-01 20:50:08 +07:00
|
|
|
break;
|
|
|
|
|
case 14:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorTJunction;
|
2026-06-26 02:04:50 +07:00
|
|
|
yRotation = 180f;
|
2026-05-01 20:50:08 +07:00
|
|
|
break;
|
|
|
|
|
case 13:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorTJunction;
|
2026-06-26 02:04:50 +07:00
|
|
|
yRotation = 270f;
|
2026-05-01 20:50:08 +07:00
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 15:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorCross;
|
|
|
|
|
yRotation = 0f;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
prefabToSpawn = visualProfile.corridorDeadEnd;
|
|
|
|
|
yRotation = 0f;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2026-05-01 02:48:43 +07:00
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
float finalRotation = yRotation;
|
|
|
|
|
if (prefabToSpawn == visualProfile.corridorTJunction) finalRotation += visualProfile.tJunctionOffset;
|
|
|
|
|
if (prefabToSpawn == visualProfile.corridorDeadEnd) finalRotation += visualProfile.deadEndOffset;
|
|
|
|
|
if (prefabToSpawn == visualProfile.corridorCorner) finalRotation += visualProfile.cornerOffset;
|
|
|
|
|
|
|
|
|
|
if (prefabToSpawn == null) prefabToSpawn = visualProfile.corridorPrefab;
|
|
|
|
|
|
2026-06-26 02:04:50 +07:00
|
|
|
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;
|
|
|
|
|
}
|
2026-05-01 20:50:08 +07:00
|
|
|
}
|
|
|
|
|
|
2026-05-06 01:47:40 +07:00
|
|
|
private bool IsPath(MazeGrid grid, int x, int z)
|
2026-05-01 20:50:08 +07:00
|
|
|
{
|
2026-05-06 01:47:40 +07:00
|
|
|
if (grid == null || !grid.IsInBounds(x, z)) return false;
|
|
|
|
|
MazeCellType type = grid.GetCell(x, z);
|
2026-05-01 20:50:08 +07:00
|
|
|
return type == MazeCellType.Corridor
|
|
|
|
|
|| type == MazeCellType.Processing
|
|
|
|
|
|| type == MazeCellType.Start
|
|
|
|
|
|| type == MazeCellType.End
|
2026-05-06 01:47:40 +07:00
|
|
|
|| type == MazeCellType.Path
|
2026-05-08 09:50:34 +07:00
|
|
|
|| type == MazeCellType.StairsUp
|
|
|
|
|
|| type == MazeCellType.StairsDown;
|
2026-05-01 02:48:43 +07:00
|
|
|
}
|
|
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
// =================================================================================
|
|
|
|
|
// ANIMATION
|
|
|
|
|
// =================================================================================
|
|
|
|
|
private IEnumerator AnimateCell(Transform target)
|
2026-05-01 02:48:43 +07:00
|
|
|
{
|
2026-05-01 20:50:08 +07:00
|
|
|
if (target == null) yield break;
|
|
|
|
|
|
2026-06-26 00:21:29 +07:00
|
|
|
if (currentAnimationType == CellAnimationType.None) yield break;
|
|
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
float duration = Mathf.Max(0.01f, visualProfile.animationDuration);
|
|
|
|
|
float elapsed = 0;
|
|
|
|
|
Vector3 finalScale = target.localScale;
|
2026-06-26 00:21:29 +07:00
|
|
|
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);
|
|
|
|
|
}
|
2026-05-01 20:50:08 +07:00
|
|
|
|
|
|
|
|
while (elapsed < duration)
|
|
|
|
|
{
|
|
|
|
|
if (target == null) yield break;
|
|
|
|
|
|
|
|
|
|
elapsed += Time.deltaTime;
|
|
|
|
|
float t = Mathf.Clamp01(elapsed / duration);
|
|
|
|
|
|
2026-06-26 00:21:29 +07:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 20:50:08 +07:00
|
|
|
yield return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (target != null)
|
|
|
|
|
{
|
|
|
|
|
target.localScale = finalScale;
|
2026-06-26 00:21:29 +07:00
|
|
|
target.localPosition = finalPosition;
|
|
|
|
|
target.localRotation = finalRotation;
|
2026-05-01 20:50:08 +07:00
|
|
|
}
|
2026-04-21 23:28:49 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|