A way to preserve terrain data after changing mesh, texture, tree and grass maps in game.

I made a script and workflow to avoid permanent changes on terrains, that can be done in game mode, if the mesh or textures are changed. I tested it with mesh deformation and texture changes and tree/grass changes and it works fine. It would be nice, if more of you could test/use this script/workflow. I would be thankful for every improvements.

I was searching for a long time for a solution, and this is the most convenient and safe solution.

This code is free to use for commercial purposes !!!

These are just some lines for so much investigation…

using UnityEngine;


/// <summary>
/// Problem: I want to change the mesh and the texture on the terrain. And this must be safe!
/// Lot of examples using OnApplicationQuit() for setting back textures, meshes, but a blue screen
/// does not call that method and the terrain has then permanent changes. Think about that
/// happens at a customer...
///
/// Idea: Duplicate the TerrainData for backup purposes. The TerrainCollider script
/// references to it. Is on the same GameObject as the Terrain script. Click on the
/// TerrainData and you see where it is. Duplicate the TerrainData (Strg + D).
/// Create a folder "Resources" (Unity scans for this folder name and uses all Resources
/// as Resources folder, which can be accessed in a running game.). Create under the
/// "Resources" folder a "TerrainData" folder and drag and drop the duplicated
/// TerrainData in this folder.
///
/// Add the TerrainPreserver.cs script on the Terrain GameObject (in the scene).
///
/// If you start the game, then this script copies all the terrain data from the duplicate
/// to the current terrain data. So, if you change the terrain, then at the next start
/// all the data will be overridden with the backupped terrain data.
///
/// The only one thing what you have to do is, to always create a backup (Strg + D and copy
/// it to the Resource/TerrainData folder), if you change the terrain data.
/// Keep in mind, the terrain data are:
/// - Trees, grasses, height and texture changes placed with unity terrain tool.
///
/// If you just places prefabs (Tree, grass prefab) on the terrain (without the terrain tool),
/// then you don't have to update the backup/duplicate TerrainData.
///
/// If crash happens, then just start and stop the game to reset the terrain for the editor mode.
/// </summary>

namespace metadesc {
    public class TerrainPreserver : MonoBehaviour {

        TerrainData td1;
        TerrainData td2;


        void Awake () {

            Terrain terrain = GetComponent<Terrain>();
            if (terrain == null) {
                Debug.LogError("No terrain on GameObject: " + gameObject);
                return;
            }

            td1 = terrain.terrainData;

            // This is the backup name/path of the cloned TerrainData.
           string tdBackupName = "TerrainData/" + td1.name;
            td2 = Resources.Load<TerrainData>(tdBackupName);
            if (td2 == null) {
               Debug.LogError("No TerrainData backup in a Resources folder, mussing folder is: Assets/Resources/TerrainData/");
                return;
            }

            // If blue screen, we still have to copy, for sure. It is a fast operation.
            resetTerrainDataChanges();
        }

        void OnApplicationQuit() {
            // To reset the terrain after quite the application.
            resetTerrainDataChanges();
        }

        void resetTerrainDataChanges() {
            // Terrain collider
            td1.SetHeights(0, 0, td2.GetHeights(0, 0, td1.heightmapWidth, td1.heightmapHeight));
            // Textures
            td1.SetAlphamaps(0, 0, td2.GetAlphamaps(0, 0, td1.alphamapWidth, td1.alphamapHeight));
            // Trees
            td1.treeInstances = td2.treeInstances;
            // Grasses
            td1.SetDetailLayer(0, 0, 0, td2.GetDetailLayer(0, 0, td1.detailWidth, td1.detailHeight, 0));
        }
    }
}
1 Like

You can also write an editor script on top of it and place a button for backuping your map.

Here is the editor code. The editor code automatizes copying the TerrainData to Resources/TerrainData folder at starting the game in Unity. I also added a button for copying the TerrainData for avoiding to start the game, if the terrain changed. Should be Blue-Screen safe.

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


