Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Custom Editor losing settings on Play

Discussion in 'Immediate Mode GUI (IMGUI)' started by Joshua_Falkner, Apr 5, 2012.

  1. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    I asked this out on answers, but I'll prob have better luck finding out if this is a bug here.

    I think the behavior I'm seeing is a bug, but I thought I'd ask it here first in case I'm just missing something. When writing my custom editors, all of my changes are being discarded when I hit the "play" button for any object I need 'type' I need to instantiate. I'll keep the code simple:

    First, I have a class called HiddenObject:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class HiddenObject {
    5.     public GameObject hiddenObj { get; set; }
    6. }
    Next, here's the script I'll attach to an object in my Scene:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Inventory : MonoBehaviour {
    5.  
    6.     public HiddenObject hiddenObject = new HiddenObject();
    7. }
    Finally, here's my custom editor script:

    Code (csharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System.Collections;
    4.  
    5. [CustomEditor(typeof(Inventory))]
    6. public class InventoryEditor : Editor {
    7.     private Inventory _inv;
    8.  
    9.     void Awake()
    10.     {
    11.         _inv = (Inventory)target;
    12.     }
    13.  
    14.     public override void OnInspectorGUI()
    15.     {        
    16.         _inv.hiddenObject.hiddenObj = (GameObject)EditorGUILayout.ObjectField("Hidden Object", _inv.hiddenObject.hiddenObj, typeof(GameObject), true);
    17.         if(GUI.changed)
    18.              EditorUtility.SetDirty(_inv);    
    19.     }  
    20. }
    When I click on my Object in the Scene Hierarchy I can see the Custom Inspector displaying properly and I can click on the Game Object Field and select something or drag a game object to it. This all works correctly.

    The moment I click the "Play" button, it's all nulled out though. Clicking stop doesn't bring back my changes, everything has been nulled out.

    I'm certain this has to do with the fact that I'm having to instantiate the HiddenObject (new HiddenObject()) but not exactly sure how else I'm supposed to do it. Am I missing something, or is this a bug?
     
    IgorAherne likes this.
  2. dodo

    dodo

    Joined:
    Jul 13, 2011
    Posts:
    49
    This is not a bug, I had the same problems when I was writing my first unity editor code. The solution is simple, but I feel the need to describe what unity does when you hit the play button:

    When you hit the play button in the editor, all the objects in the active scene are serialized and saved, so that unity can deserialize and return them to their original state when you stop the execution in the editor. Unity also creates copies of all objects in the scene, so the changes you do during play mode change the copies, not the original objects in the scene. During this copy process it deserializes the objects with the data it saved just before copying, so no visible change is done on the objects. But not in your case!

    The problem in your case is the object that you are creating(HiddenObject) is not serializable. So, when you hit play, unity serializes the gameobject in you scene, which has the Inventory script on it, but the hiddenObject member of your Inventory class is not serializable, so it's not saved. And obviously it can't be deserialized either, since it wasn't saved in the first place. So, your hiddenObject is set to null whenever unity deserializes your Inventory class, which is when you hit play or stop. Your object will also be set to null when you save your scene, close the scene and open it again.

    All the scripts which inherit from MonoBehaviour are serializable, but your custom classes are not. To inform unity that you want your class to be serialized you have to use the [System.Serializable] attribute:

    Code (csharp):
    1.  
    2. [System.Serializable]
    3. public class HiddenObject
    4.  
    Also, unity only serializes the public members in your class, if you want your private members to be serialized too, you should inform unity with the [SerializeField] attribute:

    Code (csharp):
    1.  
    2.     [SerializeField]
    3.     private int myMember = 0;
    4.  
    On the contrary, there might be cases where you don't want a public member to be serialized, in that case you can use the [NonSerialized] attribute:

    Code (csharp):
    1.  
    2.     [NonSerialized]
    3.     public int myMember = 0;
    4.  
    Hope this helps.
     
  3. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    Hey Dodo - thanks for the detailed reply! This all makes perfect sense, and I honestly thought this would work, but after adding in [System.Serializable] I'm still getting the same nulling out behavior. Reading up on Serializable led me to ScriptableObject and I made the following changes, but unfortunately no matter what I try here I can't get the custom editor to persist my changes:

    Custom class:

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. using System.Collections;
    4.  
    5. [System.Serializable]
    6. public class HiddenObject : ScriptableObject {
    7.  
    8.     public GameObject hiddenObj { get; set; }
    9.  
    10. }
    Inventory Script:

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. using System.Collections;
    4.  
    5. public class Inventory : MonoBehaviour {
    6.  
    7.     public HiddenObject hiddenObject;
    8.  
    9.     void Awake()
    10.     {
    11.         hiddenObject = ScriptableObject.CreateInstance(typeof(HiddenObject)) as HiddenObject;
    12.     }
    13. }
    Didn't need to change the InventoryEditor script.

    Am I digging in the wrong direction on this one?
     
  4. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    Ok, figured this out - Dodo was right on the money - the only thing I had wrong was that I didn't need to get; set; each of my variables in my custom class. As soon as I removed that everything worked correctly.
     
  5. dodo

    dodo

    Joined:
    Jul 13, 2011
    Posts:
    49
    Glad I could be of help :)

    About your last problem, as you've already noticed, if you use the default property, unity can't serialize your member since the backing field for the default property is private. So, if you want to use properties and need the backing field of the property to be serialized, you should define the field yourself, and add a [SerializeField] attribute:

    Code (csharp):
    1.  
    2. [SerializeField]
    3. private int myField;
    4.  
    5. public int MyField
    6. {
    7.     get
    8.     {
    9.         return myField;
    10.     }
    11.     set
    12.     {
    13.         myField = value;
    14.     }
    15. }
    16.  
    Cheers
     
    astracat111 and lauraaa like this.
  6. hoesterey

    hoesterey

    Joined:
    Mar 19, 2010
    Posts:
    659
    Thanks so much for this thread!

    I've been battling with this all day. I had most of my stuff saving but not my lists. Didn't know the .Dirty call.

    The unity docs for customizing the editor could use some love and some more examples.
     
  7. Jean-Fabre

    Jean-Fabre

    Joined:
    Sep 6, 2007
    Posts:
    429
    Thanks for this thread, very helpful! indeed the doc should really gets updated on that topic.
     
  8. Bloody-Swamp

    Bloody-Swamp

    Joined:
    Jul 30, 2012
    Posts:
    38
    Damn that was so easy... but my head still hurts.

    Thanks Dodo for your answer and Joshua_Falkner for this thread!
    Cheers! :D
     
  9. JoshOClock

    JoshOClock

    Joined:
    Dec 8, 2010
    Posts:
    107
    I ran into this problem as well. For me the answer was also the .Dirty call.

    I found that I couldn't just set the GameObject as .Dirty but had to use the GetComponent to get the script that changed and then THAT worked.
     
  10. xJavier

    xJavier

    Joined:
    May 10, 2014
    Posts:
    8
    Hi, hope someone can help me, I' running thru this and I've not been able to solve it.... here's my code..

    Player Controller:

    Code (CSharp):
    1. public class PlayerController : MonoBehaviour {
    2.  
    3.  
    4.     #region publicMembers
    5.     [Range (0.1f,700f)]
    6.     public float jumpForce;
    7.     [Range (1,3)]
    8.     public float walkingSpeed;
    9.     [Range (4,30)]
    10.     public float runningSpeed;
    11.     public bool alwaysRunning;
    12.     public bool runAutomatically;
    13.     [Range (0.01f,1f)]
    14.     public float runAutomaticallySpeed;
    15. //    [HideInInspector,SerializeField]
    16.     [SerializeField]
    17.     public bool canStackJump;
    18.     [HideInInspector]
    19.     public int howManyJumpStacks;
    20.     [HideInInspector]
    21.     public float jumpForceIncrement;
    22.     [HideInInspector]
    23.     public float timeToRestartJumpStack;
    24. ..............
    and the Editor:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [CustomEditor(typeof(PlayerController))]
    5. [CanEditMultipleObjects]
    6. [System.Serializable]
    7. public class PlayerControllerUnityEditor : Editor {
    8.     [SerializeField]
    9.     PlayerController _playerController;
    10.  
    11.     public bool stackJump;
    12.  
    13.  
    14.     void OnEnable() {
    15.         // Setup serialized property
    16.         _playerController = (PlayerController)target;
    17.  
    18.     }
    19.  
    20.     public override void OnInspectorGUI() {
    21.         DrawDefaultInspector();
    22.         EditorGUILayout.BeginFadeGroup (1);
    23.             stackJump = EditorGUILayout.BeginToggleGroup ("Stack Jump",stackJump);
    24.             EditorGUILayout.BeginHorizontal ();
    25.                 _playerController.canStackJump = stackJump;
    26.             EditorGUILayout.EndHorizontal ();
    27.             EditorGUILayout.BeginVertical ();
    28.                 if (_playerController.canStackJump) {
    29.             EditorGUILayout.BeginHorizontal();
    30.                     GUILayout.Label("How Many Jump Stacks");
    31.                     _playerController.howManyJumpStacks = EditorGUILayout.IntSlider(_playerController.howManyJumpStacks,1,5);
    32.             EditorGUILayout.EndHorizontal();
    33.             EditorGUILayout.BeginHorizontal();
    34.                     GUILayout.Label("Jump Force Increase");
    35.                     _playerController.jumpForceIncrement = EditorGUILayout.Slider(_playerController.jumpForceIncrement,0.01f,100f);
    36.             EditorGUILayout.EndHorizontal();
    37.             EditorGUILayout.BeginHorizontal();
    38.                     GUILayout.Label("Time To Restart Stack");
    39.                     _playerController.timeToRestartJumpStack = EditorGUILayout.Slider(_playerController.timeToRestartJumpStack,0.01f,5.0f);
    40.             EditorGUILayout.EndHorizontal();
    41.                 }
    42.             EditorGUILayout.EndVertical ();
    43.             EditorGUILayout.EndToggleGroup ();
    44.         EditorGUILayout.EndFadeGroup ();
    45.  
    46.         if (GUI.changed) {
    47.             EditorUtility.SetDirty(_playerController);  
    48.             serializedObject.ApplyModifiedProperties ();
    49.         }
    50.     }
    51. }
    What I'm trying to do is to set the value of the property "canStackJump" and if it's true then display 3 Sliders with different properties, hope someone can help me , thanks in advanced.
     
  11. ArchonLight

    ArchonLight

    Joined:
    Aug 12, 2014
    Posts:
    6
    xJavier, why didn't you start your own thread?

    Are you getting an error?
     
  12. xJavier

    xJavier

    Joined:
    May 10, 2014
    Posts:
    8
    Well I'm seeing the same behavior thats why I didn't create a new thread,

    Nop, I'm not getting any error, I just check a box in the editor and when I hit the play button, the checkbox is unchecked.
     
  13. xJavier

    xJavier

    Joined:
    May 10, 2014
    Posts:
    8
    Nevermind, I was able to solve it,


    Code (CSharp):
    1.  
    2.     void OnEnable() {
    3.         // Setup serialized property
    4.         _playerController = (PlayerController)target;
    5.         stackJump = _playerController.canStackJump;
    6.  
    7.     }
    not sure if it's the best way but it's working now. thanks
     
  14. kingcoyote

    kingcoyote

    Joined:
    Jun 15, 2014
    Posts:
    1
    I apologize for dredging up an old thread, but I came across the same issue and after figuring out how to get past it, I thought I should share what I found.

    When using a custom editor, you have to call EditorUtility.SetDirty(target) if you are not also using DrawDefaultInspector. The default inspector will handle the .SetDirty for it's fields just fine, but if you create a new inspector and completely negate the default inspector, you must call .SetDirty for anything to be saved.
     
    SuppleTeets, Fuestrine, rus89 and 7 others like this.
  15. dude4004

    dude4004

    Joined:
    Jul 15, 2014
    Posts:
    181
    kingcoyote, thanks it worked perfectly!
    I just used EditorUtility.SetDity("name of my component"); and it saved correctly without reseting when I was clicking Play button!
     
    liphttam1 and astracat111 like this.
  16. 37daves

    37daves

    Joined:
    Apr 14, 2014
    Posts:
    1
    Just wanted to say thank you to kingcoyote, I have been messing around with custom inspectors and have been having the same problem for hours. I have read similar answers on other threads but for some reason as soon as I read your post I got everything working in a few minutes. Thanks!
     
  17. N-Dream-AG

    N-Dream-AG

    Joined:
    Jul 27, 2015
    Posts:
    16
    I'll add that I had to SetDirty in order to solve the issue for me even though I did user DrawDefaultInspector.
     
  18. ardiawanbagusharisa

    ardiawanbagusharisa

    Joined:
    Oct 26, 2015
    Posts:
    9
    Thanks! this is still useful even in 2017. *or maybe I just too noob*
    Anyway, I want to share my problem too so anyone with a similar problem can get through it.

    Problem: I want to update the values on the fields on the inspector based on the hardcoded value in a script (In Edit mode).
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. public class PlayerPropertyTest : MonoBehaviour {
    6.     PlayerScript player;
    7.     public float damage = 10f;
    8.     public float health = 100f;
    9.     public float speed = 5f;
    10. }
    11.  
    But, as you can see it doesn't updates anything.
    Capture.PNG

    So, I just update the same script to be like this:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. [ExecuteInEditMode]
    5. public class PlayerPropertyTest : MonoBehaviour {
    6.     PlayerScript player;
    7.     public float damage = 10f;
    8.     public float health = 100f;
    9.     public float speed = 5f;
    10.  
    11.     void OnEnable() {
    12.         player = ScriptableObject.CreateInstance<PlayerScript>();
    13.         SerializedObject so = new UnityEditor.SerializedObject(player);
    14.         SerializedProperty spdamage = so.FindProperty("_damage");
    15.         SerializedProperty sphealth = so.FindProperty("_health");
    16.         SerializedProperty spspeed = so.FindProperty("_moveSpeed");
    17.  
    18.         damage = spdamage.floatValue;
    19.         health = sphealth.floatValue;
    20.         speed = spspeed.floatValue;
    21.     }
    22. }
    23.  
    24. public class PlayerScript : ScriptableObject {
    25.     public float _damage = 10f;
    26.     public float _health = 100f;
    27.     public float _maxHealth = 100f;
    28.     public float _attackFrequency = 1f;
    29.     public float _attackRange = 1f;
    30.     public float _moveSpeed = 5f;
    31. }
    And voila!
    Capture1.PNG
     
  19. Jonesy19

    Jonesy19

    Joined:
    Mar 24, 2013
    Posts:
    9
    Thanks for pointing this out. It's exactly what I needed and I know it saved me a ton of headaches!