using UnityEngine; using System.Collections; using System.Collections.Generic; using System.ComponentModel; public class EndlessTerrain : MonoBehaviour { const float scale = 5f; const float viewerMoveThresholdForChunkUpdate = 25f; const float sqrViewerMoveThresholdForChunkUpdate = viewerMoveThresholdForChunkUpdate * viewerMoveThresholdForChunkUpdate; public LODInfo[] detailLevels; public static float maxViewDist; public Transform viewer; public Material mapMaterial; public static Vector2 viewerPosition; Vector2 viewerPositionOld; static MapGenerator mapGenerator; int chunkSize; int chunksVisibleInViewDst; Dictionary terrainChunkDictionary = new Dictionary(); static List terrainChunksVisibleLastUpdate = new List(); private void Start() { mapGenerator = FindFirstObjectByType(); maxViewDist = detailLevels[detailLevels.Length - 1].visibleDstThreshold; chunkSize = MapGenerator.mapChunkSize - 1; chunksVisibleInViewDst = Mathf.RoundToInt(maxViewDist / chunkSize); UpdateVisibleChunks(); } private void Update() { viewerPosition = new Vector2(viewer.position.x, viewer.position.z) / scale; if((viewerPositionOld-viewerPosition).sqrMagnitude > sqrViewerMoveThresholdForChunkUpdate) { viewerPositionOld = viewerPosition; UpdateVisibleChunks(); } } void UpdateVisibleChunks() { for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++) { terrainChunksVisibleLastUpdate[i].SetVisible(false); } terrainChunksVisibleLastUpdate.Clear(); int currentChunkCoordX = Mathf.RoundToInt(viewerPosition.x / chunkSize); int currentChunkCoordY = Mathf.RoundToInt(viewerPosition.y / chunkSize); for (int yOffset = -chunksVisibleInViewDst; yOffset <= chunksVisibleInViewDst; yOffset++) { for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++) { Vector2 viewedChunkCoord = new Vector2(currentChunkCoordX + xOffset, currentChunkCoordY + yOffset); if (terrainChunkDictionary.ContainsKey(viewedChunkCoord)) { terrainChunkDictionary[viewedChunkCoord].UpdateTerrainChunk(); } else { terrainChunkDictionary.Add(viewedChunkCoord, new TerrainChunk(viewedChunkCoord, chunkSize, detailLevels, transform, mapMaterial)); } } } } public class TerrainChunk { GameObject meshObject; Vector2 position; Bounds bounds; MeshRenderer meshRenderer; MeshFilter meshFilter; MeshCollider meshCollider; LODInfo[] detailLevels; LODMesh[] lodMeshes; LODMesh collisionLODMesh; MapData mapData; bool mapDataReceived; int previousLODIndex = -1; public TerrainChunk(Vector2 coord, int size, LODInfo[] detailLevels, Transform parent, Material material) { this.detailLevels = detailLevels; position = coord * size; bounds = new Bounds(position, Vector2.one * size); Vector3 positionV3 = new Vector3(position.x, 0, position.y); meshObject = new GameObject("Terrain Chunk"); meshRenderer = meshObject.AddComponent(); meshFilter = meshObject.AddComponent(); meshRenderer.material = material; meshObject.transform.position = positionV3 * scale; meshObject.transform.parent = parent; meshObject.transform.localScale = Vector3.one * scale; meshCollider = meshObject.AddComponent(); SetVisible(false); lodMeshes = new LODMesh[detailLevels.Length]; for (int i = 0; i < detailLevels.Length; i++) { lodMeshes[i] = new LODMesh(detailLevels[i].lod, UpdateTerrainChunk); if (detailLevels[i].useForCollider) { collisionLODMesh = lodMeshes[i]; } } mapGenerator.RequestMapData(position,OnMapDataReceived); } void OnMapDataReceived(MapData mapData) { this.mapData = mapData; mapDataReceived = true; Texture2D texture = TextureGenerator.TextureFromColorMap(mapData.colorMap, MapGenerator.mapChunkSize, MapGenerator.mapChunkSize); meshRenderer.material.mainTexture = texture; UpdateTerrainChunk(); } public void UpdateTerrainChunk() { if (mapDataReceived) { float viewerDstFromNearestEdge = Mathf.Sqrt(bounds.SqrDistance(viewerPosition)); bool visible = viewerDstFromNearestEdge <= maxViewDist; if (visible) { int lodIndex = 0; for (int i = 0; i < detailLevels.Length - 1; i++) { if (viewerDstFromNearestEdge > detailLevels[i].visibleDstThreshold) { lodIndex = i + 1; } else { break; } } if (lodIndex != previousLODIndex) { LODMesh lodMesh = lodMeshes[lodIndex]; if (lodMesh.hasMesh) { previousLODIndex = lodIndex; meshFilter.mesh = lodMesh.mesh; } else if (!lodMesh.hasRequestedMesh) { lodMesh.RequestMesh(mapData); } } if(lodIndex == 0) { if (collisionLODMesh.hasMesh) { meshCollider.sharedMesh = collisionLODMesh.mesh; } else if (!collisionLODMesh.hasRequestedMesh) { collisionLODMesh.RequestMesh(mapData); } } terrainChunksVisibleLastUpdate.Add(this); } SetVisible(visible); } } public void SetVisible(bool visible) { meshObject.SetActive(visible); } public bool IsVisible() { return meshObject.activeSelf; } } class LODMesh { public Mesh mesh; public bool hasRequestedMesh; public bool hasMesh; int lod; System.Action updateCallback; public LODMesh(int lod, System.Action updateCallback) { this.lod = lod; this.updateCallback = updateCallback; } void OnMeshDataReceived(MeshData meshData) { mesh = meshData.CreateMesh(); hasMesh = true; updateCallback(); } public void RequestMesh(MapData mapData) { hasRequestedMesh = true; mapGenerator.RequestMeshData(mapData, lod, OnMeshDataReceived); } } [System.Serializable] public struct LODInfo { public int lod; public float visibleDstThreshold; public bool useForCollider; } }