EndlessTerrain.cs 7.7 KB

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