Custom Editor losing settings on Play

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:

using UnityEngine;
using System.Collections;

public class HiddenObject {
    public GameObject hiddenObj { get; set; }
}

Next, here’s the script I’ll attach to an object in my Scene:

using UnityEngine;
using System.Collections;

public class Inventory : MonoBehaviour {

    public HiddenObject hiddenObject = new HiddenObject();
}

Finally, here’s my custom editor script:

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor(typeof(Inventory))]
public class InventoryEditor : Editor {
    private Inventory _inv;

    void Awake()
    {
        _inv = (Inventory)target;
    }

    public override void OnInspectorGUI()
    {        
        _inv.hiddenObject.hiddenObj = (GameObject)EditorGUILayout.ObjectField("Hidden Object", _inv.hiddenObject.hiddenObj, typeof(GameObject), true);
        if(GUI.changed)
             EditorUtility.SetDirty(_inv);    
    }   
}

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?

1 Like

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:

[System.Serializable]
public class HiddenObject

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:

    [SerializeField]
    private int myMember = 0;

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:

    [NonSerialized]
    public int myMember = 0;

Hope this helps.

14 Likes

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:

using UnityEngine;

using System.Collections;

[System.Serializable]
public class HiddenObject : ScriptableObject {

    public GameObject hiddenObj { get; set; }

}

Inventory Script:

using UnityEngine;

using System.Collections;

public class Inventory : MonoBehaviour {

    public HiddenObject hiddenObject;

    void Awake()
    {
        hiddenObject = ScriptableObject.CreateInstance(typeof(HiddenObject)) as HiddenObject;
    }
}

Didn’t need to change the InventoryEditor script.

Am I digging in the wrong direction on this one?

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.

3 Likes

Glad I could be of help :slight_smile:

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:

[SerializeField]
private int myField;

public int MyField
{
    get
    {
        return myField;
    }
    set
    {
        myField = value;
    }
}

Cheers

2 Likes

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.

Thanks for this thread, very helpful! indeed the doc should really gets updated on that topic.

Damn that was so easy… but my head still hurts.

Thanks Dodo for your answer and Joshua_Falkner for this thread!
Cheers! :smile:

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.

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:

public class PlayerController : MonoBehaviour {


    #region publicMembers
    [Range (0.1f,700f)]
    public float jumpForce;
    [Range (1,3)]
    public float walkingSpeed;
    [Range (4,30)]
    public float runningSpeed;
    public bool alwaysRunning;
    public bool runAutomatically;
    [Range (0.01f,1f)]
    public float runAutomaticallySpeed;
//    [HideInInspector,SerializeField]
    [SerializeField]
    public bool canStackJump;
    [HideInInspector]
    public int howManyJumpStacks;
    [HideInInspector]
    public float jumpForceIncrement;
    [HideInInspector]
    public float timeToRestartJumpStack;
..............

and the Editor:

using UnityEngine;
using UnityEditor;

[CustomEditor(typeof(PlayerController))]
[CanEditMultipleObjects]
[System.Serializable]
public class PlayerControllerUnityEditor : Editor {
    [SerializeField]
    PlayerController _playerController;

    public bool stackJump;
  

    void OnEnable() {
        // Setup serialized property
        _playerController = (PlayerController)target;

    }

    public override void OnInspectorGUI() {
        DrawDefaultInspector();
        EditorGUILayout.BeginFadeGroup (1);
            stackJump = EditorGUILayout.BeginToggleGroup ("Stack Jump",stackJump);
            EditorGUILayout.BeginHorizontal ();
                _playerController.canStackJump = stackJump;
            EditorGUILayout.EndHorizontal ();
            EditorGUILayout.BeginVertical ();
                if (_playerController.canStackJump) {
            EditorGUILayout.BeginHorizontal();
                    GUILayout.Label("How Many Jump Stacks");
                    _playerController.howManyJumpStacks = EditorGUILayout.IntSlider(_playerController.howManyJumpStacks,1,5);
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.BeginHorizontal();
                    GUILayout.Label("Jump Force Increase");
                    _playerController.jumpForceIncrement = EditorGUILayout.Slider(_playerController.jumpForceIncrement,0.01f,100f);
            EditorGUILayout.EndHorizontal();
            EditorGUILayout.BeginHorizontal();
                    GUILayout.Label("Time To Restart Stack");
                    _playerController.timeToRestartJumpStack = EditorGUILayout.Slider(_playerController.timeToRestartJumpStack,0.01f,5.0f);
            EditorGUILayout.EndHorizontal();
                }
            EditorGUILayout.EndVertical ();
            EditorGUILayout.EndToggleGroup ();
        EditorGUILayout.EndFadeGroup ();

        if (GUI.changed) {
            EditorUtility.SetDirty(_playerController);  
            serializedObject.ApplyModifiedProperties ();
        }
    }
}

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.

xJavier, why didn’t you start your own thread?

Are you getting an error?

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.

Nevermind, I was able to solve it,

    void OnEnable() {
        // Setup serialized property
        _playerController = (PlayerController)target;
        stackJump = _playerController.canStackJump;

    }

not sure if it’s the best way but it’s working now. thanks

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.

11 Likes

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!

2 Likes

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!

I’ll add that I had to SetDirty in order to solve the issue for me even though I did user DrawDefaultInspector.

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).

using UnityEngine;
using UnityEditor;

public class PlayerPropertyTest : MonoBehaviour {
    PlayerScript player;
    public float damage = 10f;
    public float health = 100f;
    public float speed = 5f;
}

But, as you can see it doesn’t updates anything.
3283891--254098--Capture.PNG

So, I just update the same script to be like this:

using UnityEngine;
using UnityEditor;

[ExecuteInEditMode]
public class PlayerPropertyTest : MonoBehaviour {
    PlayerScript player;
    public float damage = 10f;
    public float health = 100f;
    public float speed = 5f;

    void OnEnable() {
        player = ScriptableObject.CreateInstance<PlayerScript>();
        SerializedObject so = new UnityEditor.SerializedObject(player);
        SerializedProperty spdamage = so.FindProperty("_damage");
        SerializedProperty sphealth = so.FindProperty("_health");
        SerializedProperty spspeed = so.FindProperty("_moveSpeed");

        damage = spdamage.floatValue;
        health = sphealth.floatValue;
        speed = spspeed.floatValue;
    }
}

public class PlayerScript : ScriptableObject {
    public float _damage = 10f;
    public float _health = 100f;
    public float _maxHealth = 100f;
    public float _attackFrequency = 1f;
    public float _attackRange = 1f;
    public float _moveSpeed = 5f;
}

And voila!
3283891--254099--Capture1.PNG

Thanks for pointing this out. It’s exactly what I needed and I know it saved me a ton of headaches!