Unity3D Crashes upon destroying terrain

So this “bug” have annoyed me for a while now and I keep scratching my head over why it happens. My thoughts are that it may have something to do with the “SetNeighbors” method, as if I comment that out it doesn’t crash.

So I was wondering if anyone else have this issues and what I should do to avoid this from happening?

My code for dynamic terrain adding/deletion is here:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ChunkLoader : MonoBehaviour
{
    int LoadDistance = 5;
    int Center { get { return (LoadDistance - 1) / 2; } }
    int ChunkSize = 512;
    List<List<GameObject>> Chunks = new List<List<GameObject>>();

    Terrain lt;
    Terrain rt;
    Terrain tt;
    Terrain bt;

    [SerializeField]
    GameObject Player;

    // Use this for initialization
    void Start ()
    {
        for (int i = 0; i < LoadDistance; i++)
        {
            List<GameObject> lgo = new List<GameObject>();
            for (int j = 0; j < LoadDistance; j++)
            {
                GameObject go = new GameObject();
                go.AddComponent<SimpleTerrainGenerator>();
                go.transform.position = new Vector3(((((LoadDistance - 1) / 2) - LoadDistance) + j + 1) * ChunkSize, 0, (((LoadDistance - 1) / 2 - LoadDistance) + i + 1) * ChunkSize);
                go.name = go.name + "[" + (j - Center) + ";" + (i - Center) + "]";
                lgo.Add(go);
                go.GetComponent<SimpleTerrainGenerator>().Starter();
            }
            Chunks.Add(lgo);
        }
        for (int i = 0; i < LoadDistance; i++)
        {
            for (int j = 0; j < LoadDistance; j++)
            {
                if (i != 0)
                {
                    bt = Chunks[i - 1][j].GetComponent<Terrain>();
                    TerrainTools.StitchTerrains(bt, Chunks[i][j].GetComponent<Terrain>(), 64, 0, 20);
                }
                if (i != LoadDistance - 1)
                {
                    tt = Chunks[i + 1][j].GetComponent<Terrain>();
                    TerrainTools.StitchTerrains(tt, Chunks[i][j].GetComponent<Terrain>(), 64, 0, 20);
                }
                if (j != 0)
                {
                    lt = Chunks[i][j - 1].GetComponent<Terrain>();
                    TerrainTools.StitchTerrains(lt, Chunks[i][j].GetComponent<Terrain>(), 64, 0, 20);
                }
                if (j != LoadDistance - 1)
                {
                    rt = Chunks[i][j + 1].GetComponent<Terrain>();
                    TerrainTools.StitchTerrains(rt, Chunks[i][j].GetComponent<Terrain>(), 64, 0, 20);
                }

                Chunks[i][j].GetComponent<Terrain>().SetNeighbors(lt, tt, rt, bt);
                Chunks[i][j].GetComponent<Terrain>().Flush();
                bt = null;
                tt = null;
                lt = null;
                rt = null;
            }
        }

    }

    // Update is called once per frame
    void Update()
    {
        if (Player.transform.position.z < Chunks[Center - 1][Center].transform.position.z + (ChunkSize / 2))
        {
            AddColumnBack();
            DeleteColumnFront();
        }
        if (Player.transform.position.z > Chunks[Center + 1][Center].transform.position.z - (ChunkSize / 2))
        {
            AddColumnFront();
            DeleteColumnBack();
        }
        if (Player.transform.position.x < Chunks[Center][Center - 1].transform.position.x + (ChunkSize / 2))
        {
            AddRowBack();
            DeleteRowFront();
        }
        if (Player.transform.position.x > Chunks[Center][Center + 1].transform.position.x - (ChunkSize / 2))
        {
            AddRowFront();
            DeleteRowBack();
        }
    }

    void AddColumnFront()
    {
        Vector3 vec = Chunks[Chunks.Count - 1][Center].transform.position;
        List<GameObject> lgo = new List<GameObject>();
        int c = -(Center * ChunkSize);
        for (int i = 0; i < Chunks.Count; i++)
        {
            GameObject go = new GameObject();
            go.transform.position = new Vector3(vec.x + c, 0, vec.z + ChunkSize);
            go.AddComponent<SimpleTerrainGenerator>();
            go.name = go.name + "[" + go.transform.position.x / ChunkSize + ";" + go.transform.position.z / ChunkSize + "]";
            lgo.Add(go);
            go.GetComponent<SimpleTerrainGenerator>().Starter();
            c += ChunkSize;
        }

        Chunks.Add(lgo);
    }

    void AddColumnBack()
    {
        Vector3 vec = Chunks[0][Center].transform.position;
        List<GameObject> lgo = new List<GameObject>();
        int c = -(Center * ChunkSize);
        for (int i = 0; i < Chunks.Count; i++)
        {
            GameObject go = new GameObject();
            go.transform.position = new Vector3(vec.x + c, 0, vec.z - ChunkSize);
            go.AddComponent<SimpleTerrainGenerator>();
            go.name = go.name + "[" + go.transform.position.x / ChunkSize + ";" + go.transform.position.z / ChunkSize + "]";
            lgo.Add(go);
            go.GetComponent<SimpleTerrainGenerator>().Starter();
            c += ChunkSize;
        }
        Chunks.Insert(0, lgo);
    }

    void AddRowFront()
    {
        Vector3 vec = Chunks[Center][Chunks[1].Count - 1].transform.position;
        int c = -(Center * ChunkSize);
        foreach (List<GameObject> lgo in Chunks)
        {
            GameObject go = new GameObject();
            go.transform.position = new Vector3(vec.x + ChunkSize, 0, vec.z + c);
            go.AddComponent<SimpleTerrainGenerator>();
            go.name = go.name + "[" + go.transform.position.x / ChunkSize + ";" + go.transform.position.z / ChunkSize + "]";
            go.GetComponent<SimpleTerrainGenerator>().Starter();
            lgo.Add(go);
            c += ChunkSize;
        }
    }

    void AddRowBack()
    {
        Vector3 vec = Chunks[Center][0].transform.position;
        int c = -(Center * ChunkSize);
        foreach (List<GameObject> lgo in Chunks)
        {
            GameObject go = new GameObject();
            go.transform.position = new Vector3(vec.x - ChunkSize, 0, vec.z + c);
            go.AddComponent<SimpleTerrainGenerator>();
            go.name = go.name + "[" + go.transform.position.x / ChunkSize + ";" + go.transform.position.z / ChunkSize + "]";
            go.GetComponent<SimpleTerrainGenerator>().Starter();
            lgo.Insert(0, go);
            c += ChunkSize;
        }
    }

    void DeleteColumnFront()
    {
        for (int i = 0; i < Chunks[Chunks.Count - 1].Count; i++)
        {
            GameObject.Destroy(Chunks[Chunks.Count - 1][i].gameObject);
        }
        Chunks.RemoveAt(Chunks.Count - 1);
    }

    void DeleteColumnBack()
    {
        for (int i = 0; i < Chunks[0].Count; i++)
        {
            GameObject.Destroy(Chunks[0][i].gameObject);
        }
        Chunks.RemoveAt(0);
    }

    void DeleteRowFront()
    {
        foreach (List<GameObject> lgo in Chunks)
        {
            GameObject.Destroy(lgo[lgo.Count - 1]);
            lgo.RemoveAt(lgo.Count - 1);
        }
    }

    void DeleteRowBack()
    {
        foreach (List<GameObject> lgo in Chunks)
        {
            GameObject.Destroy(lgo[0]);
            lgo.RemoveAt(0);
        }
    }
}

