Merge branch 'main' of https://scove-vault.duckdns.org/scove/HALLUCINATION
This commit is contained in:
File diff suppressed because one or more lines are too long
BIN
.gemini-workspace-history/session-2026-05-03-21-30.json.gz
Normal file
BIN
.gemini-workspace-history/session-2026-05-03-21-30.json.gz
Normal file
Binary file not shown.
20
.idea/.idea.HALLUCINATE/.idea/workspace.xml
generated
20
.idea/.idea.HALLUCINATE/.idea/workspace.xml
generated
@@ -5,12 +5,14 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="f9183c68-daf0-43b8-be4c-fad79983f91b" name="Changes" comment="">
|
||||
<change beforePath="$PROJECT_DIR$/.gemini-workspace-history/active-context.md" beforeDir="false" afterPath="$PROJECT_DIR$/.gemini-workspace-history/active-context.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/.idea.HALLUCINATE/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HALLUCINATE/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/Scenes/Main Scene.unity" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scenes/Main Scene.unity" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Network/BasicSpawner.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Network/BasicSpawner.cs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/Network/PlayerDataManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/Network/PlayerDataManager.cs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/UI/LobbyController.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/UI/LobbyController.cs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/UI/Lobby.uxml" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/UI/Lobby.uxml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/.gemini-workspace-history/active-context.md" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/.gemini-workspace-history/active-context.md" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/GameSetup/Maze/MazeGrid.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/GameSetup/Maze/MazeGrid.cs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/GameSetup/Maze/MazeManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/GameSetup/Maze/MazeManager.cs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/GameSetup/Maze/MazeRenderer.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/GameSetup/Maze/MazeRenderer.cs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/Scripts/GameSetup/Maze/MazeVisualProfile.cs" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/Scripts/GameSetup/Maze/MazeVisualProfile.cs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Assets/UI/MainPanelSettings.asset" beforeDir="false" afterPath="$PROJECT_DIR$/Assets/UI/MainPanelSettings.asset" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -18,7 +20,7 @@
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="EmbeddingIndexingInfo">
|
||||
<option name="cachedIndexableFilesCount" value="18" />
|
||||
<option name="cachedIndexableFilesCount" value="28" />
|
||||
<option name="fileBasedEmbeddingIndicesEnabled" value="true" />
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
@@ -26,6 +28,8 @@
|
||||
</component>
|
||||
<component name="HighlightingSettingsPerFile">
|
||||
<setting file="file://$PROJECT_DIR$/Assets/Scripts/Audio/AudioManager.cs" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/Assets/Scripts/Network/BasicSpawner.cs" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/Assets/Scripts/UI/LobbyController.cs" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/Assets/Third Parties/Photon/Fusion/Editor/Fusion.Unity.Editor.cs" root0="SKIP_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/Assets/UI/MainPanelSettings.asset" root0="FORCE_HIGHLIGHTING" />
|
||||
<setting file="file://$PROJECT_DIR$/Library/PackageCache/com.unity.render-pipelines.core@04ab0eefa0c3/Editor/Utilities/LocalizationHelper.cs" root0="SKIP_HIGHLIGHTING" />
|
||||
@@ -158,6 +162,10 @@
|
||||
<workItem from="1777631506531" duration="2333000" />
|
||||
<workItem from="1777639175483" duration="9746000" />
|
||||
<workItem from="1777652919998" duration="1890000" />
|
||||
<workItem from="1777732628340" duration="74000" />
|
||||
<workItem from="1777732739619" duration="808000" />
|
||||
<workItem from="1777818351880" duration="313000" />
|
||||
<workItem from="1777991464807" duration="4843000" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -7,6 +7,7 @@ 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; set; }
|
||||
|
||||
@@ -5,34 +5,43 @@ using UnityEngine;
|
||||
namespace Hallucinate.GameSetup.Maze
|
||||
{
|
||||
/// <summary>
|
||||
/// Central controller for the Multi-floor Maze system.
|
||||
/// Handles initialization order and cross-floor connections.
|
||||
/// Central controller for the Maze system.
|
||||
/// Manages algorithm selection, debug speed, and regeneration.
|
||||
/// </summary>
|
||||
public class MazeManager : MonoBehaviour
|
||||
{
|
||||
public enum AlgorithmType { Recursive, Wilsons, Prims, Crawler }
|
||||
|
||||
[Header("System Settings")]
|
||||
[Tooltip("Số lượng tầng mê cung cần sinh ra")]
|
||||
[Range(1, 10)] public int floorCount = 2;
|
||||
public float floorHeight = 4.0f;
|
||||
public MazeGrid[] mazes;
|
||||
public float floorHeight = 3.5f;
|
||||
public int connectionsPerFloor = 2;
|
||||
|
||||
[Header("Grid Settings")]
|
||||
[SerializeField] private AlgorithmType selectedAlgorithm;
|
||||
[SerializeField] private int width = 20;
|
||||
[SerializeField] private int depth = 20;
|
||||
[SerializeField] private int width = 30;
|
||||
[SerializeField] private int depth = 30;
|
||||
|
||||
[Header("Debug Settings")]
|
||||
[SerializeField] private bool debugMode = true;
|
||||
[Range(0.001f, 0.5f)]
|
||||
[SerializeField] private float visualizationInterval = 0.05f;
|
||||
|
||||
[Header("References")]
|
||||
[SerializeField] private MazeRenderer rendererPrefab;
|
||||
[SerializeField] private MazeRenderer mazeRenderer;
|
||||
[SerializeField] private Transform mazeContainer;
|
||||
|
||||
private List<MazeGrid> _mazeFloors = new List<MazeGrid>();
|
||||
private List<MazeRenderer> _activeRenderers = new List<MazeRenderer>();
|
||||
[Header("Corridor Setting")]
|
||||
public GameObject straightManHoleLadder;
|
||||
public GameObject straightManHoleUp;
|
||||
public GameObject deadendManHoleLadder;
|
||||
public GameObject deadendManHoleUp;
|
||||
|
||||
|
||||
|
||||
private MazeGrid _grid;
|
||||
private Coroutine _generationCoroutine;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Debug.Log("--- Tớ là: " + gameObject.name + " đang gọi Regenerate! ---");
|
||||
Regenerate();
|
||||
}
|
||||
|
||||
@@ -44,117 +53,94 @@ namespace Hallucinate.GameSetup.Maze
|
||||
}
|
||||
}
|
||||
|
||||
//[ContextMenu("Regenerate")]
|
||||
[ContextMenu("Regenerate")]
|
||||
public void Regenerate()
|
||||
{
|
||||
StopAllCoroutines();
|
||||
ClearExistingMaze();
|
||||
|
||||
// 1. Khởi tạo dữ liệu các tầng (Logic Data)
|
||||
for (int i = 0; i < floorCount; i++)
|
||||
if (_generationCoroutine != null)
|
||||
{
|
||||
// FIX: Khởi tạo Object trước khi gán thuộc tính
|
||||
MazeGrid newFloor = new MazeGrid(width, depth);
|
||||
newFloor.Level = i;
|
||||
StopCoroutine(_generationCoroutine);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
List<Vector2Int> possibleConnections = new List<Vector2Int>();
|
||||
|
||||
for (int z = 0; z < depth; z++)
|
||||
{
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
possibleConnections.Add(new Vector2Int(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShuffleList(possibleConnections);
|
||||
|
||||
int connectionsMade = 0;
|
||||
foreach (Vector2Int pos in possibleConnections)
|
||||
{
|
||||
if (connectionsMade >= connectionsPerFloor) break;
|
||||
|
||||
int x = pos.x;
|
||||
int z = pos.y;
|
||||
|
||||
// Set stair cells
|
||||
currentFloor.SetCell(x, z, MazeCellType.StairUp);
|
||||
nextFloor.SetCell(x, z, MazeCellType.StairDown);
|
||||
|
||||
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);
|
||||
|
||||
// 2. Chạy thuật toán sinh mê cung cho tầng này (Sync để đảm bảo có dữ liệu trước khi nối tầng)
|
||||
IMazeAlgorithm algorithm = GetAlgorithm(selectedAlgorithm);
|
||||
algorithm.Generate(newFloor);
|
||||
|
||||
_mazeFloors.Add(newFloor);
|
||||
}
|
||||
|
||||
// 3. Tạo điểm nối cầu thang (Stairs) giữa các tầng kề nhau
|
||||
ConnectFloors();
|
||||
|
||||
// 4. Hiển thị 3D cho tất cả các tầng
|
||||
RenderAllFloors();
|
||||
}
|
||||
|
||||
private void ConnectFloors()
|
||||
if (debugMode)
|
||||
{
|
||||
if (_mazeFloors.Count < 2) return;
|
||||
|
||||
for (int i = 0; i < _mazeFloors.Count - 1; i++)
|
||||
_generationCoroutine = StartCoroutine(algorithm.GenerateStepByStep(_grid, visualizationInterval));
|
||||
}
|
||||
else
|
||||
{
|
||||
MazeGrid currentFloor = _mazeFloors[i];
|
||||
MazeGrid nextFloor = _mazeFloors[i + 1];
|
||||
|
||||
List<Vector2Int> overlapPaths = new List<Vector2Int>();
|
||||
|
||||
// Tìm các tọa độ (x, z) mà cả 2 tầng đều là đường đi (Corridor)
|
||||
for (int z = 1; z < depth - 1; z++)
|
||||
{
|
||||
for (int x = 1; x < width - 1; x++)
|
||||
{
|
||||
if (currentFloor.GetCell(x, z) == MazeCellType.Corridor &&
|
||||
nextFloor.GetCell(x, z) == MazeCellType.Corridor)
|
||||
{
|
||||
overlapPaths.Add(new Vector2Int(x, z));
|
||||
algorithm.Generate(_grid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trộn ngẫu nhiên danh sách để điểm nối rải rác
|
||||
ShuffleList(overlapPaths);
|
||||
|
||||
int actualConnections = Mathf.Min(connectionsPerFloor, overlapPaths.Count);
|
||||
for (int j = 0; j < actualConnections; j++)
|
||||
{
|
||||
Vector2Int pos = overlapPaths[j];
|
||||
// Đánh dấu logic để Renderer tự chọn Prefab cầu thang từ VisualProfile
|
||||
currentFloor.SetCell(pos.x, pos.y, MazeCellType.StairsUp);
|
||||
nextFloor.SetCell(pos.x, pos.y, MazeCellType.StairsDown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderAllFloors()
|
||||
{
|
||||
Debug.Log("--- Đang chạy hàm này! ---");
|
||||
for (int i = 0; i < _mazeFloors.Count; i++)
|
||||
{
|
||||
if (_activeRenderers.Count > 100) {
|
||||
Debug.LogError("DỪNG LẠI! Quá nhiều tầng rồi!");
|
||||
return;
|
||||
}
|
||||
// Tạo một Renderer instance cho mỗi tầng
|
||||
MazeRenderer floorRenderer = Instantiate(rendererPrefab, mazeContainer);
|
||||
floorRenderer.gameObject.name = $"Floor_Renderer_{i}";
|
||||
|
||||
// Đặt vị trí Y của tầng
|
||||
floorRenderer.transform.localPosition = new Vector3(0, i * floorHeight, 0);
|
||||
|
||||
// Khởi tạo hiển thị cho Grid tương ứng
|
||||
floorRenderer.Initialize(_mazeFloors[i], floorRenderer.transform);
|
||||
|
||||
_activeRenderers.Add(floorRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearExistingMaze()
|
||||
{
|
||||
foreach (var renderer in _activeRenderers)
|
||||
{
|
||||
if (renderer != null)
|
||||
{
|
||||
renderer.Clear();
|
||||
Destroy(renderer.gameObject);
|
||||
}
|
||||
}
|
||||
_activeRenderers.Clear();
|
||||
_mazeFloors.Clear();
|
||||
|
||||
// Xóa sạch các object con cũ trong container (nếu có)
|
||||
if (mazeContainer != null)
|
||||
{
|
||||
foreach (Transform child in mazeContainer)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ShuffleList<T>(List<T> list)
|
||||
{
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
|
||||
@@ -8,124 +8,39 @@ 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;
|
||||
if (!_grids.Contains(grid))
|
||||
{
|
||||
_grids.Add(grid);
|
||||
grid.OnCellChanged += (x, z, type) => HandleCellChanged(grid, x, z, type);
|
||||
}
|
||||
|
||||
// ĐỪNG đăng ký cái này vội: 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);
|
||||
UpdateCellVisual(grid, x, z, grid.GetCell(x, z), false);
|
||||
}
|
||||
}
|
||||
|
||||
// ĐĂNG KÝ Ở ĐÂY: Sau khi đã vẽ xong hết rồi mới nghe ngóng thay đổi
|
||||
grid.OnCellChanged += HandleCellChanged;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
@@ -138,52 +53,53 @@ namespace Hallucinate.GameSetup.Maze
|
||||
}
|
||||
|
||||
_spawnedCells.Clear();
|
||||
_currentGrid = null;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private void HandleCellChanged(int x, int z, MazeCellType type)
|
||||
private void HandleCellChanged(MazeGrid grid, int x, int z, MazeCellType type)
|
||||
{
|
||||
UpdateCellVisual(x, z, type, true);
|
||||
UpdateCellVisual(grid, x, z, type, true);
|
||||
|
||||
UpdateNeighborVisual(x + 1, z);
|
||||
UpdateNeighborVisual(x - 1, z);
|
||||
UpdateNeighborVisual(x, z + 1);
|
||||
UpdateNeighborVisual(x, z - 1);
|
||||
UpdateNeighborVisual(grid, x + 1, z);
|
||||
UpdateNeighborVisual(grid, x - 1, z);
|
||||
UpdateNeighborVisual(grid, x, z + 1);
|
||||
UpdateNeighborVisual(grid, x, z - 1);
|
||||
}
|
||||
|
||||
private void UpdateNeighborVisual(int x, int z)
|
||||
private void UpdateNeighborVisual(MazeGrid grid, int x, int z)
|
||||
{
|
||||
if (_currentGrid != null && _currentGrid.IsInBounds(x, z))
|
||||
if (grid != null && grid.IsInBounds(x, z))
|
||||
{
|
||||
if (IsPath(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);
|
||||
|
||||
// 1. DÙNG DestroyImmediate ĐỂ XÓA THẬT SỰ TRƯỚC KHI TẠO MỚI
|
||||
if (_spawnedCells.TryGetValue(pos, out GameObject oldObj))
|
||||
if (_spawnedCells.TryGetValue(posKey, out GameObject oldObj))
|
||||
{
|
||||
if (oldObj != null)
|
||||
{
|
||||
DestroyImmediate(oldObj);
|
||||
}
|
||||
_spawnedCells.Remove(pos);
|
||||
Destroy(oldObj);
|
||||
_spawnedCells.Remove(posKey);
|
||||
}
|
||||
|
||||
GameObject prefab = null;
|
||||
Quaternion rotation = Quaternion.identity;
|
||||
|
||||
// Logic xét duyệt loại prefab
|
||||
if (type == MazeCellType.Corridor || type == MazeCellType.Processing)
|
||||
{
|
||||
(prefab, rotation) = GetCorridorPrefabAndRotation(x, z);
|
||||
(prefab, rotation) = GetCorridorPrefabAndRotation(grid, x, z);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -193,17 +109,17 @@ namespace Hallucinate.GameSetup.Maze
|
||||
if (prefab == null) return;
|
||||
|
||||
float safeScale = Mathf.Max(0.001f, visualProfile.scale);
|
||||
float modelScaleMultiplier = 0.167f;
|
||||
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)
|
||||
{
|
||||
@@ -214,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;
|
||||
@@ -303,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;
|
||||
@@ -314,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,12 +17,8 @@ namespace Hallucinate.GameSetup.Maze
|
||||
public GameObject pathPrefab;
|
||||
public GameObject startPrefab;
|
||||
public GameObject endPrefab;
|
||||
public GameObject stairsUp;
|
||||
|
||||
[Header("Room Pieces")]
|
||||
public GameObject wallpiece;
|
||||
public GameObject floorpiece;
|
||||
public GameObject cellingpiece;
|
||||
public GameObject stairUpPrefab;
|
||||
public GameObject stairDownPrefab;
|
||||
|
||||
[Header("Corridor Types")]
|
||||
public GameObject corridorStraight;
|
||||
@@ -45,7 +41,8 @@ namespace Hallucinate.GameSetup.Maze
|
||||
MazeCellType.Path => pathPrefab,
|
||||
MazeCellType.Start => startPrefab,
|
||||
MazeCellType.End => endPrefab,
|
||||
MazeCellType.StairsUp => stairsUp,
|
||||
MazeCellType.StairUp => stairUpPrefab,
|
||||
MazeCellType.StairDown => stairDownPrefab,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
8
Assets/Scripts/UI/Components.meta
Normal file
8
Assets/Scripts/UI/Components.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50f4c7bcd8223714fa716961ca9e3688
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
133
Assets/Scripts/UI/Components/VectorShapeElement.cs
Normal file
133
Assets/Scripts/UI/Components/VectorShapeElement.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Hallucinate.UI.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// An adjustable Vector Element that you can "sketch" and tweak directly in UI Builder (UXML).
|
||||
/// Supports Parametric shapes (Pill, Polygon, Star) and Custom Paths.
|
||||
/// </summary>
|
||||
public class VectorShapeElement : VisualElement
|
||||
{
|
||||
public enum ShapeType { Pill, Polygon, Star, CustomPath }
|
||||
|
||||
public new class UxmlFactory : UxmlFactory<VectorShapeElement, UxmlTraits> { }
|
||||
|
||||
public new class UxmlTraits : VisualElement.UxmlTraits
|
||||
{
|
||||
UxmlEnumAttributeDescription<ShapeType> m_ShapeType = new UxmlEnumAttributeDescription<ShapeType> { name = "shape-type", defaultValue = ShapeType.Pill };
|
||||
UxmlColorAttributeDescription m_FillColor = new UxmlColorAttributeDescription { name = "fill-color", defaultValue = Color.white };
|
||||
UxmlColorAttributeDescription m_StrokeColor = new UxmlColorAttributeDescription { name = "stroke-color", defaultValue = Color.black };
|
||||
UxmlFloatAttributeDescription m_StrokeWidth = new UxmlFloatAttributeDescription { name = "stroke-width", defaultValue = 2f };
|
||||
UxmlFloatAttributeDescription m_CornerRadius = new UxmlFloatAttributeDescription { name = "corner-radius", defaultValue = 10f };
|
||||
UxmlIntAttributeDescription m_Sides = new UxmlIntAttributeDescription { name = "sides", defaultValue = 5 };
|
||||
UxmlFloatAttributeDescription m_Inwardness = new UxmlFloatAttributeDescription { name = "star-inwardness", defaultValue = 0.5f };
|
||||
UxmlStringAttributeDescription m_PathData = new UxmlStringAttributeDescription { name = "path-data", defaultValue = "" };
|
||||
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
var ate = ve as VectorShapeElement;
|
||||
|
||||
ate.shapeType = m_ShapeType.GetValueFromBag(bag, cc);
|
||||
ate.fillColor = m_FillColor.GetValueFromBag(bag, cc);
|
||||
ate.strokeColor = m_StrokeColor.GetValueFromBag(bag, cc);
|
||||
ate.strokeWidth = m_StrokeWidth.GetValueFromBag(bag, cc);
|
||||
ate.cornerRadius = m_CornerRadius.GetValueFromBag(bag, cc);
|
||||
ate.sides = Mathf.Max(3, m_Sides.GetValueFromBag(bag, cc));
|
||||
ate.inwardness = Mathf.Clamp01(m_Inwardness.GetValueFromBag(bag, cc));
|
||||
ate.pathData = m_PathData.GetValueFromBag(bag, cc);
|
||||
|
||||
ate.MarkDirtyRepaint();
|
||||
}
|
||||
}
|
||||
|
||||
public ShapeType shapeType { get; set; }
|
||||
public Color fillColor { get; set; }
|
||||
public Color strokeColor { get; set; }
|
||||
public float strokeWidth { get; set; }
|
||||
public float cornerRadius { get; set; }
|
||||
public int sides { get; set; }
|
||||
public float inwardness { get; set; }
|
||||
public string pathData { get; set; }
|
||||
|
||||
public VectorShapeElement()
|
||||
{
|
||||
generateVisualContent += OnGenerateVisualContent;
|
||||
}
|
||||
|
||||
private void OnGenerateVisualContent(MeshGenerationContext mgc)
|
||||
{
|
||||
var paint = mgc.painter2D;
|
||||
var rect = contentRect;
|
||||
|
||||
if (rect.width <= 0 || rect.height <= 0) return;
|
||||
|
||||
paint.BeginPath();
|
||||
paint.fillColor = fillColor;
|
||||
paint.strokeColor = strokeColor;
|
||||
paint.lineWidth = strokeWidth;
|
||||
|
||||
switch (shapeType)
|
||||
{
|
||||
case ShapeType.Pill: DrawPill(paint, rect); break;
|
||||
case ShapeType.Polygon: DrawPolygon(paint, rect, false); break;
|
||||
case ShapeType.Star: DrawPolygon(paint, rect, true); break;
|
||||
case ShapeType.CustomPath: DrawCustomPath(paint); break;
|
||||
}
|
||||
|
||||
paint.ClosePath();
|
||||
paint.Fill();
|
||||
if (strokeWidth > 0) paint.Stroke();
|
||||
}
|
||||
|
||||
private void DrawPill(Painter2D paint, Rect rect)
|
||||
{
|
||||
float r = Mathf.Min(cornerRadius, rect.width / 2f, rect.height / 2f);
|
||||
paint.MoveTo(new Vector2(r, 0));
|
||||
paint.LineTo(new Vector2(rect.width - r, 0));
|
||||
paint.ArcTo(new Vector2(rect.width, 0), new Vector2(rect.width, r), r);
|
||||
paint.LineTo(new Vector2(rect.width, rect.height - r));
|
||||
paint.ArcTo(new Vector2(rect.width, rect.height), new Vector2(rect.width - r, rect.height), r);
|
||||
paint.LineTo(new Vector2(r, rect.height));
|
||||
paint.ArcTo(new Vector2(0, rect.height), new Vector2(0, rect.height - r), r);
|
||||
paint.LineTo(new Vector2(0, r));
|
||||
paint.ArcTo(new Vector2(0, 0), new Vector2(r, 0), r);
|
||||
}
|
||||
|
||||
private void DrawPolygon(Painter2D paint, Rect rect, bool isStar)
|
||||
{
|
||||
Vector2 center = rect.center;
|
||||
float radius = Mathf.Min(rect.width, rect.height) / 2f;
|
||||
int totalPoints = isStar ? sides * 2 : sides;
|
||||
float angleStep = 360f / totalPoints;
|
||||
|
||||
for (int i = 0; i < totalPoints; i++)
|
||||
{
|
||||
float angle = (i * angleStep - 90f) * Mathf.Deg2Rad;
|
||||
float r = radius;
|
||||
if (isStar && i % 2 != 0) r *= inwardness;
|
||||
|
||||
Vector2 pos = center + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * r;
|
||||
if (i == 0) paint.MoveTo(pos);
|
||||
else paint.LineTo(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCustomPath(Painter2D paint)
|
||||
{
|
||||
if (string.IsNullOrEmpty(pathData)) return;
|
||||
string[] pairs = pathData.Split(' ');
|
||||
for (int i = 0; i < pairs.Length; i++)
|
||||
{
|
||||
string[] coords = pairs[i].Split(',');
|
||||
if (coords.Length == 2 && float.TryParse(coords[0], out float x) && float.TryParse(coords[1], out float y))
|
||||
{
|
||||
if (i == 0) paint.MoveTo(new Vector2(x, y));
|
||||
else paint.LineTo(new Vector2(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/Components/VectorShapeElement.cs.meta
Normal file
2
Assets/Scripts/UI/Components/VectorShapeElement.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b988e1760186e3f4d8c8041cbc6d64a2
|
||||
@@ -21,7 +21,7 @@ MonoBehaviour:
|
||||
m_ScaleMode: 1
|
||||
m_ReferenceSpritePixelsPerUnit: 100
|
||||
m_PixelsPerUnit: 100
|
||||
m_Scale: 1.8199999
|
||||
m_Scale: 1
|
||||
m_ReferenceDpi: 96
|
||||
m_FallbackDpi: 96
|
||||
m_ReferenceResolution: {x: 1200, y: 800}
|
||||
|
||||
9
Assets/UI/PauseMenu 1.uxml
Normal file
9
Assets/UI/PauseMenu 1.uxml
Normal file
@@ -0,0 +1,9 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
||||
<ui:VisualElement name="PauseMenuRoot" style="flex-grow: 1; align-items: center; justify-content: center; background-color: rgba(0, 0, 0, 0.7);">
|
||||
<ui:VisualElement name="PauseContainer" style="width: 400px; padding: 40px; background-color: rgba(20, 20, 20, 0.95); border-radius: 10px; border-width: 2px; border-color: rgb(0, 255, 204);">
|
||||
<ui:Label text="PAUSE" name="PauseTitle" style="font-size: 40px; color: rgb(0, 255, 204); -unity-font-style: bold; -unity-text-align: middle-center; margin-bottom: 30px;" />
|
||||
<ui:Button text="RESUME" name="ResumeBtn" class="button-spring" style="margin-bottom: 15px; height: 50px; font-size: 20px; color: white; background-color: rgba(255, 255, 255, 0.1); border-radius: 5px;" />
|
||||
<ui:Button text="LEAVE ROOM" name="QuitBtn" class="button-spring" style="height: 50px; font-size: 20px; color: rgb(255, 100, 100); background-color: rgba(255, 255, 255, 0.1); border-radius: 5px;" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
10
Assets/UI/PauseMenu 1.uxml.meta
Normal file
10
Assets/UI/PauseMenu 1.uxml.meta
Normal file
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01cf14ea38cdb3c4c8d8420124fbd479
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
Reference in New Issue
Block a user