Search Unity

Scripted scene changes not being saved

Discussion in 'Scripting' started by nockieboy, Apr 12, 2018.

  1. nockieboy

    nockieboy

    Joined:
    Jul 7, 2012
    Posts:
    48
    Okay, so it seems Unity 2017 does something different from previous versions of Unity - I haven't really used it since 5.4, but here I am trying to fix a bug in an editor script I used to use.

    Basically, the editor script looks at objects in the scene and re-assigns materials where necessary. This all worked fine in 5.*, but since 2017 it seems to have broken. What's happening now is that the re-assignments are all completed, but even though I'm marking the scene as dirty in the script and saving the scene after the script has run, when I load the scene back up (restart Unity, for example) the changes are lost.

    I'm using this to set the scene as dirty:

    EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());


    ... and that should get called once per scene (if there's multiple scenes, for example).

    To cover my bases, I'm also doing this after every renderer has a material re-assigned:

    EditorUtility.SetDirty((Component)goRenderer);


    The scene is marked dirty when the script completes (the asterisk is visible in the title bar after the scene filename) but saving it doesn't save the material re-assignments.

    I haven't tried the Undo.RegisterCompleteObjectUndo() as there are sometimes thousands of re-assignments - I image that would flood the undo buffer in short order..

    Anyone have any ideas or suggestions?
     
  2. nockieboy

    nockieboy

    Joined:
    Jul 7, 2012
    Posts:
    48
    @Baste - Do you have any thoughts on this issue? I noticed you've provided some solutions to similar issues. When the script completes, the scene shows all the changes and is marked dirty and thus can be saved, but the changes aren't saved...
     
  3. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    My general go-to that seems to work in most cases is this one:

    Code (csharp):
    1. using System;
    2. using UnityEditor;
    3. using UnityEngine;
    4. using UnityEditor.SceneManagement;
    5. using Object = UnityEngine.Object;
    6.  
    7. /// <summary>
    8. /// Unity's refusing to maintain EditorUtility.SetDirty, so for the times where you need it over
    9. /// Undo or SerializableObject, this is how to actually dirty objects.
    10. /// </summary>
    11. public static class EditorFix {
    12.     public static void SetObjectDirty(Object o) {
    13.         if(Application.isPlaying)
    14.             return;
    15.  
    16.         if (o is GameObject) {
    17.             SetObjectDirty((GameObject) o);
    18.         } else if (o is Component) {
    19.             SetObjectDirty((Component) o);
    20.         }
    21.         else {
    22.             EditorUtility.SetDirty(o);
    23.         }
    24.     }
    25.  
    26.     public static void SetObjectDirty(GameObject go) {
    27.         if(Application.isPlaying)
    28.             return;
    29.  
    30.         HandlePrefabInstance(go);
    31.         EditorUtility.SetDirty(go);
    32.  
    33.         //This stopped happening in EditorUtility.SetDirty after multi-scene editing was introduced.
    34.         EditorSceneManager.MarkSceneDirty(go.scene);
    35.     }
    36.  
    37.     public static void SetObjectDirty(Component comp) {
    38.         if(Application.isPlaying)
    39.             return;
    40.  
    41.         HandlePrefabInstance(comp.gameObject);
    42.         EditorUtility.SetDirty(comp);
    43.  
    44.         //This stopped happening in EditorUtility.SetDirty after multi-scene editing was introduced.
    45.         EditorSceneManager.MarkSceneDirty(comp.gameObject.scene);
    46.     }
    47.  
    48.     // Some prefab overrides are not handled by Undo.RecordObject or EditorUtility.SetDirty.
    49.     // eg. adding an item to an array/list on a prefab instance updates that the instance
    50.     // has a different array count than the prefab, but not any data about the added thing
    51.     private static void HandlePrefabInstance(GameObject gameObject) {
    52.         var prefabType = PrefabUtility.GetPrefabType(gameObject);
    53.         if (prefabType == PrefabType.PrefabInstance) {
    54.             PrefabUtility.RecordPrefabInstancePropertyModifications(gameObject);
    55.         }
    56.     }
    57. }
    Of course, if you can use SerializedObject/Property, that's better. The API is a garbage fire, but it works really well and you get Undo and whatnot for free. Unity also maintains it actively, so you can expect it to keep working.

    Note that you can create a new SerializedObject, and work with that, instead of having to get it from a custom editor or propertydrawer or whatnot. That wasn't obvious to me:

    Code (csharp):
    1. var mySO = new SerializedObject(someObject);
    2. mySO.FindProperty("someInt").intValue = 15;
    3. mySO.ApplyModifiedProperty();
     
    Cambesa and Reahreic like this.
  4. nockieboy

    nockieboy

    Joined:
    Jul 7, 2012
    Posts:
    48
    Thanks @Baste - that 'go to' solution looks like a good catch-all. I presume it will work with prefabs in the scene of any type?

    After some more investigation, it appears the issue with the material reassignments not getting saved is pertinent to prefab instances in the scene - what appears to be prefabs of imported 3D assets, if that makes a difference. I've been doing a little research and it appears there are a variety of different types of prefab...?

    I have found another solution elsewhere where the script creates an instance of the prefab, modifies the material assignments, then replaces the original prefab. I'm hoping your solution will work too, because with thousands of prefabs in the scene this particular solution could take forever..
     
  5. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    There are different prefab types, yes. The model prefabs are strange, but I believe the essential idea of them is that if you change the imported 3D model, copies of it you have placed in the scene will change completely - so they'll get new materials and bone layouts and whatnot from the model.

    So what's probably happening is that prefab is a PrefabType.ModelPrefabInstance, and it's reverting the material to whatever material the model has in the import settings. So you'll have to do... exactly what you describe, and turn it into a normal prefab.
     
  6. flashframe

    flashframe

    Joined:
    Feb 10, 2015
    Posts:
    798
    Not sure if this is directly related to the problem you were having, but this is the thread that I arrived at after a long google search, so I'm posting in case it helps someone else.

    I was struggling with a serialized List changing size when entering and exiting play mode. My script is attached to an instance of a prefab, and is set to [ExecuteInEditMode]. In order to force unity to serialize the changes made via script, I had to add:

    Code (CSharp):
    1. PrefabUtility.RecordPrefabInstancePropertyModifications(this);
    along with the usual

    Code (CSharp):
    1. EditorSceneManager.MarkSceneDirty (this.gameObject.scene);
    2. EditorUtility.SetDirty (this);
     
    ArshakKroyan likes this.
  7. ArshakKroyan

    ArshakKroyan

    Joined:
    Mar 4, 2015
    Posts:
    32
    Thanks @flashframe.

    I hope my comment will help the ones who deal with new Prefab management system introduced in Unity 2018.3.0b1 and later.

    In my case everything works well in case I
    - change a Prefab property via code
    - use
    EditorUtility.SetDirty (this);

    - And manually apply changes onto the prefab.

    The problem arises in case I have a Prefab (later ChildPrefab) inside another Prefab (later ParentPrefab) and I want the ChildPrefab changes only be saved in ParentPrefab Instance.

    So The solution is to use
    Code (CSharp):
    1. [*]EditorSceneManager.MarkSceneDirty (this.gameObject.scene);
    2. [*]EditorUtility.SetDirty (this);
     
    BigGameCompany likes this.
  8. RLord321

    RLord321

    Joined:
    Feb 25, 2017
    Posts:
    28
    This solution worked for me, ArshakKroyan!
     
    ArshakKroyan likes this.
  9. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,338
    Since somebody liked my post from this thread earlier today: some of the info from my post is outdated as of Unity 2020 and later - maybe even earlier. Specifically, EditorUtility.SetDirty now dirties the scene of the gameobject again.

    The documentation about what you have to do now also spells out the part about prefabs, which is nice.