using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Hallucinate.GameSetup.Maze { /// /// Responsible for the visual representation of the maze. /// Handles spawning, pooling, and animations with safety checks. /// // public class MazeRenderer : MonoBehaviour // { // [SerializeField] private MazeVisualProfile visualProfile; // // private readonly Dictionary _spawnedCells = new Dictionary(); // private Transform _container; // // public void Initialize(MazeGrid grid, Transform container) // { // _container = container; // grid.OnCellChanged += HandleCellChanged; // // // Initial render // for (int z = 0; z < grid.Depth; z++) // { // for (int x = 0; x < grid.Width; x++) // { // UpdateCellVisual(x, z, grid.GetCell(x, z), false); // } // } // } // // public void Clear() // { // StopAllCoroutines(); // // foreach (var cell in _spawnedCells.Values) // { // if (cell != null) Destroy(cell); // } // _spawnedCells.Clear(); // } // // private void HandleCellChanged(int x, int z, MazeCellType type) // { // UpdateCellVisual(x, z, type, true); // } // // private void UpdateCellVisual(int x, int z, MazeCellType type, bool animate) // { // Vector2Int pos = new Vector2Int(x, z); // // if (_spawnedCells.TryGetValue(pos, out GameObject oldObj)) // { // Destroy(oldObj); // _spawnedCells.Remove(pos); // } // // GameObject prefab = visualProfile.GetPrefab(type); // if (prefab == null) return; // // // 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 * safeScale; // _spawnedCells[pos] = newObj; // // if (animate && visualProfile.animationDuration > 0) // { // StartCoroutine(AnimateCell(newObj.transform)); // } // } // // private IEnumerator AnimateCell(Transform target) // { // 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; // Use tiny positive instead of zero // // 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); // // // Ensure s is never negative // target.localScale = finalScale * Mathf.Max(0.001f, s); // yield return null; // } // // if (target != null) // { // target.localScale = finalScale; // } // } // } public class MazeRenderer : MonoBehaviour { [SerializeField] private MazeVisualProfile visualProfile; private readonly Dictionary _spawnedCells = new Dictionary(); private Transform _container; private MazeGrid _currentGrid; // Thêm biến để lưu trữ grid phục vụ việc kiểm tra hàng xóm public void Initialize(MazeGrid grid, Transform container) { _currentGrid = grid; _container = container; grid.OnCellChanged += HandleCellChanged; // Initial render for (int z = 0; z < grid.Depth; z++) { for (int x = 0; x < grid.Width; x++) { UpdateCellVisual(x, z, grid.GetCell(x, z), false); } } } public void Clear() { StopAllCoroutines(); foreach (var cell in _spawnedCells.Values) { if (cell != null) Destroy(cell); } _spawnedCells.Clear(); _currentGrid = null; } private void HandleCellChanged(int x, int z, MazeCellType type) { // 1. Cập nhật ô vừa thay đổi (Có chạy Animation) UpdateCellVisual(x, z, type, true); // 2. Cập nhật 4 ô xung quanh để chúng tự nối ống với ô mới này (KHÔNG chạy Animation để tránh giật hình) UpdateNeighborVisual(x + 1, z); UpdateNeighborVisual(x - 1, z); UpdateNeighborVisual(x, z + 1); UpdateNeighborVisual(x, z - 1); } private void UpdateNeighborVisual(int x, int z) { if (_currentGrid != null && _currentGrid.IsInBounds(x, z)) { // Chỉ cập nhật hình ảnh nếu hàng xóm là đường đi (tránh việc gọi vẽ lại tường gây tốn tài nguyên) if (IsPath(x, z)) { MazeCellType type = _currentGrid.GetCell(x, z); UpdateCellVisual(x, z, type, false); } } } private void UpdateCellVisual(int x, int z, MazeCellType type, bool animate) { Vector2Int pos = new Vector2Int(x, z); if (_spawnedCells.TryGetValue(pos, out GameObject oldObj)) { Destroy(oldObj); _spawnedCells.Remove(pos); } GameObject prefab = null; Quaternion rotation = Quaternion.identity; if (type == MazeCellType.Corridor || type == MazeCellType.Processing) { (prefab, rotation) = GetCorridorPrefabAndRotation(x, z); } else { prefab = visualProfile.GetPrefab(type); } if (prefab == null) return; float safeScale = Mathf.Max(0.001f, visualProfile.scale); // --- SỬA ĐỔI TẠI ĐÂY: Thu nhỏ prefab thành 0.5 --- float modelScaleMultiplier = 0.5f; // Giữ nguyên safeScale cho worldPos để các ô vẫn cách đều nhau Vector3 worldPos = new Vector3(x * safeScale, 0, z * safeScale); GameObject newObj = Instantiate(prefab, worldPos, rotation, _container); // Nhân thêm 0.5 vào kích thước cuối cùng của Object newObj.transform.localScale = Vector3.one * safeScale * modelScaleMultiplier; _spawnedCells[pos] = newObj; if (animate && visualProfile.animationDuration > 0) { StartCoroutine(AnimateCell(newObj.transform)); } } // ================================================================================= // THUẬT TOÁN BITMASK AUTO-TILING // ================================================================================= private (GameObject, Quaternion) GetCorridorPrefabAndRotation(int x, int z) { bool top = IsPath(x, z + 1); bool right = IsPath(x + 1, z); bool bottom = IsPath(x, z - 1); bool left = IsPath(x - 1, z); int mask = 0; if (top) mask += 1; if (right) mask += 2; if (bottom) mask += 4; if (left) mask += 8; GameObject prefabToSpawn = visualProfile.corridorDeadEnd; float yRotation = 0f; switch (mask) { // === NGÕ CỤT (Giữ nguyên) === 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; // === ĐƯỜNG THẲNG (Giữ nguyên) === case 5: prefabToSpawn = visualProfile.corridorStraight; yRotation = 0f; break; case 10: prefabToSpawn = visualProfile.corridorStraight; yRotation = 90f; break; // === GÓC CUA (Giữ nguyên) === 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 = -90f; break; // === NGÃ BA (Đã điều chỉnh lại góc xoay) === case 11: prefabToSpawn = visualProfile.corridorTJunction; yRotation = 0f; break; // Mở: Trên, Phải, Trái. Bịt: Dưới. case 7: prefabToSpawn = visualProfile.corridorTJunction; yRotation = -90f; break; // Mở: Trên, Phải, Dưới. Bịt: Trái. case 14: prefabToSpawn = visualProfile.corridorTJunction; yRotation = 180f; break; // Mở: Phải, Dưới, Trái. Bịt: Trên. case 13: prefabToSpawn = visualProfile.corridorTJunction; yRotation = 90f; break; // Mở: Trên, Dưới, Trái. Bịt: Phải. // === NGÃ TƯ (Giữ nguyên) === case 15: prefabToSpawn = visualProfile.corridorCross; yRotation = 0f; break; default: prefabToSpawn = visualProfile.corridorDeadEnd; yRotation = 0f; break; } if (prefabToSpawn == null) prefabToSpawn = visualProfile.corridorPrefab; return (prefabToSpawn, Quaternion.Euler(0, yRotation, 0)); } private bool IsPath(int x, int z) { if (_currentGrid == null || !_currentGrid.IsInBounds(x, z)) return false; MazeCellType type = _currentGrid.GetCell(x, z); return type == MazeCellType.Corridor || type == MazeCellType.Processing || type == MazeCellType.Start || type == MazeCellType.End || type == MazeCellType.Path; } // ================================================================================= // ANIMATION // ================================================================================= private IEnumerator AnimateCell(Transform target) { 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; } } } }