All forgot to mention the generator, but it pretty much just create the terrain and makes all the setup and such:

using UnityEngine;
using System;
using System.Collections;
using LibNoise.Unity;
using LibNoise.Unity.Generator;
using LibNoise.Unity.Operator;

public class SimpleTerrainGenerator : MonoBehaviour
{
    public GameObject[] go;
    public int[] percentage;
    public int seed;
    //public HeightMap HeightMap;
    //public BiomeMap BiomeMap;
    public Terrain ThisTerrain;
    public Terrain TerTop;
    public Terrain TerLeft;
    public Terrain TerRight;
    public Terrain TerBottom;

    // Use this for initialization
    public void Starter()
    {
        Terrain terrrain = this.gameObject.AddComponent<Terrain>();
        TerrainData terrraindata = terrrain.terrainData = new TerrainData();
        terrrain.terrainData.heightmapResolution = 256;
        terrrain.terrainData.size = new Vector3(512, 512, 512);
        TerrainCollider terrraincollider = this.gameObject.AddComponent<TerrainCollider>();
        terrraincollider.terrainData = terrraindata;

        float fl = UnityEngine.Random.Range(0.0f, 100000.0f);
        //PrepareTerrain();
        float[,] h = new float[terrraindata.heightmapResolution, terrraindata.heightmapResolution];

        Vector3 gopos = terrrain.transform.position;
        float cwidth = terrraindata.size.x;
        int resolution = terrraindata.heightmapResolution;
        float[,] hmap = new float[resolution, resolution];
        double yoffset = 0 - (gopos.x / cwidth);
        double xoffset = (gopos.z / cwidth);
        Noise2D tmpNoiseMap = new Noise2D(resolution, resolution, new Perlin(0.25, 1, 0.5, 4, 0, QualityMode.High));
        tmpNoiseMap.GeneratePlanar(xoffset, (xoffset) + (1f / resolution) * (resolution + 1), -yoffset, (-yoffset) + (1f / resolution) * (resolution + 1));

            for (int hY = 0; hY < resolution; hY++)
            {
                for (int hX = 0; hX < resolution; hX++)
                {
                    hmap[hX, hY] = ((tmpNoiseMap[hX, hY] * 0.5f) + 0.5f) * 1;
                }
            }
      

        terrrain.GetComponent<Terrain>().terrainData.SetHeights(0, 0, hmap);
        }
}

