Update
This commit is contained in:
@@ -7,10 +7,11 @@ namespace Hallucinate.GameSetup.Maze
|
||||
/// Holds the logical state of the maze grid.
|
||||
/// Notifies listeners whenever a cell changes to trigger visual updates.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class MazeGrid
|
||||
{
|
||||
public int Width { get; private set; }
|
||||
public int Depth { get; private set; }
|
||||
public int Width { get; set; }
|
||||
public int Depth { get; set; }
|
||||
public int Level { get; set; }
|
||||
|
||||
private readonly MazeCellType[,] _cells;
|
||||
|
||||
@@ -56,33 +56,39 @@ namespace Hallucinate.GameSetup.Maze
|
||||
[ContextMenu("Regenerate")]
|
||||
public void Regenerate()
|
||||
{
|
||||
// Bước 1: Khởi tạo tất cả các tầng mê cung
|
||||
for (int i = 0; i < mazes.Length; i++)
|
||||
if (_generationCoroutine != null)
|
||||
{
|
||||
mazes[i].width = width;
|
||||
mazes[i].depth = depth;
|
||||
mazes[i].level = i;
|
||||
mazes[i].Build(); // Lưu ý: Hàm Build này của bạn nên được tối ưu để KHÔNG Instantiate Model vội.
|
||||
StopCoroutine(_generationCoroutine);
|
||||
}
|
||||
|
||||
// Bước 2: Tạo điểm nối giữa các cặp tầng (0->1, 1->2, 2->3...)
|
||||
mazeRenderer.Clear();
|
||||
|
||||
// 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
|
||||
IMazeAlgorithm algorithmForFloor = GetAlgorithm(selectedAlgorithm);
|
||||
algorithmForFloor.Generate(mazes[i]);
|
||||
}
|
||||
|
||||
// Step 2: Create connections between adjacent floors
|
||||
for (int i = 0; i < mazes.Length - 1; i++)
|
||||
{
|
||||
MazeGrid currentFloor = mazes[i];
|
||||
MazeGrid nextFloor = mazes[i + 1];
|
||||
|
||||
// 2.1: Quét tìm TẤT CẢ các tọa độ (x, z) có thể làm điểm nối
|
||||
List<Vector2Int> possibleConnections = new List<Vector2Int>();
|
||||
|
||||
for (int z = 0; z < depth; z++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
// Nếu cả Tầng Dưới và Tầng Trên đều đang là đường đi (Path) ở tọa độ này
|
||||
// (Bạn cần đổi điều kiện này cho khớp với Enum/Class thực tế của bạn)
|
||||
bool isCurrentFloorPath =
|
||||
currentFloor.piecePlace[x, z].piece == MazeGrid.PieceType.Vertical_Straight;
|
||||
bool isNextFloorPath = nextFloor.piecePlace[x, z].piece == MazeGrid.PieceType.Vertical_Straight;
|
||||
// Check if both floors have a corridor at this position
|
||||
bool isCurrentFloorPath = currentFloor.GetCell(x, z) == MazeCellType.Corridor;
|
||||
bool isNextFloorPath = nextFloor.GetCell(x, z) == MazeCellType.Corridor;
|
||||
|
||||
if (isCurrentFloorPath && isNextFloorPath)
|
||||
{
|
||||
@@ -91,60 +97,48 @@ namespace Hallucinate.GameSetup.Maze
|
||||
}
|
||||
}
|
||||
|
||||
// 2.2: Chọn ngẫu nhiên N điểm từ danh sách để làm thang nối
|
||||
int connectionsMade = 0;
|
||||
|
||||
// Trộn ngẫu nhiên danh sách (Shuffle) để các điểm nối không bị dồn về 1 góc
|
||||
ShuffleList(possibleConnections);
|
||||
|
||||
int connectionsMade = 0;
|
||||
foreach (Vector2Int pos in possibleConnections)
|
||||
{
|
||||
if (connectionsMade >= connectionsPerFloor) break; // Đã đủ số lượng đường lên thì dừng
|
||||
if (connectionsMade >= connectionsPerFloor) break;
|
||||
|
||||
int x = pos.x;
|
||||
int z = pos.y;
|
||||
|
||||
// Xóa model đường đi cũ (nếu hàm Build() đã lỡ Instantiate)
|
||||
if (currentFloor.piecePlace[x, z].model != null) Destroy(currentFloor.piecePlace[x, z].model);
|
||||
if (nextFloor.piecePlace[x, z].model != null) Destroy(nextFloor.piecePlace[x, z].model);
|
||||
|
||||
// TÍNH TOẠ ĐỘ Y CHUẨN XÁC: Y = Tầng * Độ_Cao_Tầng
|
||||
Vector3 upManHolePos = new Vector3(x * currentFloor.scale, currentFloor.level * floorHeight,
|
||||
z * currentFloor.scale);
|
||||
Vector3 ladderManPos = new Vector3(x * nextFloor.scale, nextFloor.level * floorHeight,
|
||||
z * nextFloor.scale);
|
||||
|
||||
// Sinh Model mới tại điểm đã chốt
|
||||
currentFloor.piecePlace[x, z].model =
|
||||
Instantiate(straightManHoleUp, upManHolePos, Quaternion.identity);
|
||||
nextFloor.piecePlace[x, z].model =
|
||||
Instantiate(straightManHoleLadder, ladderManPos, Quaternion.identity);
|
||||
|
||||
// Cập nhật loại dữ liệu để hệ thống ghi nhận đây là ô cầu thang
|
||||
// currentFloor.piecePlace[x, z].piece = Maze.PieceType.StairsUp; (Nếu bạn có enum này)
|
||||
// Set stair cells
|
||||
currentFloor.SetCell(x, z, MazeCellType.StairUp);
|
||||
nextFloor.SetCell(x, z, MazeCellType.StairDown);
|
||||
|
||||
connectionsMade++;
|
||||
}
|
||||
}
|
||||
|
||||
if (_generationCoroutine != null)
|
||||
// Step 3: Render all floors
|
||||
if (mazes.Length > 0)
|
||||
{
|
||||
StopCoroutine(_generationCoroutine);
|
||||
}
|
||||
|
||||
mazeRenderer.Clear();
|
||||
_grid = new MazeGrid(width, depth);
|
||||
mazeRenderer.Initialize(_grid, mazeContainer);
|
||||
|
||||
IMazeAlgorithm algorithm = GetAlgorithm(selectedAlgorithm);
|
||||
|
||||
if (debugMode)
|
||||
{
|
||||
_generationCoroutine = StartCoroutine(algorithm.GenerateStepByStep(_grid, visualizationInterval));
|
||||
for (int i = 0; i < mazes.Length; i++)
|
||||
{
|
||||
mazeRenderer.Initialize(mazes[i], mazeContainer, i == 0);
|
||||
}
|
||||
_grid = mazes[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
algorithm.Generate(_grid);
|
||||
_grid = new MazeGrid(width, depth);
|
||||
mazeRenderer.Initialize(_grid, mazeContainer);
|
||||
|
||||
IMazeAlgorithm algorithm = GetAlgorithm(selectedAlgorithm);
|
||||
|
||||
if (debugMode)
|
||||
{
|
||||
_generationCoroutine = StartCoroutine(algorithm.GenerateStepByStep(_grid, visualizationInterval));
|
||||
}
|
||||
else
|
||||
{
|
||||
algorithm.Generate(_grid);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void ShuffleList<T>(List<T> list)
|
||||
|
||||
@@ -8,119 +8,37 @@ namespace Hallucinate.GameSetup.Maze
|
||||
/// Responsible for the visual representation of the maze.
|
||||
/// Handles spawning, pooling, and animations with safety checks.
|
||||
/// </summary>
|
||||
// public class MazeRenderer : MonoBehaviour
|
||||
// {
|
||||
// [SerializeField] private MazeVisualProfile visualProfile;
|
||||
//
|
||||
// private readonly Dictionary<Vector2Int, GameObject> _spawnedCells = new Dictionary<Vector2Int, GameObject>();
|
||||
// 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;
|
||||
public float floorHeight = 3.5f;
|
||||
|
||||
private readonly Dictionary<Vector2Int, GameObject> _spawnedCells = new Dictionary<Vector2Int, GameObject>();
|
||||
public float Scale => visualProfile != null ? visualProfile.scale : 1f;
|
||||
|
||||
private readonly Dictionary<Vector3Int, GameObject> _spawnedCells = new Dictionary<Vector3Int, GameObject>();
|
||||
private Transform _container;
|
||||
private MazeGrid _currentGrid;
|
||||
private List<MazeGrid> _grids = new List<MazeGrid>();
|
||||
|
||||
public void Initialize(MazeGrid grid, Transform container)
|
||||
public void Initialize(MazeGrid grid, Transform container, bool clearExisting = true)
|
||||
{
|
||||
_currentGrid = grid;
|
||||
if (clearExisting)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
_container = container;
|
||||
grid.OnCellChanged += HandleCellChanged;
|
||||
if (!_grids.Contains(grid))
|
||||
{
|
||||
_grids.Add(grid);
|
||||
grid.OnCellChanged += (x, z, type) => HandleCellChanged(grid, x, z, type);
|
||||
}
|
||||
|
||||
// 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);
|
||||
UpdateCellVisual(grid, x, z, grid.GetCell(x, z), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,39 +53,45 @@ namespace Hallucinate.GameSetup.Maze
|
||||
}
|
||||
|
||||
_spawnedCells.Clear();
|
||||
_currentGrid = null;
|
||||
}
|
||||
|
||||
private void HandleCellChanged(int x, int z, MazeCellType type)
|
||||
{
|
||||
UpdateCellVisual(x, z, type, true);
|
||||
|
||||
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))
|
||||
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
if (IsPath(x, z))
|
||||
// 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();
|
||||
}
|
||||
|
||||
private void HandleCellChanged(MazeGrid grid, int x, int z, MazeCellType type)
|
||||
{
|
||||
UpdateCellVisual(grid, x, z, type, true);
|
||||
|
||||
UpdateNeighborVisual(grid, x + 1, z);
|
||||
UpdateNeighborVisual(grid, x - 1, z);
|
||||
UpdateNeighborVisual(grid, x, z + 1);
|
||||
UpdateNeighborVisual(grid, x, z - 1);
|
||||
}
|
||||
|
||||
private void UpdateNeighborVisual(MazeGrid grid, int x, int z)
|
||||
{
|
||||
if (grid != null && grid.IsInBounds(x, z))
|
||||
{
|
||||
if (IsPath(grid, x, z))
|
||||
{
|
||||
MazeCellType type = _currentGrid.GetCell(x, z);
|
||||
UpdateCellVisual(x, z, type, false);
|
||||
MazeCellType type = grid.GetCell(x, z);
|
||||
UpdateCellVisual(grid, x, z, type, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCellVisual(int x, int z, MazeCellType type, bool animate)
|
||||
private void UpdateCellVisual(MazeGrid grid, int x, int z, MazeCellType type, bool animate)
|
||||
{
|
||||
Vector2Int pos = new Vector2Int(x, z);
|
||||
Vector3Int posKey = new Vector3Int(x, grid.Level, z);
|
||||
|
||||
if (_spawnedCells.TryGetValue(pos, out GameObject oldObj))
|
||||
if (_spawnedCells.TryGetValue(posKey, out GameObject oldObj))
|
||||
{
|
||||
Destroy(oldObj);
|
||||
_spawnedCells.Remove(pos);
|
||||
_spawnedCells.Remove(posKey);
|
||||
}
|
||||
|
||||
GameObject prefab = null;
|
||||
@@ -175,7 +99,7 @@ namespace Hallucinate.GameSetup.Maze
|
||||
|
||||
if (type == MazeCellType.Corridor || type == MazeCellType.Processing)
|
||||
{
|
||||
(prefab, rotation) = GetCorridorPrefabAndRotation(x, z);
|
||||
(prefab, rotation) = GetCorridorPrefabAndRotation(grid, x, z);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -187,15 +111,15 @@ namespace Hallucinate.GameSetup.Maze
|
||||
float safeScale = Mathf.Max(0.001f, visualProfile.scale);
|
||||
float modelScaleMultiplier = 0.25f;
|
||||
|
||||
Vector3 localPos = new Vector3(x * safeScale, 0, z * safeScale);
|
||||
float yOffset = grid.Level * floorHeight;
|
||||
Vector3 localPos = new Vector3(x * safeScale, yOffset, z * safeScale);
|
||||
|
||||
// GameObject newObj = Instantiate(prefab, worldPos, rotation, _container);
|
||||
GameObject newObj = Instantiate(prefab, _container);
|
||||
|
||||
newObj.transform.localPosition = localPos;
|
||||
newObj.transform.localRotation = rotation;
|
||||
newObj.transform.localScale = Vector3.one * safeScale * modelScaleMultiplier;
|
||||
_spawnedCells[pos] = newObj;
|
||||
_spawnedCells[posKey] = newObj;
|
||||
|
||||
if (animate && visualProfile.animationDuration > 0)
|
||||
{
|
||||
@@ -206,12 +130,12 @@ namespace Hallucinate.GameSetup.Maze
|
||||
// =================================================================================
|
||||
// THUẬT TOÁN BITMASK AUTO-TILING
|
||||
// =================================================================================
|
||||
private (GameObject, Quaternion) GetCorridorPrefabAndRotation(int x, int z)
|
||||
private (GameObject, Quaternion) GetCorridorPrefabAndRotation(MazeGrid grid, 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);
|
||||
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);
|
||||
|
||||
int mask = 0;
|
||||
if (top) mask += 1;
|
||||
@@ -295,7 +219,6 @@ namespace Hallucinate.GameSetup.Maze
|
||||
break;
|
||||
}
|
||||
|
||||
// --- CỘNG THÊM OFFSET (Đã xóa bỏ phần code thừa bị lặp lại) ---
|
||||
float finalRotation = yRotation;
|
||||
if (prefabToSpawn == visualProfile.corridorTJunction) finalRotation += visualProfile.tJunctionOffset;
|
||||
if (prefabToSpawn == visualProfile.corridorDeadEnd) finalRotation += visualProfile.deadEndOffset;
|
||||
@@ -306,15 +229,17 @@ namespace Hallucinate.GameSetup.Maze
|
||||
return (prefabToSpawn, Quaternion.Euler(0, finalRotation, 0));
|
||||
}
|
||||
|
||||
private bool IsPath(int x, int z)
|
||||
private bool IsPath(MazeGrid grid, int x, int z)
|
||||
{
|
||||
if (_currentGrid == null || !_currentGrid.IsInBounds(x, z)) return false;
|
||||
MazeCellType type = _currentGrid.GetCell(x, z);
|
||||
if (grid == null || !grid.IsInBounds(x, z)) return false;
|
||||
MazeCellType type = grid.GetCell(x, z);
|
||||
return type == MazeCellType.Corridor
|
||||
|| type == MazeCellType.Processing
|
||||
|| type == MazeCellType.Start
|
||||
|| type == MazeCellType.End
|
||||
|| type == MazeCellType.Path;
|
||||
|| type == MazeCellType.Path
|
||||
|| type == MazeCellType.StairUp
|
||||
|| type == MazeCellType.StairDown;
|
||||
}
|
||||
|
||||
// =================================================================================
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Hallucinate.GameSetup.Maze
|
||||
public GameObject pathPrefab;
|
||||
public GameObject startPrefab;
|
||||
public GameObject endPrefab;
|
||||
public GameObject stairUpPrefab;
|
||||
public GameObject stairDownPrefab;
|
||||
|
||||
[Header("Corridor Types")]
|
||||
public GameObject corridorStraight;
|
||||
@@ -39,6 +41,8 @@ namespace Hallucinate.GameSetup.Maze
|
||||
MazeCellType.Path => pathPrefab,
|
||||
MazeCellType.Start => startPrefab,
|
||||
MazeCellType.End => endPrefab,
|
||||
MazeCellType.StairUp => stairUpPrefab,
|
||||
MazeCellType.StairDown => stairDownPrefab,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user