Search Unity

Variables are not saved with the Scene when using Custom Editor

Discussion in 'Scripting' started by Dymental, Dec 18, 2015.

  1. Dymental

    Dymental

    Joined:
    Sep 14, 2014
    Posts:
    29
    I have the Followin Code:

    Testscript.cs
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Testscript : MonoBehaviour {
    4.     public string myTextArea;
    5. }
    TestScriptEditor.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [CustomEditor(typeof(Testscript))]
    5. public class TestScriptEditor : Editor
    6. {
    7.     public override void OnInspectorGUI()
    8.     {
    9.         Testscript MyTest = (Testscript)target;
    10.         MyTest.myTextArea = EditorGUILayout.TextArea(MyTest.myTextArea);
    11.         if (GUI.changed) { EditorUtility.SetDirty(MyTest); }
    12.     }
    13. }
    When I change the value in the Inspector and save the scene (Without changing anything other).
    The variable is not saved in the instanced script.

    I guess I am overseeing something here, please help

    Edit: corrected variable
     
    Last edited: Dec 19, 2015
    JoRouss likes this.
  2. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,282
    In you code sample you should be passing the variable.into the text field, not "".

    Code (csharp):
    1. MyTest.myTextArea = EditorGUILayout.TextArea(MyTest.myTextArea);
     
  3. Dymental

    Dymental

    Joined:
    Sep 14, 2014
    Posts:
    29
    Ah forgot that with the test script, but it does not save nor puts the scene on dirty :(
     
  4. Dymental

    Dymental

    Joined:
    Sep 14, 2014
    Posts:
    29
    If I reset the script or add a gameobeject > force the Scene to be dirty > and save the scene then the variables will be saved, just not when I edit the field and don't change anything (seems like the scene data is ignoring it)
     
  5. Dymental

    Dymental

    Joined:
    Sep 14, 2014
    Posts:
    29
  6. ioSoftSmith

    ioSoftSmith

    Joined:
    May 22, 2014
    Posts:
    4
    I'm having the exact same issue but only slightly more complicated. The value I'm changing in the custom editor is a field of a nested [Serializable] class that is saved as a [SerializedField] of the target monobehaviour.
    Using setdirty does not work. The Unity editor does not detect any changes when switching scenes, nor does it save the changes if <CNTL-S> is pressed.

    But if something else in the scene causes it to be "dirty" (ie adding a new gameobject, etc.) and then the scene is saved, the value is saved.

    Example:
    Code (CSharp):
    1.  
    2. //MyBehaviour.cs  ----------------------------
    3. using UnityEngine;
    4. using System.Collections;
    5. using System;
    6.  
    7. [ExecuteInEditMode]
    8. public class MyBehaviour : MonoBehaviour
    9. {
    10.  
    11.   [SerializeField]
    12.   public MyClass SerializedClass;
    13.  
    14.   public bool MySerializedField
    15.   {
    16.      get { return SerializedClass.MySerializedField; }
    17.      set { SerializedClass.MySerializedField = value; }
    18.   }
    19.  
    20.   void Start()
    21.   {
    22.      if (SerializedClass == null)
    23.         SerializedClass = new MyClass(false);
    24.   }
    25.  
    26. }
    27.  
    28. [Serializable]
    29. public class MyClass
    30. {
    31.  
    32.   [SerializeField]
    33.   public bool MySerializedField;
    34.  
    35.   public MyClass(bool _value)
    36.   {
    37.      MySerializedField = _value;
    38.   }
    39. }
    40.  
    41.  
    42. //MyBehaviourEditor.cs -----------------------
    43. using UnityEngine;
    44. using System.Collections;
    45. using UnityEditor;
    46.  
    47. [CustomEditor(typeof(MyBehaviour))]
    48. public class MyBehaviourEditor : Editor
    49. {
    50.   private MyBehaviour m_Target { get { return (MyBehaviour)target; } }
    51.   public override void OnInspectorGUI()
    52.   {
    53.      m_Target.MySerializedField = EditorGUILayout.Toggle("Bool Value", m_Target.MySerializedField);
    54.   if (GUI.changed)
    55.      EditorUtility.SetDirty((MyBehaviour)target);
    56.   }
    57. }
    58.  
     
    Last edited: Jan 18, 2016
  7. Ian094

    Ian094

    Joined:
    Jun 20, 2013
    Posts:
    1,548
    Hey, I had the same problem a while ago.

    You'll have to mark the scene as modified by using EditorSceneManager.MarkSceneDirty

    Use the "UnityEditor.SceneManagement" & "UnityEngine.SceneManagement" namespaces :
    Code (CSharp):
    1. using UnityEditor.SceneManagement;
    2. using UnityEngine.SceneManagement;
    Then in your 'if (GUI.changed)' statement, mark the scene as modified :
    Code (CSharp):
    1. if(GUI.changed){
    2.             EditorUtility.SetDirty(myTest);
    3.             EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
    4. }
     
    andreiagmu, WaySG, Hann and 10 others like this.
  8. Immanuel-Scholz

    Immanuel-Scholz

    Joined:
    Jun 8, 2013
    Posts:
    221
    Instead of manually marking stuff dirty, you can also use SerializedObject and SerializedProperty.

    Code (csharp):
    1.  
    2. SerializedProperty myTextArea = serializedObject.FindProperty("myTextArea");
    3. EditorGUILayout.PropertyField(myTextArea, true);
    4. serializedObject.ApplyModifiedProperties();
    5.  
    Additionally to handling the dirty stuff (harhar) for you, it also fixes Undo/Redo and multi-editing.
     
    andreiagmu, Mycroft and ioSoftSmith like this.
  9. ioSoftSmith

    ioSoftSmith

    Joined:
    May 22, 2014
    Posts:
    4
    Both of your answers were extremely helpful!
    Just what I needed thanks!
     
    Mycroft likes this.
  10. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Been complaining here about the same thing. It seems like Unity's done a breaking change, and not really notified anyone. Either that, or multiscene editing introduced a bug in EditorUtility.SetDirty. Here's a fix if you want to stick to the old behaviour:

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor.SceneManagement;
    3.  
    4. public static class EditorFix {
    5.         public static void SetObjectDirty(Object o) {
    6.             EditorUtility.SetDirty(o);
    7.         }
    8.  
    9.         public static void SetObjectDirty(GameObject go) {
    10.             EditorUtility.SetDirty(go);
    11.             EditorSceneManager.MarkSceneDirty(go.scene); //This used to happen automatically from SetDirty
    12.         }
    13.  
    14.         public static void SetObjectDirty(Component comp) {
    15.             EditorUtility.SetDirty(comp);
    16.             EditorSceneManager.MarkSceneDirty(comp.gameObject.scene); //This used to happen automatically from SetDirty
    17.         }
    18.     }
    19.  

    Use EditorFix.SetObjectDirty where you'd use EditorUtility.SetDirty before. Remember to put the script in an editor folder, as your code won't compile on builds otherwise!
     
  11. bug5532

    bug5532

    Joined:
    Aug 16, 2011
    Posts:
    307
    Thank you so much! this had been bugging me for hours!
     
  12. IgorAherne

    IgorAherne

    Joined:
    May 15, 2013
    Posts:
    393
    @Baste Thank you! Really should look into Serialized property, but this was a very good save, cheers
     
  13. Discipol

    Discipol

    Joined:
    May 6, 2015
    Posts:
    83
    Hi mate, your solution doesn't work for me :| trying to have two gameobjects reference each other in one property. Your solution (or the old simple SetDirty) no longer work. I don't want to uset FindReference because I don't want to commit to a string. renamings occurs all the time!

    Do you have a solution please? Thank you!
     
  14. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Should work. Code?
     
  15. Discipol

    Discipol

    Joined:
    May 6, 2015
    Posts:
    83
    I'm an idiot. Why it didn't work is because I was using HashSet<T> and it isn't serializable. Trying to find a workaround for THAT now >.> sorry for ever doubting you Baste.
     
  16. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    No problem!

    For the HashSet, I'd recommend you to make a SerializeableHashSet<T>. It should wrap a normal HashSet, anduse a List<T> to serialize and deserialize it's data, using ISerializationCallbackReceiver. You just put all the data into the List during OnBeforeSerialize and read it into the HashSet during OnAfterDeserialize.

    It's the same way you'd serialize eg. a Dictionary or a two-dimensional array, just much easier as you're working with a simple, one-dimensional structure.
     
  17. Discipol

    Discipol

    Joined:
    May 6, 2015
    Posts:
    83
    I tried, can't see it in the inspector, setdirty doesn't work on it. Courtesy of: https://forum.unity3d.com/threads/iserializationcallbackreceiver-throwing-exceptions.329917/
    Code (CSharp):
    1. [Serializable]
    2. public class SerializableHashSet<T> : HashSet<T>, ISerializationCallbackReceiver
    3. {
    4.     [SerializeField]
    5.     private List<T> elements = new List<T>();
    6.  
    7.     // save the set to a list
    8.     public void OnBeforeSerialize()
    9.     {
    10.         elements.Clear();
    11.         elements.AddRange( this );
    12.     }
    13.  
    14.     // load set from list
    15.     public void OnAfterDeserialize()
    16.     {
    17.         Clear();
    18.         UnionWith( elements );
    19.     }
    20. }