MeshMazeRenderer.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. /// <summary>
  4. /// Generates 3D meshes for maze walls and floors instead of using tilemaps
  5. /// Much more efficient for large mazes and allows for better performance
  6. /// </summary>
  7. public class MeshMazeRenderer : MonoBehaviour
  8. {
  9. [Header("Maze Setup")]
  10. [SerializeField] private MazeController mazeController;
  11. [Header("Materials")]
  12. [SerializeField] private Material floorMaterial;
  13. [SerializeField] private Material wallMaterial;
  14. [Header("Start/End Markers")]
  15. [SerializeField] private GameObject startMarkerPrefab;
  16. [SerializeField] private GameObject exitMarkerPrefab;
  17. [SerializeField] private Color startMarkerColor = new Color(0f, 1f, 0f, 0.65f);
  18. [SerializeField] private Color exitMarkerColor = new Color(1f, 0f, 0f, 0.65f);
  19. [SerializeField] private float markerSize = 3f;
  20. [SerializeField] private bool showStartEndRoomHighlights = true;
  21. [SerializeField] private Color startRoomHighlightColor = new Color(0f, 1f, 0f, 0.25f);
  22. [SerializeField] private Color exitRoomHighlightColor = new Color(1f, 0f, 0f, 0.25f);
  23. [Header("Fog of War")]
  24. [SerializeField] private bool showFogOfWar = true;
  25. [SerializeField] private MazeFogOfWar fogOfWar;
  26. [SerializeField] private Color fogColor = new Color(0f, 0f, 0f, 0.6f);
  27. [Header("Mesh Settings")]
  28. [SerializeField] private float wallHeight = 2f;
  29. [SerializeField] private float tileSize = 1f;
  30. [SerializeField] private bool generateFloor = true;
  31. [SerializeField] private bool generateWalls = true;
  32. [SerializeField] private bool twoSidedWalls = true;
  33. [SerializeField] private bool wallCaps = true;
  34. [Header("Optimization")]
  35. [SerializeField] private int chunkSize = 32;
  36. [SerializeField] private int renderDistance = 2;
  37. private Dictionary<Vector2Int, GameObject> chunkObjects = new();
  38. private Transform cameraTransform;
  39. private GameObject markerContainer;
  40. private GameObject fogOfWarContainer;
  41. private bool lastFogVisible;
  42. void OnEnable()
  43. {
  44. if (mazeController == null)
  45. {
  46. mazeController = GetComponent<MazeController>();
  47. }
  48. }
  49. void Start()
  50. {
  51. cameraTransform = Camera.main?.transform;
  52. lastFogVisible = showFogOfWar;
  53. if (mazeController != null && mazeController.GetCurrentMaze() != null)
  54. {
  55. GenerateMazeMesh();
  56. }
  57. }
  58. void Update()
  59. {
  60. if (cameraTransform != null)
  61. {
  62. UpdateVisibleChunks();
  63. }
  64. if (showFogOfWar != lastFogVisible)
  65. {
  66. UpdateFogVisibility();
  67. }
  68. else if (fogOfWarContainer == null && showFogOfWar && fogOfWar != null)
  69. {
  70. UpdateFogVisibility();
  71. }
  72. }
  73. /// <summary>
  74. /// Generates the complete maze mesh
  75. /// </summary>
  76. public void GenerateMazeMesh()
  77. {
  78. if (mazeController == null)
  79. {
  80. mazeController = GetComponent<MazeController>();
  81. }
  82. if (mazeController == null)
  83. {
  84. Debug.LogError("MeshMazeRenderer requires a MazeController reference.");
  85. return;
  86. }
  87. if (fogOfWar == null)
  88. {
  89. fogOfWar = GetComponent<MazeFogOfWar>() ?? FindFirstObjectByType<MazeFogOfWar>();
  90. }
  91. ClearChunks();
  92. ClearMarkers();
  93. ClearFogOfWar();
  94. var maze = mazeController.GetCurrentMaze();
  95. if (maze == null)
  96. {
  97. Debug.LogWarning("No maze to render yet. Generate the maze first, then refresh the mesh.");
  98. return;
  99. }
  100. Debug.Log($"MeshMazeRenderer: rendering maze {maze.Width}x{maze.Height} with {maze.Rooms.Count} rooms");
  101. // For very large mazes, use chunked generation
  102. if (maze.Width > chunkSize || maze.Height > chunkSize)
  103. {
  104. GenerateChunkedMesh(maze);
  105. }
  106. else
  107. {
  108. GenerateSingleMesh(maze);
  109. }
  110. RenderStartExitMarkers(maze);
  111. if (showFogOfWar && fogOfWar != null)
  112. {
  113. RenderFogOfWar(maze);
  114. }
  115. }
  116. /// <summary>
  117. /// Generates a single mesh for smaller mazes
  118. /// </summary>
  119. private void GenerateSingleMesh(MazeData maze)
  120. {
  121. GameObject chunkObj = new GameObject("MazeMesh");
  122. chunkObj.transform.parent = transform;
  123. chunkObj.transform.localPosition = Vector3.zero;
  124. MeshFilter meshFilter = chunkObj.AddComponent<MeshFilter>();
  125. MeshRenderer meshRenderer = chunkObj.AddComponent<MeshRenderer>();
  126. Mesh mesh = CreateMazeMesh(maze, 0, 0, maze.Width, maze.Height);
  127. meshFilter.mesh = mesh;
  128. // Assign materials
  129. Material[] materials = new Material[generateFloor && generateWalls ? 2 : 1];
  130. int matIndex = 0;
  131. if (generateFloor) materials[matIndex++] = floorMaterial;
  132. if (generateWalls) materials[matIndex++] = wallMaterial;
  133. meshRenderer.materials = materials;
  134. chunkObjects[new Vector2Int(0, 0)] = chunkObj;
  135. }
  136. /// <summary>
  137. /// Generates chunked meshes for large mazes
  138. /// </summary>
  139. private void GenerateChunkedMesh(MazeData maze)
  140. {
  141. int chunksX = Mathf.CeilToInt((float)maze.Width / chunkSize);
  142. int chunksY = Mathf.CeilToInt((float)maze.Height / chunkSize);
  143. for (int cx = 0; cx < chunksX; cx++)
  144. {
  145. for (int cy = 0; cy < chunksY; cy++)
  146. {
  147. Vector2Int chunkPos = new Vector2Int(cx, cy);
  148. CreateChunkMesh(maze, chunkPos);
  149. }
  150. }
  151. }
  152. /// <summary>
  153. /// Creates a mesh for a single chunk
  154. /// </summary>
  155. private void CreateChunkMesh(MazeData maze, Vector2Int chunkPos)
  156. {
  157. int startX = chunkPos.x * chunkSize;
  158. int startY = chunkPos.y * chunkSize;
  159. int endX = Mathf.Min(startX + chunkSize, maze.Width);
  160. int endY = Mathf.Min(startY + chunkSize, maze.Height);
  161. GameObject chunkObj = new GameObject($"Chunk_{chunkPos.x}_{chunkPos.y}");
  162. chunkObj.transform.parent = transform;
  163. chunkObj.transform.localPosition = new Vector3(startX * tileSize, 0, startY * tileSize);
  164. MeshFilter meshFilter = chunkObj.AddComponent<MeshFilter>();
  165. MeshRenderer meshRenderer = chunkObj.AddComponent<MeshRenderer>();
  166. Mesh mesh = CreateMazeMesh(maze, startX, startY, endX - startX, endY - startY);
  167. meshFilter.mesh = mesh;
  168. // Assign materials
  169. Material[] materials = new Material[generateFloor && generateWalls ? 2 : 1];
  170. int matIndex = 0;
  171. if (generateFloor) materials[matIndex++] = floorMaterial;
  172. if (generateWalls) materials[matIndex++] = wallMaterial;
  173. meshRenderer.materials = materials;
  174. chunkObjects[chunkPos] = chunkObj;
  175. }
  176. private void RenderStartExitMarkers(MazeData maze)
  177. {
  178. if (markerContainer != null)
  179. {
  180. Destroy(markerContainer);
  181. }
  182. markerContainer = new GameObject("MazeMarkers");
  183. markerContainer.transform.parent = transform;
  184. markerContainer.transform.localPosition = Vector3.zero;
  185. markerContainer.transform.localRotation = Quaternion.identity;
  186. foreach (var start in maze.StartPoints)
  187. {
  188. CreateMarker(maze, start, startMarkerPrefab, startMarkerColor, "StartMarker", startRoomHighlightColor);
  189. }
  190. foreach (var exit in maze.ExitPoints)
  191. {
  192. CreateMarker(maze, exit, exitMarkerPrefab, exitMarkerColor, "ExitMarker", exitRoomHighlightColor);
  193. }
  194. }
  195. private void CreateMarker(MazeData maze, Vector2Int tilePos, GameObject prefab, Color color, string namePrefix, Color roomHighlight)
  196. {
  197. Vector3 markerPos = new Vector3(tilePos.x * tileSize + tileSize * 0.5f, 0.1f, tilePos.y * tileSize + tileSize * 0.5f);
  198. GameObject marker = prefab != null ? Instantiate(prefab, markerContainer.transform) : null;
  199. if (marker == null)
  200. {
  201. marker = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
  202. marker.transform.parent = markerContainer.transform;
  203. marker.transform.localPosition = markerPos;
  204. marker.transform.localScale = new Vector3(markerSize, markerSize * 0.2f, markerSize);
  205. DestroyImmediate(marker.GetComponent<Collider>());
  206. var renderer = marker.GetComponent<MeshRenderer>();
  207. renderer.material = CreateURPUnlitColorMaterial(color, true);
  208. renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  209. renderer.receiveShadows = false;
  210. }
  211. else
  212. {
  213. marker.transform.parent = markerContainer.transform;
  214. marker.transform.localPosition = markerPos;
  215. marker.transform.localRotation = Quaternion.identity;
  216. marker.transform.localScale = Vector3.one * markerSize;
  217. var renderer = marker.GetComponent<MeshRenderer>();
  218. if (renderer != null)
  219. {
  220. renderer.material = CreateURPUnlitColorMaterial(color, true);
  221. renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  222. renderer.receiveShadows = false;
  223. }
  224. }
  225. marker.name = namePrefix + "_" + tilePos.x + "_" + tilePos.y;
  226. if (showStartEndRoomHighlights)
  227. {
  228. var room = maze.GetRoomAtTile(tilePos.x, tilePos.y);
  229. if (room != null)
  230. {
  231. CreateRoomHighlight(room, roomHighlight);
  232. }
  233. }
  234. }
  235. private void CreateRoomHighlight(MazeRoom room, Color highlightColor)
  236. {
  237. if (room == null) return;
  238. GameObject highlight = new GameObject("RoomHighlight_" + room.Id);
  239. highlight.transform.parent = markerContainer.transform;
  240. highlight.transform.localPosition = Vector3.zero;
  241. var meshFilter = highlight.AddComponent<MeshFilter>();
  242. var meshRenderer = highlight.AddComponent<MeshRenderer>();
  243. meshRenderer.material = CreateURPUnlitColorMaterial(highlightColor, true);
  244. meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  245. meshRenderer.receiveShadows = false;
  246. Mesh mesh = new Mesh();
  247. float minX = room.MinX * tileSize;
  248. float maxX = (room.MaxX + 1) * tileSize;
  249. float minZ = room.MinY * tileSize;
  250. float maxZ = (room.MaxY + 1) * tileSize;
  251. float y = 0.15f;
  252. mesh.vertices = new Vector3[] {
  253. new Vector3(minX, y, minZ),
  254. new Vector3(maxX, y, minZ),
  255. new Vector3(maxX, y, maxZ),
  256. new Vector3(minX, y, maxZ)
  257. };
  258. mesh.uv = new Vector2[] {
  259. new Vector2(0,0),
  260. new Vector2(1,0),
  261. new Vector2(1,1),
  262. new Vector2(0,1)
  263. };
  264. mesh.triangles = new int[] { 0, 1, 2, 0, 2, 3 };
  265. mesh.RecalculateNormals();
  266. meshFilter.mesh = mesh;
  267. }
  268. private void ClearMarkers()
  269. {
  270. if (markerContainer != null)
  271. {
  272. Destroy(markerContainer);
  273. markerContainer = null;
  274. }
  275. }
  276. private void ClearFogOfWar()
  277. {
  278. if (fogOfWarContainer != null)
  279. {
  280. Destroy(fogOfWarContainer);
  281. fogOfWarContainer = null;
  282. }
  283. }
  284. /// <summary>
  285. /// Toggles fog of war visibility
  286. /// </summary>
  287. public void ToggleFogOfWar()
  288. {
  289. showFogOfWar = !showFogOfWar;
  290. UpdateFogVisibility();
  291. }
  292. /// <summary>
  293. /// Sets fog of war visibility
  294. /// </summary>
  295. public void SetFogOfWarVisible(bool visible)
  296. {
  297. showFogOfWar = visible;
  298. UpdateFogVisibility();
  299. }
  300. /// <summary>
  301. /// Refreshes the fog of war display (call this when entity vision changes)
  302. /// </summary>
  303. public void RefreshFogOfWar()
  304. {
  305. var maze = mazeController?.GetCurrentMaze();
  306. if (maze != null && showFogOfWar && fogOfWar != null)
  307. {
  308. ClearFogOfWar();
  309. RenderFogOfWar(maze);
  310. }
  311. }
  312. private void UpdateFogVisibility()
  313. {
  314. if (showFogOfWar && fogOfWar != null)
  315. {
  316. if (fogOfWarContainer == null)
  317. {
  318. var maze = mazeController?.GetCurrentMaze();
  319. if (maze != null)
  320. {
  321. RenderFogOfWar(maze);
  322. }
  323. }
  324. else if (!fogOfWarContainer.activeSelf)
  325. {
  326. fogOfWarContainer.SetActive(true);
  327. }
  328. }
  329. else if (fogOfWarContainer != null)
  330. {
  331. fogOfWarContainer.SetActive(false);
  332. }
  333. lastFogVisible = showFogOfWar;
  334. }
  335. /// <summary>
  336. /// Creates a URP-compatible unlit material for color overlays.
  337. /// </summary>
  338. private Material CreateURPUnlitColorMaterial(Color color, bool transparent)
  339. {
  340. Shader shader = Shader.Find("Universal Render Pipeline/Unlit");
  341. if (shader == null)
  342. {
  343. shader = Shader.Find("Unlit/Color");
  344. }
  345. Material material = new Material(shader);
  346. // Always set base color with alpha
  347. if (material.HasProperty("_BaseColor"))
  348. {
  349. material.SetColor("_BaseColor", color);
  350. }
  351. else if (material.HasProperty("_Color"))
  352. {
  353. material.SetColor("_Color", color);
  354. }
  355. else
  356. {
  357. material.color = color;
  358. }
  359. if (transparent)
  360. {
  361. // Enable alpha blending for transparency
  362. material.SetFloat("_AlphaClip", 0);
  363. if (material.HasProperty("_Surface"))
  364. {
  365. material.SetFloat("_Surface", 1f);
  366. }
  367. if (material.HasProperty("_Blend"))
  368. {
  369. material.SetFloat("_Blend", 0f);
  370. }
  371. if (material.HasProperty("_Cull"))
  372. {
  373. material.SetFloat("_Cull", 0f);
  374. }
  375. material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
  376. material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
  377. material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
  378. material.SetInt("_ZWrite", 0);
  379. material.EnableKeyword("_ALPHABLEND_ON");
  380. }
  381. else if (material.HasProperty("_Cull"))
  382. {
  383. material.SetFloat("_Cull", 0f);
  384. }
  385. return material;
  386. }
  387. /// <summary>
  388. /// Renders the fog of war overlay
  389. /// </summary>
  390. private void RenderFogOfWar(MazeData maze)
  391. {
  392. if (fogOfWarContainer != null)
  393. {
  394. Destroy(fogOfWarContainer);
  395. }
  396. fogOfWarContainer = new GameObject("FogOfWar");
  397. fogOfWarContainer.transform.parent = transform;
  398. fogOfWarContainer.transform.localPosition = Vector3.zero;
  399. fogOfWarContainer.transform.localRotation = Quaternion.identity;
  400. fogOfWarContainer.SetActive(showFogOfWar);
  401. var fogMesh = new Mesh();
  402. var vertices = new List<Vector3>();
  403. var uvs = new List<Vector2>();
  404. var triangles = new List<int>();
  405. Material fogMaterial = CreateURPUnlitColorMaterial(fogColor, true);
  406. HashSet<Vector2Int> exploredTiles = fogOfWar.GetExploredTiles();
  407. int vertexIndex = 0;
  408. for (int x = 0; x < maze.Width; x++)
  409. {
  410. for (int y = 0; y < maze.Height; y++)
  411. {
  412. Vector2Int tilePos = new Vector2Int(x, y);
  413. if (!exploredTiles.Contains(tilePos))
  414. {
  415. float posX = x * tileSize;
  416. float posZ = y * tileSize;
  417. vertices.Add(new Vector3(posX, 0.08f, posZ));
  418. vertices.Add(new Vector3(posX + tileSize, 0.08f, posZ));
  419. vertices.Add(new Vector3(posX + tileSize, 0.08f, posZ + tileSize));
  420. vertices.Add(new Vector3(posX, 0.08f, posZ + tileSize));
  421. uvs.Add(new Vector2(0, 0));
  422. uvs.Add(new Vector2(1, 0));
  423. uvs.Add(new Vector2(1, 1));
  424. uvs.Add(new Vector2(0, 1));
  425. triangles.Add(vertexIndex);
  426. triangles.Add(vertexIndex + 1);
  427. triangles.Add(vertexIndex + 2);
  428. triangles.Add(vertexIndex);
  429. triangles.Add(vertexIndex + 2);
  430. triangles.Add(vertexIndex + 3);
  431. vertexIndex += 4;
  432. }
  433. }
  434. }
  435. fogMesh.SetVertices(vertices);
  436. fogMesh.SetUVs(0, uvs);
  437. fogMesh.SetTriangles(triangles, 0);
  438. fogMesh.RecalculateNormals();
  439. fogMesh.RecalculateBounds();
  440. GameObject fogOverlay = new GameObject("FogOverlay");
  441. fogOverlay.transform.parent = fogOfWarContainer.transform;
  442. fogOverlay.transform.localPosition = Vector3.zero;
  443. fogOverlay.transform.localRotation = Quaternion.identity;
  444. var fogFilter = fogOverlay.AddComponent<MeshFilter>();
  445. var fogRenderer = fogOverlay.AddComponent<MeshRenderer>();
  446. fogFilter.mesh = fogMesh;
  447. fogRenderer.material = fogMaterial;
  448. fogRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
  449. fogRenderer.receiveShadows = false;
  450. }
  451. /// <summary>
  452. /// Creates the actual mesh data for a maze section
  453. /// </summary>
  454. private Mesh CreateMazeMesh(MazeData maze, int offsetX, int offsetY, int width, int height)
  455. {
  456. Mesh mesh = new Mesh();
  457. List<Vector3> vertices = new List<Vector3>();
  458. List<Vector2> uvs = new List<Vector2>();
  459. List<int> floorTriangles = new List<int>();
  460. List<int> wallTriangles = new List<int>();
  461. // Generate floor and walls
  462. for (int x = 0; x < width; x++)
  463. {
  464. for (int y = 0; y < height; y++)
  465. {
  466. int worldX = offsetX + x;
  467. int worldY = offsetY + y;
  468. var tile = maze.GetTile(worldX, worldY);
  469. if (tile == null) continue;
  470. float posX = x * tileSize;
  471. float posZ = y * tileSize;
  472. if (generateFloor && tile.IsWalkable())
  473. {
  474. // Floor quad
  475. int vertStart = vertices.Count;
  476. vertices.Add(new Vector3(posX, 0, posZ));
  477. vertices.Add(new Vector3(posX + tileSize, 0, posZ));
  478. vertices.Add(new Vector3(posX + tileSize, 0, posZ + tileSize));
  479. vertices.Add(new Vector3(posX, 0, posZ + tileSize));
  480. // UVs for floor
  481. uvs.Add(new Vector2(0, 0));
  482. uvs.Add(new Vector2(1, 0));
  483. uvs.Add(new Vector2(1, 1));
  484. uvs.Add(new Vector2(0, 1));
  485. // Floor triangles
  486. floorTriangles.Add(vertStart);
  487. floorTriangles.Add(vertStart + 1);
  488. floorTriangles.Add(vertStart + 2);
  489. floorTriangles.Add(vertStart);
  490. floorTriangles.Add(vertStart + 2);
  491. floorTriangles.Add(vertStart + 3);
  492. }
  493. if (generateWalls && tile.Type == MazeTile.TileType.Wall)
  494. {
  495. // Wall quads for each side that's exposed
  496. CreateWallQuads(vertices, uvs, wallTriangles, posX, posZ, tileSize, wallHeight,
  497. maze, worldX, worldY);
  498. // Add a top cap so walls remain visible in direct top-down view
  499. if (wallCaps)
  500. {
  501. CreateWallTop(vertices, uvs, wallTriangles, posX, posZ, tileSize, wallHeight);
  502. }
  503. }
  504. }
  505. }
  506. mesh.vertices = vertices.ToArray();
  507. mesh.uv = uvs.ToArray();
  508. // Combine submeshes
  509. mesh.subMeshCount = (generateFloor ? 1 : 0) + (generateWalls ? 1 : 0);
  510. int submeshIndex = 0;
  511. if (generateFloor)
  512. {
  513. mesh.SetTriangles(floorTriangles.ToArray(), submeshIndex++);
  514. }
  515. if (generateWalls)
  516. {
  517. mesh.SetTriangles(wallTriangles.ToArray(), submeshIndex);
  518. }
  519. mesh.RecalculateNormals();
  520. mesh.RecalculateBounds();
  521. return mesh;
  522. }
  523. /// <summary>
  524. /// Creates wall quads for exposed sides
  525. /// </summary>
  526. private void CreateWallQuads(List<Vector3> vertices, List<Vector2> uvs, List<int> triangles,
  527. float posX, float posZ, float size, float height,
  528. MazeData maze, int worldX, int worldY)
  529. {
  530. // Check each direction for exposed walls
  531. Vector2Int[] directions = {
  532. new Vector2Int(0, 1), // North
  533. new Vector2Int(1, 0), // East
  534. new Vector2Int(0, -1), // South
  535. new Vector2Int(-1, 0) // West
  536. };
  537. foreach (var dir in directions)
  538. {
  539. int checkX = worldX + dir.x;
  540. int checkY = worldY + dir.y;
  541. // If neighbor is not a wall or is out of bounds, create wall face
  542. if (!maze.IsInBounds(checkX, checkY) || maze.GetTile(checkX, checkY).Type != MazeTile.TileType.Wall)
  543. {
  544. CreateWallQuad(vertices, uvs, triangles, posX, posZ, size, height, dir);
  545. }
  546. }
  547. }
  548. /// <summary>
  549. /// Creates a single wall quad
  550. /// </summary>
  551. private void CreateWallQuad(List<Vector3> vertices, List<Vector2> uvs, List<int> triangles,
  552. float posX, float posZ, float size, float height, Vector2Int direction)
  553. {
  554. int vertStart = vertices.Count;
  555. // Determine quad vertices based on direction
  556. Vector3 v0, v1, v2, v3;
  557. if (direction == Vector2Int.up) // North face
  558. {
  559. v0 = new Vector3(posX, 0, posZ + size);
  560. v1 = new Vector3(posX + size, 0, posZ + size);
  561. v2 = new Vector3(posX + size, height, posZ + size);
  562. v3 = new Vector3(posX, height, posZ + size);
  563. }
  564. else if (direction == Vector2Int.right) // East face
  565. {
  566. v0 = new Vector3(posX + size, 0, posZ);
  567. v1 = new Vector3(posX + size, 0, posZ + size);
  568. v2 = new Vector3(posX + size, height, posZ + size);
  569. v3 = new Vector3(posX + size, height, posZ);
  570. }
  571. else if (direction == Vector2Int.down) // South face
  572. {
  573. v0 = new Vector3(posX + size, 0, posZ);
  574. v1 = new Vector3(posX, 0, posZ);
  575. v2 = new Vector3(posX, height, posZ);
  576. v3 = new Vector3(posX + size, height, posZ);
  577. }
  578. else // West face
  579. {
  580. v0 = new Vector3(posX, 0, posZ + size);
  581. v1 = new Vector3(posX, 0, posZ);
  582. v2 = new Vector3(posX, height, posZ);
  583. v3 = new Vector3(posX, height, posZ + size);
  584. }
  585. vertices.Add(v0);
  586. vertices.Add(v1);
  587. vertices.Add(v2);
  588. vertices.Add(v3);
  589. // UVs
  590. uvs.Add(new Vector2(0, 0));
  591. uvs.Add(new Vector2(1, 0));
  592. uvs.Add(new Vector2(1, 1));
  593. uvs.Add(new Vector2(0, 1));
  594. // Front-facing triangles
  595. triangles.Add(vertStart);
  596. triangles.Add(vertStart + 1);
  597. triangles.Add(vertStart + 2);
  598. triangles.Add(vertStart);
  599. triangles.Add(vertStart + 2);
  600. triangles.Add(vertStart + 3);
  601. // Optionally duplicate the face for the opposite side
  602. if (twoSidedWalls)
  603. {
  604. triangles.Add(vertStart + 2);
  605. triangles.Add(vertStart + 1);
  606. triangles.Add(vertStart);
  607. triangles.Add(vertStart + 3);
  608. triangles.Add(vertStart + 2);
  609. triangles.Add(vertStart + 0);
  610. }
  611. }
  612. /// <summary>
  613. /// Creates a top cap for a wall tile so it remains visible from above
  614. /// </summary>
  615. private void CreateWallTop(List<Vector3> vertices, List<Vector2> uvs, List<int> triangles,
  616. float posX, float posZ, float size, float height)
  617. {
  618. int vertStart = vertices.Count;
  619. vertices.Add(new Vector3(posX, height, posZ));
  620. vertices.Add(new Vector3(posX + size, height, posZ));
  621. vertices.Add(new Vector3(posX + size, height, posZ + size));
  622. vertices.Add(new Vector3(posX, height, posZ + size));
  623. uvs.Add(new Vector2(0, 0));
  624. uvs.Add(new Vector2(1, 0));
  625. uvs.Add(new Vector2(1, 1));
  626. uvs.Add(new Vector2(0, 1));
  627. triangles.Add(vertStart);
  628. triangles.Add(vertStart + 1);
  629. triangles.Add(vertStart + 2);
  630. triangles.Add(vertStart);
  631. triangles.Add(vertStart + 2);
  632. triangles.Add(vertStart + 3);
  633. if (twoSidedWalls)
  634. {
  635. triangles.Add(vertStart + 2);
  636. triangles.Add(vertStart + 1);
  637. triangles.Add(vertStart);
  638. triangles.Add(vertStart + 3);
  639. triangles.Add(vertStart + 2);
  640. triangles.Add(vertStart + 0);
  641. }
  642. }
  643. /// <summary>
  644. /// Updates which chunks are visible
  645. /// </summary>
  646. private void UpdateVisibleChunks()
  647. {
  648. if (chunkObjects.Count <= 1) return; // No chunking used
  649. Vector2Int cameraChunk = WorldToChunk(cameraTransform.position);
  650. foreach (var chunk in chunkObjects)
  651. {
  652. Vector2Int chunkPos = chunk.Key;
  653. bool isVisible = Mathf.Abs(chunkPos.x - cameraChunk.x) <= renderDistance &&
  654. Mathf.Abs(chunkPos.y - cameraChunk.y) <= renderDistance;
  655. chunk.Value.SetActive(isVisible);
  656. }
  657. }
  658. /// <summary>
  659. /// Converts world position to chunk coordinates
  660. /// </summary>
  661. private Vector2Int WorldToChunk(Vector3 worldPos)
  662. {
  663. return new Vector2Int(
  664. Mathf.FloorToInt(worldPos.x / (chunkSize * tileSize)),
  665. Mathf.FloorToInt(worldPos.z / (chunkSize * tileSize))
  666. );
  667. }
  668. /// <summary>
  669. /// Clears all chunk objects
  670. /// </summary>
  671. private void ClearChunks()
  672. {
  673. foreach (var chunk in chunkObjects.Values)
  674. {
  675. Destroy(chunk);
  676. }
  677. chunkObjects.Clear();
  678. }
  679. /// <summary>
  680. /// Regenerates the mesh
  681. /// </summary>
  682. public void RefreshMesh()
  683. {
  684. GenerateMazeMesh();
  685. }
  686. }