namespace metadesc {
   [CustomEditor(typeof(TerrainPreserver))]
   [InitializeOnLoad]
   public class TerrainPreserverEditor : Editor {
   
       private Color32 textColor1 = new Color32(120, 220, 250, 255);
       private Color32 textColor2 = new Color32(210, 240, 250, 255);
       private Color32 bgColor1 = new Color32(190, 200, 230, 255);
       TerrainPreserver terrainPreserver;
       static bool IsInLaunching = false;
 
 
       static TerrainPreserverEditor() {
           IsInLaunching = true;
       }
   
       void OnEnable() {
           terrainPreserver = target as TerrainPreserver;
           if (IsInLaunching) {
               CopyTerrain();
               IsInLaunching = false;
           }
       }
   
       public override void OnInspectorGUI() {
           serializedObject.Update ();
       
           GUI.contentColor = textColor1;
           EditorGUILayout.HelpBox("Preserves the terrain data to avoid changing the original terrain data at runtime.\n" +
               "- All terrains must have a UNIQUE NAME, else the backups will override each other.\n" +
               "- A FOLDER 'Assets/Resources/TerrainData' must be available for the backupped TerrainData.\n" +
               "- In the editor mode at each game start the terrain will be cloned.\n" +
               "- In a game release the TerrainData clone must be up to data, just for sure check this.\n" +
               "- The BUTTON is just needed to copy the terrain manually, without starting the game.",
               MessageType.Info);
       
           GUI.backgroundColor = bgColor1;
           GUI.contentColor = textColor2;
       
           if (GUILayout.Button(new GUIContent("(Re)Create TerrainData clone.",
                   "(Re)Creates the TerrainData clone to the Resources folder."))) {
               CopyTerrain();
           }
       }
   
       protected void CopyTerrain() {
           Terrain tOriginal = terrainPreserver.GetComponent<Terrain>();
           TerrainData td1 = tOriginal.terrainData;
           string tdBackupName = "Assets/Resources/TerrainData/" + td1.name + ".asset";
           string originalbackupName = AssetDatabase.GetAssetPath(td1);
           FileInfo oFi = new FileInfo(originalbackupName);
           FileInfo bFi = new FileInfo(tdBackupName);
           if (bFi == null || bFi.CreationTime < oFi.CreationTime) {
               Debug.Log("Backup Terrain Data");
               AssetDatabase.CopyAsset(originalbackupName, tdBackupName);
           }
       }
   }
}

Edit: Creation time will be checked to avoid redundant copying of terrain data.

Don’t forget to save the scene after you changed your terrain, else the changes will be overridden after starting the game. At saving the scene the time stamp of the terrain changes and the new terrain will be copied to the backup terrain data.

Hello! im using “TerrainPreserver”, im search way to save terrain. I did everything as in the instructions and this is work, my terrain is save and load, but only in game editor, when im exit, build and run my game, my terrain lost. I do not know exactly what folder “Resources” work, but my terrain from “Resoruces” not work. Any idea how to save terrain for a beginner? Regards!

1 Like

Hi. I have same issue did you find any fix for this?

The terrain collider does not take into account the heights generated by displacement.
But looking at the wireframe, the polygons are there, it’s not just a “scenic” effect.

Unity has a built-in class calledMeshCollider. This class allows the physics system to interactwith a triangulated mesh easily. As the base mesh has already been calculated for eachterrain tile, their data can easily be reused for mesh colliders. The problem is, that thesemesh colliders would not represent the tessellated and displaced version displayed by theGPU. This leads to objects colliding with seemingly invisible edges and faces where theterrain is indented and rolling through faces where terrain is extruded. Solving the collision problem is not an easy task because the displaced terrain data must somehow return to the CPU to use it in physical collision detection.

A solution could be to save the terrain data after its “coloring” including heights generated by the displace and apply it to the collider.
Do you think it is possible to do it?