- Home /
Dynamic Terrain Loading
I have a lot of Questions about Infinite Terrains and Terrain.SetNeighbors().
Here is a list of the questions:
1 If you use Terrain.SetNeighbors() do all the Neighboring Terrains have to be creared at the same resolution?
2 If I'm storing the extra Terrains as Assets Or separate scene files to Additve load, will the Terrain.SetNeighbors() function throw out a bunch of errors because the extra terrains are not in the Current scene?
3 Should the terrains be made into Prefabs?
4 Is there any major Benifit or drawback to making them prefabs?
5 Is there currently any way to Paint multiple terrains in Unity at the same time?
6 Is there already a system like UniLOD for terrains?
7 Is there any Hands on tutorials for infinite worlds?
What I imagine An Infinite Terrain system to be :
Like the old arcade games in the 80's if the player travled to far to the left of world/screen his location would be moved to the far right of the screen. And the same with the top or bottom.
Then the terrain would be a grid such as a chess board where the ring of terrains around the player would always be loaded.
Terrains could check "distance from player" and unload/destoy themselves when no longer needed.
Not sure how efficient this system would be with all the loading and unloading.
So I'm wondering if this is the right direction to go or if someone has already made a better system.
Answer by jonas-echterhoff · May 13, 2010 at 05:34 PM
Check out this example. It will dynamically load und unload neighboring terrains as the player moves around the world, and should answer most of your questions.
I tried to play this on my browser and the Unity player says made with Beta can only play on your machine.
I haven't updated yet to get the Beta. So I tried to rebuild on my version of Unity but got this error:
Assets/Plugins/ZoneLoader.cs(130,17): error CS0246: The type or namespace name `AsynchronousOperation' could not be found. Are you missing a using directive or an assembly reference?
I will go through your code though to see what I can learn from it Thanks. :)
Got it going :) It's fast and tells me a lot. Thank You
Answer by SoloChristian · Oct 12, 2010 at 07:18 PM
using UnityEngine; using System.Collections; using System;
using Object = UnityEngine.Object;
public class ZoneLoader : MonoBehaviour { const float m_LoadDistance = 1500.0F; const float m_UnloadDistance = 2500.0F;
//EDDIE CHANGE TO FIT NEW TERRAIN SIZE
public const float m_GridSize = 8046.0F;
public const int m_ZoneCount = 12;
static public ZoneLoader ms_Singleton;
static ZoneLoader singleton
{
get
{
// @TODO: use FindObjectsOfType to implement improved hotloading of scripts
if (ms_Singleton == null)
{
GameObject go = new GameObject ("ZoneLoader", typeof(ZoneLoader));
ms_Singleton = go.GetComponent(typeof(ZoneLoader)) as ZoneLoader;
}
return ms_Singleton;
}
}
bool m_IsLoading = false;
bool m_IsUnloading = false;
bool m_UseWWW = true;
bool m_UseWWWCaching = false;
int m_CacheVersion = 1;
Vector3 m_PlayerPosition;
Transform m_PlayerTransform;
static int m_ZonesUnloadedSinceLastAssetUnload = 0;
[System.Serializable]
public class Zone
{
public bool m_Loaded = false;
public bool m_IsLoadable = true;
public GameObject m_Root;
public Terrain m_Terrain;
public AssetBundle m_ZoneBundle;
public WWW m_ZoneDownload;
}
static string GetBaseUrl ()
{
if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.WindowsEditor)
return "file:// + " + Application.dataPath + "/../builds/AssetBundles/";
if (Application.platform == RuntimePlatform.OSXPlayer)
return "file:// + " + Application.dataPath + "/../../AssetBundles/";
else
return "AssetBundles/";
}
Zone[] m_Zones = new Zone[m_ZoneCount * m_ZoneCount];
static public string GetPrefix (string prefix, string postfix, int x, int y)
{
return string.Format("{0}{1}-{2}{3}", prefix, x, y, postfix);
}
static public string GetPrefix (int x, int y)
{
return GetPrefix("Map_", "", x, y);
}
IEnumerator LoadZone (int x, int y)
{
////@TODO: if (!Application.CanStreamedLevelBeLoaded (GetPrefix(x, y)))
if (m_IsLoading)
{
Debug.LogError("Already loading zone");
yield break;
}
Zone zone = m_Zones[m_ZoneCount*y + x];
// Zone cant be loaded
if (!zone.m_IsLoadable)
yield break;
m_IsLoading = true;
string levelName = GetPrefix(x, y);
if (m_UseWWWCaching || m_UseWWW)
{
string fullUrl = GetBaseUrl() + levelName + ".unity3d";
if (m_UseWWWCaching)
zone.m_ZoneDownload = WWW.LoadFromCacheOrDownload (fullUrl, m_CacheVersion);
else
zone.m_ZoneDownload = new WWW (fullUrl);
yield return zone.m_ZoneDownload;
// Make sure there were no errors in the download
if (zone.m_ZoneDownload.error != null)
{
Debug.LogError(zone.m_ZoneDownload.error);
zone.m_IsLoadable = false;
m_IsLoading = false;
zone.m_ZoneDownload.Dispose();
zone.m_ZoneDownload = null;
yield break;
}
// Load the scene so it becomes accessable from Application.LoadLevel
zone.m_ZoneBundle = zone.m_ZoneDownload.assetBundle;
zone.m_ZoneDownload.Dispose();
zone.m_ZoneDownload = null;
if (zone.m_ZoneBundle == null)
{
zone.m_IsLoadable = false;
m_IsLoading = false;
yield break;
}
}
// Load Level
AsyncOperation async = Application.LoadLevelAdditiveAsync(levelName);
yield return async;
// Necessary to prevent overwriting of another load level additive following immediately
// yield return 0;
// Find the root game object containing the level data
zone.m_Root = GameObject.Find("/" + levelName);
if (zone.m_Root != null)
{
Transform terain = zone.m_Root.transform.Find("Terrain");
if (terain)
zone.m_Terrain = terain.GetComponent(typeof(Terrain)) as Terrain;
zone.m_Loaded = true;
}
else
{
Debug.LogError(levelName + " could not be found after loading level");
}
// Hookup neighboring terrains so there are no seams in the LOD
for (int yi=Mathf.Max(y-1, 0);yi<Mathf.Min(y+2, m_ZoneCount);yi++)
{
for (int xi=Mathf.Max(x-1, 0);xi<Mathf.Min(x+2, m_ZoneCount);xi++)
{
Terrain curTerrain = GetLoadedTerrain(xi, yi);
if (curTerrain != null)
{
Terrain left = GetLoadedTerrain(xi-1, yi);
Terrain right = GetLoadedTerrain(xi+1, yi);
Terrain top = GetLoadedTerrain(xi, yi+1);
Terrain bottom = GetLoadedTerrain(xi, yi-1);
curTerrain.SetNeighbors (left, top, right, bottom);
}
}
}
m_IsLoading = false;
}
IEnumerator UnloadZone (int x, int y)
{
m_IsUnloading = true;
Zone zone = m_Zones[m_ZoneCount*y + x];
zone.m_Loaded = false;
if (zone.m_Root)
Destroy(zone.m_Root);
else
Debug.LogError("Root for zone has already been unloaded:" + GetPrefix(x, y));
zone.m_Terrain = null;
zone.m_Root = null;
yield return 0;
if (m_UseWWWCaching || m_UseWWW)
{
zone.m_ZoneBundle.Unload(true);
zone.m_ZoneBundle = null;
}
m_ZonesUnloadedSinceLastAssetUnload++;
m_IsUnloading = false;
}
/// Unload assets at some points
/// AsynchronousOperation op = Resources.GarbageCollectAssets(-1);
void OnEnable ()
{
ms_Singleton = this;
}
void Awake ()
{
ms_Singleton = this;
m_Zones = new Zone[m_ZoneCount * m_ZoneCount];
for (int i=0;i<m_Zones.Length;i++)
{
m_Zones[i] = new Zone();
}
if (m_UseWWWCaching)
{
///////@TODO: REMOVE THIS
Caching.Authorize ("test", "", 1024*1024*1000, "");
Caching.CleanCache();
}
// Precompute which dongs are loadable
/*
for (int y=0;y<m_DongCount;y++)
{
for (int x=0;x<m_DongCount;x++)
{
Dong dong = m_Dongs[m_DongCount*y + x];
dong.m_IsLoadable = CachingManifest.singleton.HasDongInZoneType(x, y, ms_ZoneType);
}
}
*/
ms_Singleton = this;
}
float GetSqrDistance (Vector3 position, int x, int y)
{
float minx = x * m_GridSize;
float maxx = (x+1) * m_GridSize;
float miny = y * m_GridSize;
float maxy = (y+1) * m_GridSize;
float xDistance = 0.0F;
float yDistance = 0.0F;
if (position.x < minx)
xDistance = Mathf.Abs(minx - position.x);
else if (position.x > maxx)
xDistance = Mathf.Abs(maxx - position.x);
if (position.z < miny)
yDistance = Mathf.Abs(miny - position.z);
else if (position.z > maxy)
yDistance = Mathf.Abs(maxy - position.z);
return xDistance * xDistance + yDistance * yDistance;
}
public static void SetPosition (Vector3 position)
{
singleton.m_PlayerPosition = position;
}
public static void SetPlayerTransform (Transform player)
{
singleton.m_PlayerTransform = player;
}
void GetClosestZone (Vector3 position, float closestDistance, out int closestX, out int closestY)
{
closestX = -1;
closestY = -1;
closestDistance = closestDistance * closestDistance;
for (int y=0;y<m_ZoneCount;y++)
{
for (int x=0;x<m_ZoneCount;x++)
{
Zone zone = m_Zones[m_ZoneCount*y + x];
if (!zone.m_Loaded && zone.m_IsLoadable)
{
float sqrDistance = GetSqrDistance(position, x, y);
if (sqrDistance < closestDistance)
{
closestDistance = sqrDistance;
closestX = x;
closestY = y;
}
}
}
}
}
void Update ()
{
if (m_PlayerTransform)
m_PlayerPosition = m_PlayerTransform.position;
if (m_IsLoading || m_IsUnloading)
return;
// Unload any zone that is far enough away
for (int y=0;y<m_ZoneCount;y++)
{
for (int x=0;x<m_ZoneCount;x++)
{
Zone zone = m_Zones[m_ZoneCount*y + x];
if (zone.m_Loaded)
{
float sqrDistance = GetSqrDistance(m_PlayerPosition, x, y);
if (sqrDistance > m_UnloadDistance * m_UnloadDistance)
{
StartCoroutine(UnloadZone(x, y));
return;
}
}
}
}
// Try loading the closest zone
int closestX, closestY;
GetClosestZone(m_PlayerPosition, m_LoadDistance, out closestX, out closestY);
if (closestX != -1)
StartCoroutine(LoadZone (closestX, closestY));
}
Terrain GetLoadedTerrain (int x, int y)
{
if ((x >= 0 && x < m_ZoneCount) && (y >= 0 && y < m_ZoneCount))
{
Zone zone = m_Zones[m_ZoneCount*y + x];
if (zone.m_Loaded)
return zone.m_Terrain;
}
return null;
}
}
I forget what I changed to get it working but here is my copy of it.
Answer by Toni · Oct 13, 2010 at 01:11 PM
Hi Christian,
thanks for the quick answer, but using your file I got new errors...
Assets/Editor/GenerateScenes.cs(53,52): error CS0103: The name `TerrainLighting' does not exist in the current context
and
Assets/Editor/GenerateScenes.cs(53,41): error CS1061: Type UnityEngine.Terrain' does not contain a definition for
lighting' and no extension method lighting' of type
UnityEngine.Terrain' could be found (are you missing a using directive or an assembly reference?)
I'm totally lost here...
Thanks in advance,
Toni
Answer by ViperCode_ · Apr 16, 2011 at 07:25 PM
Christian, it appears that you have just commented out the line dealing with the "AsynchronousOperation"
Answer by fzzzt · Dec 18, 2011 at 07:21 PM
I wanted to see this demo in Unity 3 so I figured it out. There are two/three compilation errors:
In GenerateScenes.cs, comment out lines 52 and 53:
//Terrain terrain = terrainGO.GetComponent(typeof(Terrain)) as Terrain;
//terrain.lighting = TerrainLighting.Lightmap;
In ZoneLoader.cs, change line 130 from:
AsynchronousOperation async = Application.LoadLevelAdditiveAsynchronous[...]
To:
AsyncOperation async = Application.LoadLevelAdditiveAsync(levelName);
This works in the free version of Unity also, though it throws errors at runtime due to the lack of LoadLevelAdditiveAsync (and falls back to LoadLevelAdditive I think).
Your answer
Follow this Question
Related Questions
Can I move just some of the verts on a mesh? 1 Answer
Path finding on a procedural terrain 0 Answers
[C#] 2D infinite Terrain Generation with prefabs?! -> lag 0 Answers
Tile generation problem 1 Answer