Skip to content

Instantly share code, notes, and snippets.

@bsimser
Last active November 15, 2021 15:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bsimser/5a07ca1cbc7634c5c566 to your computer and use it in GitHub Desktop.
Save bsimser/5a07ca1cbc7634c5c566 to your computer and use it in GitHub Desktop.
Simple endless terrain for Unity
///////////////////////////////////////////////////////////////////////////////
//
// This code is licensed under MIT license.
//
// Copyright © 2014 Bil Simser, https://weblogs.aspnet/bsimser <bsimser@shaw.ca>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
///////////////////////////////////////////////////////////////////////////////
using UnityEngine;
using System.Collections;
using Utility;
public class TerrainManager : MonoBehaviour {
public GameObject playerGameObject;
public Terrain referenceTerrain;
public int TERRAIN_BUFFER_COUNT = 50;
public int spread = 1;
private int[] currentTerrainID;
private Terrain[] terrainBuffer;
private DoubleKeyDictionary<int, int, int> terrainUsage;
private DoubleKeyDictionary<int, int, TerrainData> terrainUsageData;
private BitArray usedTiles;
private BitArray touchedTiles;
private Vector3 referencePosition;
private Vector2 referenceSize;
private Quaternion referenceRotation;
// Use this for initialization
void Start () {
currentTerrainID = new int[2];
terrainBuffer = new Terrain[TERRAIN_BUFFER_COUNT];
terrainUsage = new DoubleKeyDictionary<int, int, int>();
terrainUsageData = new DoubleKeyDictionary<int, int, TerrainData>();
usedTiles = new BitArray(TERRAIN_BUFFER_COUNT, false);
touchedTiles = new BitArray(TERRAIN_BUFFER_COUNT, false);
referencePosition = referenceTerrain.transform.position;
referenceRotation = referenceTerrain.transform.rotation;
referenceSize = new Vector2(referenceTerrain.terrainData.size.x, referenceTerrain.terrainData.size.z);
for(int i=0; i<TERRAIN_BUFFER_COUNT; i++)
{
TerrainData tData = new TerrainData();
CopyTerrainDataFromTo(referenceTerrain.terrainData, ref tData);
terrainBuffer[i] = Terrain.CreateTerrainGameObject(tData).GetComponent<Terrain>();
terrainBuffer[i].gameObject.active = false;
}
}
// Update is called once per frame
void Update () {
ResetTouch();
Vector3 warpPosition = playerGameObject.transform.position;
TerrainIDFromPosition(ref currentTerrainID, ref warpPosition);
string dbgString = "";
dbgString = "CurrentID : " + currentTerrainID[0] + ", " + currentTerrainID[1] + "\n\n";
for(int i=-spread;i<=spread;i++)
{
for(int j=-spread;j<=spread;j++)
{
DropTerrainAt(currentTerrainID[0] + i, currentTerrainID[1] + j);
dbgString += (currentTerrainID[0] + i) + "," + (currentTerrainID[1] + j) + "\n";
}
}
Debug.Log(dbgString);
ReclaimTiles();
}
void TerrainIDFromPosition(ref int[] currentTerrainID, ref Vector3 position)
{
currentTerrainID[0] = Mathf.RoundToInt((position.x - referencePosition.x )/ referenceSize.x);
currentTerrainID[1] = Mathf.RoundToInt((position.z - referencePosition.z )/ referenceSize.y);
}
void DropTerrainAt(int i, int j)
{
// Check if terrain exists, if it does, activate it.
if(terrainUsage.ContainsKey(i, j) && terrainUsage[i,j] != -1)
{
// Tile mapped, use it.
}
// If terrain doesn't exist, drop it.
else
{
terrainUsage[i,j] = FindNextAvailableTerrainID();
if(terrainUsage[i,j] == -1) Debug.LogError("No more tiles, failing...");
}
if(terrainUsageData.ContainsKey(i,j))
{
// Restore the data for this tile
}
else
{
// Create a new data object
terrainUsageData[i,j] = CreateNewTerrainData();
}
ActivateUsedTile(i, j);
usedTiles[terrainUsage[i,j]] = true;
touchedTiles[terrainUsage[i,j]] = true;
}
TerrainData CreateNewTerrainData()
{
TerrainData tData = new TerrainData();
CopyTerrainDataFromTo(referenceTerrain.terrainData, ref tData);
return tData;
}
void ResetTouch()
{
touchedTiles.SetAll(false);
}
int CountOnes(BitArray arr)
{
int count = 0;
for(int i=0;i<arr.Length;i++)
{
if(arr[i])
count++;
}
return count;
}
void ReclaimTiles()
{
if(CountOnes(usedTiles) > ((spread*2 + 1)*(spread*2 + 1)))
{
for(int i=0;i<usedTiles.Length;i++)
{
if(usedTiles[i] && !touchedTiles[i])
{
usedTiles[i] = false;
terrainBuffer[i].gameObject.active = false;
}
}
}
}
void ActivateUsedTile(int i, int j)
{
terrainBuffer[terrainUsage[i, j]].gameObject.transform.position =
new Vector3( referencePosition.x + i * referenceSize.x,
referencePosition.y,
referencePosition.z + j * referenceSize.y);
terrainBuffer[terrainUsage[i, j]].gameObject.transform.rotation = referenceRotation;
terrainBuffer[terrainUsage[i, j]].gameObject.active = true;
terrainBuffer[terrainUsage[i, j]].terrainData = terrainUsageData[i, j];
}
int FindNextAvailableTerrainID()
{
for(int i=0;i<usedTiles.Length;i++)
if(!usedTiles[i]) return i;
return -1;
}
void CopyTerrainDataFromTo(TerrainData tDataFrom, ref TerrainData tDataTo)
{
tDataTo.SetDetailResolution(tDataFrom.detailResolution, 8);
tDataTo.heightmapResolution = tDataFrom.heightmapResolution;
tDataTo.alphamapResolution = tDataFrom.alphamapResolution;
tDataTo.baseMapResolution = tDataFrom.baseMapResolution;
tDataTo.size = tDataFrom.size;
tDataTo.splatPrototypes = tDataFrom.splatPrototypes;
}
}
@MichaelReh
Copy link

Hey Bil, this is a nice and simple endless terrain. I'd like to use and modify it for my current project. Could you please add a license, for example MIT? Simply add a line to the code stating "This code is licensed under the terms of the MIT license". Thanks!

@bsimser
Copy link
Author

bsimser commented Nov 15, 2021

Hey Bil, this is a nice and simple endless terrain. I'd like to use and modify it for my current project. Could you please add a license, for example MIT? Simply add a line to the code stating "This code is licensed under the terms of the MIT license". Thanks!

Done. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment