EndlessTerrain.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. public class EndlessTerrain : MonoBehaviour
  5. {
  6. const float viewerMoveThresholdForChunkUpdate = 25f;
  7. const float sqrViewerMoveThresholdForChunkUpdate = viewerMoveThresholdForChunkUpdate * viewerMoveThresholdForChunkUpdate;
  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> terrainChunkDictionary = new Dictionary<Vector2, TerrainChunk>();
  18. static List<TerrainChunk> terrainChunksVisibleLastUpdate = new List<TerrainChunk>();
  19. 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. void Update()
  28. {
  29. viewerPosition = new Vector2(viewer.position.x, viewer.position.z) / mapGenerator.terrainData.uniformScale;
  30. if ((viewerPositionOld - viewerPosition).sqrMagnitude > sqrViewerMoveThresholdForChunkUpdate)
  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 Vector2(currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);
  50. if (terrainChunkDictionary.ContainsKey(viewedChunkCoord))
  51. {
  52. terrainChunkDictionary[viewedChunkCoord].UpdateTerrainChunk();
  53. }
  54. else
  55. {
  56. terrainChunkDictionary.Add(viewedChunkCoord, new TerrainChunk(viewedChunkCoord, chunkSize, detailLevels, transform, mapMaterial));
  57. }
  58. }
  59. }
  60. }
  61. public class TerrainChunk
  62. {
  63. GameObject meshObject;
  64. Vector2 position;
  65. Bounds bounds;
  66. MeshRenderer meshRenderer;
  67. MeshFilter meshFilter;
  68. MeshCollider meshCollider;
  69. LODInfo[] detailLevels;
  70. LODMesh[] lodMeshes;
  71. LODMesh collisionLODMesh;
  72. MapData mapData;
  73. bool mapDataReceived;
  74. int previousLODIndex = -1;
  75. public TerrainChunk(Vector2 coord, int size, LODInfo[] detailLevels, Transform parent, Material material)
  76. {
  77. this.detailLevels = detailLevels;
  78. position = coord * size;
  79. bounds = new Bounds(position, Vector2.one * size);
  80. Vector3 positionV3 = new Vector3(position.x, 0, position.y);
  81. meshObject = new GameObject("Terrain Chunk");
  82. meshRenderer = meshObject.AddComponent<MeshRenderer>();
  83. meshFilter = meshObject.AddComponent<MeshFilter>();
  84. meshCollider = meshObject.AddComponent<MeshCollider>();
  85. meshRenderer.material = material;
  86. meshObject.transform.position = positionV3 * mapGenerator.terrainData.uniformScale;
  87. meshObject.transform.parent = parent;
  88. meshObject.transform.localScale = Vector3.one * mapGenerator.terrainData.uniformScale;
  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. if (detailLevels[i].useForCollider)
  95. {
  96. collisionLODMesh = lodMeshes[i];
  97. }
  98. }
  99. mapGenerator.RequestMapData(position, OnMapDataReceived);
  100. }
  101. void OnMapDataReceived(MapData mapData)
  102. {
  103. this.mapData = mapData;
  104. mapDataReceived = true;
  105. UpdateTerrainChunk();
  106. }
  107. public void UpdateTerrainChunk()
  108. {
  109. if (mapDataReceived)
  110. {
  111. float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition));
  112. bool visible = viewerDstFromNearestEdge <= maxViewDst;
  113. if (visible)
  114. {
  115. int lodIndex = 0;
  116. for (int i = 0; i < detailLevels.Length - 1; i++)
  117. {
  118. if (viewerDstFromNearestEdge > detailLevels[i].visibleDstThreshold)
  119. {
  120. lodIndex = i + 1;
  121. }
  122. else
  123. {
  124. break;
  125. }
  126. }
  127. if (lodIndex != previousLODIndex)
  128. {
  129. LODMesh lodMesh = lodMeshes[lodIndex];
  130. if (lodMesh.hasMesh)
  131. {
  132. previousLODIndex = lodIndex;
  133. meshFilter.mesh = lodMesh.mesh;
  134. }
  135. else if (!lodMesh.hasRequestedMesh)
  136. {
  137. lodMesh.RequestMesh(mapData);
  138. }
  139. }
  140. if (lodIndex == 0)
  141. {
  142. if (collisionLODMesh.hasMesh)
  143. {
  144. meshCollider.sharedMesh = collisionLODMesh.mesh;
  145. }
  146. else if (!collisionLODMesh.hasRequestedMesh)
  147. {
  148. collisionLODMesh.RequestMesh(mapData);
  149. }
  150. terrainChunksVisibleLastUpdate.Add(this);
  151. }
  152. }
  153. SetVisible(visible);
  154. }
  155. }
  156. public void SetVisible(bool visible)
  157. {
  158. if (meshObject != null)
  159. {
  160. meshObject.SetActive(visible);
  161. }
  162. }
  163. public bool IsVisible()
  164. {
  165. return meshObject.activeSelf;
  166. }
  167. }
  168. class LODMesh
  169. {
  170. public Mesh mesh;
  171. public bool hasRequestedMesh;
  172. public bool hasMesh;
  173. int lod;
  174. System.Action updateCallback;
  175. public LODMesh(int lod, System.Action updateCallback)
  176. {
  177. this.lod = lod;
  178. this.updateCallback = updateCallback;
  179. }
  180. void OnMeshDataReceived(MeshData meshData)
  181. {
  182. mesh = meshData.CreateMesh();
  183. hasMesh = true;
  184. updateCallback();
  185. }
  186. public void RequestMesh(MapData mapData)
  187. {
  188. hasRequestedMesh = true;
  189. mapGenerator.RequestMeshData(mapData, lod, OnMeshDataReceived);
  190. }
  191. }
  192. [System.Serializable]
  193. public struct LODInfo
  194. {
  195. public int lod;
  196. public float visibleDstThreshold;
  197. public bool useForCollider;
  198. }
  199. }