Files
BABA_YAGA/Assets/Scripts/GameSetup/Maze/MazeRenderer.cs

276 lines
9.5 KiB
C#
Raw Normal View History

2026-04-21 23:28:49 +07:00
using System.Collections;
using System.Collections.Generic;
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-05-01 20:50:08 +07:00
[SerializeField] private MazeVisualProfile visualProfile;
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;
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-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-05-01 20:50:08 +07:00
if (cell != null) Destroy(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-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
{
Destroy(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-05-01 20:50:08 +07:00
GameObject prefab = null;
Quaternion rotation = Quaternion.identity;
2026-04-21 23:28:49 +07:00
2026-05-01 20:50:08 +07:00
if (type == MazeCellType.Corridor || type == MazeCellType.Processing)
{
2026-05-06 01:47:40 +07:00
(prefab, rotation) = GetCorridorPrefabAndRotation(grid, x, z);
2026-05-01 20:50:08 +07:00
}
else
{
prefab = visualProfile.GetPrefab(type);
}
2026-05-01 02:48:43 +07:00
2026-05-01 20:50:08 +07:00
if (prefab == null) return;
2026-05-01 02:48:43 +07:00
2026-05-01 20:50:08 +07:00
float safeScale = Mathf.Max(0.001f, visualProfile.scale);
2026-05-01 23:19:01 +07:00
float modelScaleMultiplier = 0.25f;
2026-04-21 23:28:49 +07:00
2026-05-06 01:47:40 +07:00
float yOffset = grid.Level * floorHeight;
Vector3 localPos = new Vector3(x * safeScale, yOffset, z * safeScale);
2026-05-01 02:48:43 +07:00
2026-05-02 20:32:27 +07:00
GameObject newObj = Instantiate(prefab, _container);
newObj.transform.localPosition = localPos;
newObj.transform.localRotation = rotation;
2026-05-01 20:50:08 +07:00
newObj.transform.localScale = Vector3.one * safeScale * modelScaleMultiplier;
2026-05-06 01:47:40 +07:00
_spawnedCells[posKey] = newObj;
2026-05-01 02:48:43 +07:00
2026-05-01 20:50:08 +07:00
if (animate && visualProfile.animationDuration > 0)
{
StartCoroutine(AnimateCell(newObj.transform));
}
2026-05-01 02:48:43 +07:00
}
2026-05-01 20:50:08 +07:00
// =================================================================================
// THUẬT TOÁN BITMASK AUTO-TILING
// =================================================================================
2026-05-06 01:47:40 +07:00
private (GameObject, Quaternion) GetCorridorPrefabAndRotation(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-05-01 02:48:43 +07:00
2026-05-01 20:50:08 +07:00
switch (mask)
{
case 1:
prefabToSpawn = visualProfile.corridorDeadEnd;
yRotation = 180f;
break;
case 2:
prefabToSpawn = visualProfile.corridorDeadEnd;
yRotation = 270f;
break;
case 4:
prefabToSpawn = visualProfile.corridorDeadEnd;
yRotation = 0f;
break;
case 8:
prefabToSpawn = visualProfile.corridorDeadEnd;
yRotation = 90f;
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;
yRotation = 180f;
break;
case 7:
prefabToSpawn = visualProfile.corridorTJunction;
yRotation = 270f;
break;
case 14:
prefabToSpawn = visualProfile.corridorTJunction;
yRotation = 0f;
break;
case 13:
prefabToSpawn = visualProfile.corridorTJunction;
yRotation = 90f;
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;
return (prefabToSpawn, Quaternion.Euler(0, finalRotation, 0));
}
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
|| type == MazeCellType.StairUp
|| type == MazeCellType.StairDown;
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;
float duration = Mathf.Max(0.01f, visualProfile.animationDuration);
float elapsed = 0;
Vector3 finalScale = target.localScale;
target.localScale = Vector3.one * 0.001f;
while (elapsed < duration)
{
if (target == null) yield break;
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);
yield return null;
}
if (target != null)
{
target.localScale = finalScale;
}
2026-04-21 23:28:49 +07:00
}
}
}