Search Unity

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

Discussion in 'Scripting' started by mkgame, Mar 14, 2017.

  1. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    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...

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3.  
    4.  
    5. /// <summary>
    6. /// Problem: I want to change the mesh and the texture on the terrain. And this must be safe!
    7. /// Lot of examples using OnApplicationQuit() for setting back textures, meshes, but a blue screen
    8. /// does not call that method and the terrain has then permanent changes. Think about that
    9. /// happens at a customer...
    10. ///
    11. /// Idea: Duplicate the TerrainData for backup purposes. The TerrainCollider script
    12. /// references to it. Is on the same GameObject as the Terrain script. Click on the
    13. /// TerrainData and you see where it is. Duplicate the TerrainData (Strg + D).
    14. /// Create a folder "Resources" (Unity scans for this folder name and uses all Resources
    15. /// as Resources folder, which can be accessed in a running game.). Create under the
    16. /// "Resources" folder a "TerrainData" folder and drag and drop the duplicated
    17. /// TerrainData in this folder.
    18. ///
    19. /// Add the TerrainPreserver.cs script on the Terrain GameObject (in the scene).
    20. ///
    21. /// If you start the game, then this script copies all the terrain data from the duplicate
    22. /// to the current terrain data. So, if you change the terrain, then at the next start
    23. /// all the data will be overridden with the backupped terrain data.
    24. ///
    25. /// The only one thing what you have to do is, to always create a backup (Strg + D and copy
    26. /// it to the Resource/TerrainData folder), if you change the terrain data.
    27. /// Keep in mind, the terrain data are:
    28. /// - Trees, grasses, height and texture changes placed with unity terrain tool.
    29. ///
    30. /// If you just places prefabs (Tree, grass prefab) on the terrain (without the terrain tool),
    31. /// then you don't have to update the backup/duplicate TerrainData.
    32. ///
    33. /// If crash happens, then just start and stop the game to reset the terrain for the editor mode.
    34. /// </summary>
    35.  
    36. namespace metadesc {
    37.     public class TerrainPreserver : MonoBehaviour {
    38.  
    39.         TerrainData td1;
    40.         TerrainData td2;
    41.  
    42.  
    43.         void Awake () {
    44.  
    45.             Terrain terrain = GetComponent<Terrain>();
    46.             if (terrain == null) {
    47.                 Debug.LogError("No terrain on GameObject: " + gameObject);
    48.                 return;
    49.             }
    50.  
    51.             td1 = terrain.terrainData;
    52.  
    53.             // This is the backup name/path of the cloned TerrainData.
    54.            string tdBackupName = "TerrainData/" + td1.name;
    55.             td2 = Resources.Load<TerrainData>(tdBackupName);
    56.             if (td2 == null) {
    57.                Debug.LogError("No TerrainData backup in a Resources folder, mussing folder is: Assets/Resources/TerrainData/");
    58.                 return;
    59.             }
    60.  
    61.             // If blue screen, we still have to copy, for sure. It is a fast operation.
    62.             resetTerrainDataChanges();
    63.         }
    64.  
    65.         void OnApplicationQuit() {
    66.             // To reset the terrain after quite the application.
    67.             resetTerrainDataChanges();
    68.         }
    69.  
    70.         void resetTerrainDataChanges() {
    71.             // Terrain collider
    72.             td1.SetHeights(0, 0, td2.GetHeights(0, 0, td1.heightmapWidth, td1.heightmapHeight));
    73.             // Textures
    74.             td1.SetAlphamaps(0, 0, td2.GetAlphamaps(0, 0, td1.alphamapWidth, td1.alphamapHeight));
    75.             // Trees
    76.             td1.treeInstances = td2.treeInstances;
    77.             // Grasses
    78.             td1.SetDetailLayer(0, 0, 0, td2.GetDetailLayer(0, 0, td1.detailWidth, td1.detailHeight, 0));
    79.         }
    80.     }
    81. }
    82.  
    83.  
    84.  
     
    Last edited: Mar 27, 2018
    Flurgle likes this.
  2. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    You can also write an editor script on top of it and place a button for backuping your map.
     
  3. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    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.

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEditor;
    6.  
    7.  
    8. namespace metadesc {
    9.    [CustomEditor(typeof(TerrainPreserver))]
    10.    [InitializeOnLoad]
    11.    public class TerrainPreserverEditor : Editor {
    12.    
    13.        private Color32 textColor1 = new Color32(120, 220, 250, 255);
    14.        private Color32 textColor2 = new Color32(210, 240, 250, 255);
    15.        private Color32 bgColor1 = new Color32(190, 200, 230, 255);
    16.        TerrainPreserver terrainPreserver;
    17.        static bool IsInLaunching = false;
    18.  
    19.  
    20.        static TerrainPreserverEditor() {
    21.            IsInLaunching = true;
    22.        }
    23.    
    24.        void OnEnable() {
    25.            terrainPreserver = target as TerrainPreserver;
    26.            if (IsInLaunching) {
    27.                CopyTerrain();
    28.                IsInLaunching = false;
    29.            }
    30.        }
    31.    
    32.        public override void OnInspectorGUI() {
    33.            serializedObject.Update ();
    34.        
    35.            GUI.contentColor = textColor1;
    36.            EditorGUILayout.HelpBox("Preserves the terrain data to avoid changing the original terrain data at runtime.\n" +
    37.                "- All terrains must have a UNIQUE NAME, else the backups will override each other.\n" +
    38.                "- A FOLDER 'Assets/Resources/TerrainData' must be available for the backupped TerrainData.\n" +
    39.                "- In the editor mode at each game start the terrain will be cloned.\n" +
    40.                "- In a game release the TerrainData clone must be up to data, just for sure check this.\n" +
    41.                "- The BUTTON is just needed to copy the terrain manually, without starting the game.",
    42.                MessageType.Info);
    43.        
    44.            GUI.backgroundColor = bgColor1;
    45.            GUI.contentColor = textColor2;
    46.        
    47.            if (GUILayout.Button(new GUIContent("(Re)Create TerrainData clone.",
    48.                    "(Re)Creates the TerrainData clone to the Resources folder."))) {
    49.                CopyTerrain();
    50.            }
    51.        }
    52.    
    53.        protected void CopyTerrain() {
    54.            Terrain tOriginal = terrainPreserver.GetComponent<Terrain>();
    55.            TerrainData td1 = tOriginal.terrainData;
    56.            string tdBackupName = "Assets/Resources/TerrainData/" + td1.name + ".asset";
    57.            string originalbackupName = AssetDatabase.GetAssetPath(td1);
    58.            FileInfo oFi = new FileInfo(originalbackupName);
    59.            FileInfo bFi = new FileInfo(tdBackupName);
    60.            if (bFi == null || bFi.CreationTime < oFi.CreationTime) {
    61.                Debug.Log("Backup Terrain Data");
    62.                AssetDatabase.CopyAsset(originalbackupName, tdBackupName);
    63.            }
    64.        }
    65.    }
    66. }
    67.  
    68.  
    Edit: Creation time will be checked to avoid redundant copying of terrain data.
     
    Last edited: Mar 27, 2018
  4. mkgame

    mkgame

    Joined:
    Feb 24, 2014
    Posts:
    592
    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.
     
  5. Pioxon

    Pioxon

    Joined:
    Nov 20, 2016
    Posts:
    4
    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!
     
    Last edited: Dec 12, 2018
    ksam2 likes this.
  6. ksam2

    ksam2

    Joined:
    Apr 28, 2012
    Posts:
    1,080
    Hi. I have same issue did you find any fix for this?
     
  7. WILEz1975

    WILEz1975

    Joined:
    Mar 23, 2013
    Posts:
    375
    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.
    Screenshot_5.png

    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?
     
    Last edited: Feb 16, 2021