EndlessTerrain.cs 7.6 KB

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