Search Unity

What's the best way to keep a GameObject from getting saved?

Discussion in 'Scripting' started by MattRix, Oct 16, 2014.

  1. MattRix

    MattRix

    Joined:
    Aug 23, 2011
    Posts:
    121
    I have a ton of *child* GameObjects that I generate both in the editor and at runtime. I don't want to save them in the Unity scene file, because I'm using git so they will create tons of data/noise that I don't need at this stage of the project.

    I tried using HideFlags.DontSave, but that causes all kinds of issues, because they become undestroyable unless I track them manually... and also it causes CheckConsistency errors, because the parent transform seems to still think it has children, even though it doesn't.

    TLDR: I want GameObjects that don't get saved in the scene file. When saving I want them to be treated as if they don't exist in the hierarchy at all.
     
    Last edited: Oct 19, 2014
  2. PolymorphiK Games

    PolymorphiK Games

    Joined:
    Oct 5, 2014
    Posts:
    51
    Create prefabs, then manually load them by instantiating them at run-time.
     
  3. MattRix

    MattRix

    Joined:
    Aug 23, 2011
    Posts:
    121
    Thanks for the idea, but unfortunately that's not really the sort of thing I want to do. The gist of what I'm doing is generating lots of dynamic meshes and gameobjects, but I don't want to store that data *at all*, not even in the assets folder inside a prefab.

    Anyway, I actually figured out a pretty solid solution for my problem. What I do now is that I hook into the "OnWillSaveAssets" callback, which gets called whenever the scene is saving. When this happens, I destroy all of the generated stuff from the scene, so none of it gets saved.

    After that, I start a one frame delay callback (using EditorApplication.update), and in that callback I recreate all the generated stuff again.

    This approach works really well because now nothing extra gets saved in the scene file, and now the assets don't even need to be recreated between play and edit mode (since although that uses serialization, it doesn't actually save to the scene file).

    An even more efficient approach would be when saving, to take all of the generated assets and hide them in a "DontSave" root gameobject, and then after saving, move them right back where they were. Right now my stuff can be regenerated super quickly so that's not an issue, but I may move to that approach later.

    Here's the code:

    Code (CSharp):
    1. public class FGAssetModificationProcessor : UnityEditor.AssetModificationProcessor
    2. {
    3.     static string[] OnWillSaveAssets (string[] paths)
    4.     {
    5.         FGCore.RemoveGeneratedAssets();
    6.  
    7.         EditorApplication.update += RefreshScene;
    8.  
    9.         return paths;
    10.     }
    11.  
    12.     static void RefreshScene()
    13.     {
    14.         EditorApplication.update -= RefreshScene;
    15.  
    16.         FGCore.CreateGeneratedAssets();
    17.     }
    18. }
     
    Last edited: Oct 19, 2014
    davl3232 and v01pe_ like this.
  4. PolymorphiK Games

    PolymorphiK Games

    Joined:
    Oct 5, 2014
    Posts:
    51
    The approach I had was to create a .asset file then just create an array of two fields. ID, Prefab. Then you can add the prefab and parent it anywhere you need it to be. When the scene closes and or the game is ended it gets cleaned up. Nothing will get saved. I use this approach when I am using something like, a character creator with a base rig then dd / switch out body parts and weapons. I can do this because my approach works. I never 'save' the prefab, in an XML file I keep the data like Head = Head Type 01 etc. then construct the character based on the XML data.
     
  5. cl9-2

    cl9-2

    Joined:
    May 31, 2013
    Posts:
    417
    Perhaps first using a script to set the temporary game objects' tags, then using another script running in ExecuteInEditMode to find and delete the tagged GameObjects?
     
  6. snlehton

    snlehton

    Joined:
    Jun 11, 2010
    Posts:
    99
    Using DontSave for game objects with parents is problematic as you said - because you take the responsibility for the object and tracking it can be really PITA. And Unity then saves null transforms in the parent hierarchy which causes the consistency check to fail.

    If you're using DontSave for a gameobject that has a dynamic mesh, one way to go around it is to use HideInHierarchy for the gameobject itself and DontSave for the mesh created. This avoids both the consistency check and the leaked mesh (I'm not sure why it doesn't leak meshes but it doesn't seem to do it). However it does save the temporary gameobject responsible for containing and rendering the mesh.

    Here is some code I've used for basis for dynamic meshes (it includes CleanMesh function which is not actually used here but show how to clean meshes properly in editor and in runtime):

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. [ExecuteInEditMode]
    6. public class DynamicMeshTest : MonoBehaviour
    7. {
    8.     [HideInInspector]
    9.     [SerializeField]
    10.     protected GameObject meshInstance;
    11.  
    12.     [Range(0, 10)]
    13.     public float width = 1;
    14.     [Range(0, 10)]
    15.     public float height = 1;
    16.  
    17.     void Update ()
    18.     {
    19.         EnsureMesh();
    20.     }
    21.  
    22.     void EnsureMesh()
    23.     {
    24.         if (!meshInstance)
    25.         {
    26.             meshInstance = new GameObject("Mesh GO");
    27.             meshInstance.transform.parent = transform;
    28.             meshInstance.transform.localPosition = Vector3.zero;
    29.             meshInstance.transform.localScale = Vector3.one;
    30.             meshInstance.transform.localRotation = Quaternion.identity;
    31.             meshInstance.hideFlags = HideFlags.HideInHierarchy;
    32.         }
    33.  
    34.         MeshFilter mf = meshInstance.GetComponent<MeshFilter>();
    35.         if (!mf)
    36.             mf = meshInstance.AddComponent<MeshFilter>();
    37.  
    38.         Mesh mesh = mf.sharedMesh;
    39.  
    40.         if (!mesh)
    41.         {
    42.             mesh = new Mesh();
    43.             mesh.hideFlags = HideFlags.DontSave;
    44.             mesh.name = "Mesh";
    45.         }
    46.  
    47.         mesh.vertices = new Vector3[] { Vector3.zero, Vector3.right * width, Vector3.up * height, Vector3.right * width + Vector3.up * height};
    48.         mesh.triangles = new int[] { 0, 2, 3, 0, 3, 1 };
    49.  
    50.         mf.sharedMesh = mesh;
    51.  
    52.         MeshRenderer mr = meshInstance.GetComponent<MeshRenderer>();
    53.         if (!mr)
    54.             mr = meshInstance.AddComponent<MeshRenderer>();
    55.     }
    56.  
    57.     void CleanMesh()
    58.     {
    59.         if (!meshInstance)
    60.             return;
    61.         if (Application.isEditor && !Application.isPlaying)
    62.             DestroyImmediate(meshInstance.GetComponent<MeshFilter>().sharedMesh);
    63.         else
    64.             Destroy(meshInstance.GetComponent<MeshFilter>().sharedMesh);
    65.     }
    66. }
    67.  
    When you put this into a gameobject on a scene and save, the scene file contains all the components (MeshRenderer, MeshFilter) but not the mesh. If you remove the DontSave from the mesh, then you also get the mesh in the scene file. For such a small rectangle mesh it's surprisingly amount of data. Then try and save temporary 2k textures into a scene :)
     
  7. snlehton

    snlehton

    Joined:
    Jun 11, 2010
    Posts:
    99
    So the price you pay is the renderer and the meshfilter components for each dynamic mesh. But luckily these components are somewhat lightweight.

    I did try to use DontSave on the MeshRenderer and MeshFilter components, but then I started getting the leaked mesh again. However, just for the MeshRenderer it worked
     
  8. v01pe_

    v01pe_

    Joined:
    Mar 25, 2015
    Posts:
    71
    Thanks Mat, exactly what I was searching for… I tried using `ISerializationCallbackReceiver`, but that also get's called when the object is shown in the inspector.

    A small simplification that is possible since Unity 2017: instead of subscribing `EditorApplication.update` there's the event
    Code (CSharp):
    1. UnityEditor.EditorApplication.delayCall += FGCore.CreateGeneratedAssets;
    That is cleared every time after it's called – thus doesn't need to be unsubscribed again. Thus the `RefreshScene` method is not needed any more (except more things need to happen).