EndlessTerrain.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using Unity.VisualScripting;
  4. public class EndlessTerrain : MonoBehaviour
  5. {
  6. const float viewerMoveThreshHoldForChunkUpdate = 25f;
  7. const float sqrViewerMoveThreshHoldForChunkUpdate = viewerMoveThreshHoldForChunkUpdate * viewerMoveThreshHoldForChunkUpdate;
  8. public LODInfo[] detailLevels;
  9. public static float maxViewDst;
  10. public Transform viewer;
  11. public Material mapMaterial;
  12. public static Vector2 viewerPosition;
  13. Vector2 viewerPositionOld;
  14. static MapGenerator mapGenerator;
  15. int chunkSize;
  16. int chunksVisibleInViewDst;
  17. Dictionary<Vector2, TerrainChunk> terrainChunkDictionnary = new Dictionary<Vector2, TerrainChunk>();
  18. List<TerrainChunk> terrainChunksVisibleLastUpdate = new List<TerrainChunk>();
  19. private void Start()
  20. {
  21. mapGenerator = FindFirstObjectByType<MapGenerator>();
  22. maxViewDst = detailLevels[detailLevels.Length-1].visibleDstThreshold;
  23. chunkSize = MapGenerator.mapChunkSize - 1;
  24. chunksVisibleInViewDst = Mathf.RoundToInt(maxViewDst / chunkSize);
  25. UpdateVisibleChunks();
  26. }
  27. private void Update()
  28. {
  29. viewerPosition = new(viewer.position.x, viewer.position.z);
  30. if ((viewerPositionOld - viewerPosition).sqrMagnitude > sqrViewerMoveThreshHoldForChunkUpdate)
  31. {
  32. viewerPositionOld = viewerPosition;
  33. UpdateVisibleChunks();
  34. }
  35. }
  36. void UpdateVisibleChunks()
  37. {
  38. for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++)
  39. {
  40. terrainChunksVisibleLastUpdate[i].SetVisible(false);
  41. }
  42. terrainChunksVisibleLastUpdate.Clear();
  43. int currentChunkCoordX = Mathf.RoundToInt(viewerPosition.x / chunkSize);
  44. int currentChunkCoordY = Mathf.RoundToInt(viewerPosition.y / chunkSize);
  45. for (int yOffset = -chunksVisibleInViewDst; yOffset <=chunksVisibleInViewDst; yOffset++)
  46. {
  47. for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++)
  48. {
  49. Vector2 viewedChunkCoord = new(currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);
  50. if (terrainChunkDictionnary.ContainsKey(viewedChunkCoord))
  51. {
  52. terrainChunkDictionnary[viewedChunkCoord].UpdateTerrainChunk();
  53. if (terrainChunkDictionnary[viewedChunkCoord].IsVisible())
  54. {
  55. terrainChunksVisibleLastUpdate.Add(terrainChunkDictionnary[viewedChunkCoord]);
  56. }
  57. }
  58. else
  59. {
  60. terrainChunkDictionnary.Add(viewedChunkCoord, new TerrainChunk(viewedChunkCoord, chunkSize, detailLevels, transform, mapMaterial));
  61. }
  62. }
  63. }
  64. }
  65. public class TerrainChunk
  66. {
  67. GameObject meshObject;
  68. Vector2 position;
  69. Bounds bounds;
  70. MeshRenderer meshRenderer;
  71. MeshFilter meshFilter;
  72. LODInfo[] detailLevels;
  73. LODMesh[] lodMeshes;
  74. MapData mapData;
  75. bool mapDataReceived;
  76. int previousLODIndex = -1;
  77. public TerrainChunk(Vector2 coord, int size, LODInfo[] detailLevels, Transform parent, Material material)
  78. {
  79. this.detailLevels = detailLevels;
  80. position = coord * size;
  81. bounds = new Bounds(position, Vector2.one * size);
  82. Vector3 positionV3 = new(position.x, 0, position.y);
  83. meshObject = new GameObject("TerrainChunk");
  84. meshRenderer = meshObject.AddComponent<MeshRenderer>();
  85. meshFilter = meshObject.AddComponent<MeshFilter>();
  86. meshRenderer.material = material;
  87. meshObject.transform.position = positionV3;
  88. meshObject.transform.parent = parent;
  89. SetVisible(false);
  90. lodMeshes = new LODMesh[detailLevels.Length];
  91. for (int i = 0; i < detailLevels.Length; i++)
  92. {
  93. lodMeshes[i] = new LODMesh(detailLevels[i].lod, UpdateTerrainChunk);
  94. }
  95. mapGenerator.RequestMapData(position, OnMapDataReceived);
  96. }
  97. void OnMapDataReceived(MapData mapData)
  98. {
  99. this.mapData = mapData;
  100. mapDataReceived = true;
  101. Texture2D texture = TextureGenerator.TextureFromColourMap(mapData.colourMap, MapGenerator.mapChunkSize, MapGenerator.mapChunkSize);
  102. meshRenderer.material.mainTexture = texture;
  103. UpdateTerrainChunk();
  104. }
  105. public void UpdateTerrainChunk()
  106. {
  107. if (mapDataReceived)
  108. {
  109. float viewDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
  110. bool visible = viewDstFromNearestEdge <= maxViewDst;
  111. if (visible)
  112. {
  113. int lodIndex = 0;
  114. for (int i = 0; i < detailLevels.Length - 1; i++)
  115. {
  116. if (viewDstFromNearestEdge > detailLevels[i].visibleDstThreshold)
  117. {
  118. lodIndex = i + 1;
  119. }
  120. else
  121. {
  122. break;
  123. }
  124. }
  125. if (lodIndex != previousLODIndex)
  126. {
  127. LODMesh lodMesh = lodMeshes[lodIndex];
  128. if (lodMesh.hasMesh)
  129. {
  130. previousLODIndex = lodIndex;
  131. meshFilter.mesh = lodMesh.mesh;
  132. }
  133. else if (!lodMesh.hasRequestedMesh)
  134. {
  135. lodMesh.RequestMesh(mapData);
  136. }
  137. }
  138. }
  139. SetVisible(visible);
  140. }
  141. }
  142. public void SetVisible(bool visible)
  143. {
  144. meshObject.SetActive(visible);
  145. }
  146. public bool IsVisible()
  147. {
  148. return meshObject.activeSelf;
  149. }
  150. }
  151. class LODMesh
  152. {
  153. public Mesh mesh;
  154. public bool hasRequestedMesh;
  155. public bool hasMesh;
  156. int lod;
  157. System.Action updateCallback;
  158. public LODMesh(int lod, System.Action updateCallback)
  159. {
  160. this.lod = lod;
  161. this.updateCallback = updateCallback;
  162. }
  163. void OnMeshDataReceived(MeshData meshData)
  164. {
  165. mesh = meshData.CreateMesh();
  166. hasMesh = true;
  167. updateCallback();
  168. }
  169. public void RequestMesh(MapData mapData)
  170. {
  171. hasRequestedMesh = true;
  172. mapGenerator.RequestMeshData(mapData, lod, OnMeshDataReceived);
  173. }
  174. }
  175. [System.Serializable]
  176. public struct LODInfo
  177. {
  178. public int lod;
  179. public float visibleDstThreshold;
  180. }
  181. }