Have you tired to see if it crashes with just one of the delete methods or is it all of them?

If it is the Neighbors then my best guess there is that you delete the objects but the ones that are left are still looking for their Neighbors. Thus they can’t find them and the game crashes. I have never played with terrain scripting that much but i would try something along the lines of your start method. But catch the terrains your about to delete and set them to null before they hit the SetNeighbors method.

Previously you could get away with simply destroying the Terrain (or I should say, the game object with the Terrain component on it), but one of the 4.x updates changed that.

There are two things to do before destroying your terrain.

  1. Reset the neighbors of any terrains that were previously using the terrain you are about to destroy, inserting null in place of the terrain. For instance, if you’re about to destroy a terrain that is the right neighbor of another terrain, call SetNeighbors(leftNeighbor, topNeighbor, null, bottomNeighbor).

  2. Reset the neighbors of the actual terrain you are about to destroy, inserting null for every neighbor.

In my Dynamic Loading Kit, I perform both of these steps, but I cannot remember if both are necessary, so try just doing step one first, and if it’s still crashing, implement step two as well. Good luck!

Thanks I tested this on one axis and it actually worked! I do find it weird though that unity does not clean this up it self or at minimum throw a run time exception… So I would assume it could be a valid bug as it literally crashes the editor?

Yeah, it probably is a bug, especially since before the 4.x update it was cleaned up automatically, and the release notes at the time made no mention of the need to clean it up manually.

But I assume the manual cleanup is the same as the automatic cleanup, and since it was working I never bother to submit a bug report.

I will assume that the native code should have a slight performance improvement versus the .net platform, and I’d also assume the unity3D team can make it perform better all together as they have the source for everything behind :slight_smile: but yeah, for now it works…