Custom Editor not saving changes

I have three scripts: Singleton, MapManager and MapManagerEditor

When I make changes in the MapManager script component, after I click to go to other scene and then come back, the values are reset!

Here is a small example:

Singleton

using UnityEngine;
using System.Collections;

public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour {
 
    public static T instance { get; private set; }

    protected virtual void Awake()
    {
        if (instance == null)
            instance = this as T;
        else
            Destroy(gameObject);
    }

    protected virtual void OnDisable()
    {
        instance = null;
    }
}

}

MapManager

using UnityEngine;
using System.Collections;

[System.Serializable]
public class MapManager : Singleton<MapManager> {

    [System.NonSerialized]
    public int lol = 0;
}

MapManagerEditor

using UnityEditor;
using System.Collections;
using UnityEditor.SceneManagement;

[CustomEditor(typeof(MapManager))]
public class MapManagerEditor : Editor
{

    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();
        MapManager mapManager = (MapManager)target;

        mapManager.lol = EditorGUILayout.IntField("lol", mapManager.lol);
}
}

Any idea how to fix this?

Fixed the problem, for anyone who has it, here is the real solution:

Use [HideInInspector] for every variable you want to change (it also serializes it).

Use [System.Serializable] in every class/struct that you are using with your variables.

Finally add this code on the bottom of OnInspectorGUI

if (GUI.changed)
        {
            EditorUtility.SetDirty(castedTarget);
            EditorSceneManager.MarkSceneDirty(castedTarget.gameObject.scene);
        }
24 Likes

FINALLY! This is the 10th article I’ve read that finally does what it says. You rock, good sir.

@ Unity, please update your docs on this -__-

I wanted to add to this. This helped me track down my problem however, the only thing of your solutions I needed to do was add the if(GUI.changed) snippet to my OnInspectorGUI method. However I also had the variables in my class set with default accesors for example

public float boo { get; set; }

however I needed to remove those so it was just

public float boo;

for some reason they hindered the process. So with those changes alone it solved my problem in case anyone tried the above and still have the issue it may be accesors.

1 Like

For future generations… this is not a good way to get your editors to serialize. I struggled with custom editors for a long time too and did a bunch of hacky garbage just to get things kind of ok. But its not this much work once you know what to focus on.

The key is to only manipulate SerializedProperties, don’t ever operate on the target’s data! Here’s a simple example…

public override void OnInspectorGUI()
{
     //do this first to make sure you have the latest version
     serializedObject.Update();
     
     //for each property you want to draw ....
     EditorGUILayout.PropertyField(serializedObject.FindProperty("boo"));

     //if you need to do something cute like use a different input type you can do this kind of thing...
     SerializedProperty specialProp = serializedObject.FindProperty("myFloatThatPretendsItsAnInt");
     specialProp.floatValue = EditorGUILayout.IntField(specialProp.floatValue as int) as float;

     //do this last!  it will loop over the properties on your object and apply any it needs to, no if necessary!
     serializedObject.ApplyModifiedProperties();
}

That’s all you need.

13 Likes

Have you tried this with a 2 dimensions array?

I have a week looking about this and can’t find anything.

@dcarnelutti

Your question doesn’t have much to do with topic and it is also bit of a necro post.

I bet that because you can’t serialize a 2D array in standard Inspector, you won’t see it in serialized object for that class either. I think you will just see null if you try to do FindProperty(“myTwoDeeArray”).

You could try to do a typical Serializable class instead that looks like two dimensional array (class with a list of arrays) and then draw that with your Custom Inspector + serializable object.

What is “castedTarget”? You don’t define it in any of your code. What should it be set to?

1 Like

This should be the first answer, i ran into this problem and this is the easiest/most elegant solution. if anyone runs into this, try this first.

1 Like

brownboot67 I love you!

1 Like

If anyone else is going insane because this isn’t working, keep in mind that he uses PropertyField.

EditorGUILayout.PropertyField(serializedObject.FindProperty("boo"));

For some reason, IntField or any others wouldn’t work for me. If you want a label beside the input field, do it like:

EditorGUILayout.PropertyField(serializedObject.FindProperty("myInt"), new GUIContent("Set myInt: "));

IntField and the other type specific fields return their corresponding type. So you have to do:

myIntProp.intValue = EditorGUILayout.IntField(myIntProp.intValue);

And you are correct there are lots of optional params for labels and styles and so on, you can read the docs if you want to sort all that out.

Hey guys, I’m having some difficulty with this.
I managed to get everything working, but after writing something in the Editor Window and hitting Enter or Tab, the value I’ve just tiped is erased.

My functions for loading as saving are working fine.
I think the problem is on the OnGUI function

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;

public class LocalizedTextEditor : EditorWindow
{
   
    public LocalizationData localizationData;
    Vector2 scrollPosition = Vector2.zero;

    [MenuItem("Window/Localized Text Editor")]
    static void Init()
    {
       
        EditorWindow.GetWindow(typeof(LocalizedTextEditor)).Show();
    }

    private void OnGUI()
    {
        scrollPosition = GUILayout.BeginScrollView(scrollPosition, true, true);
       
       
     

      
        if (localizationData != null)
        {

            SerializedObject serializedObject = new SerializedObject(this);
            serializedObject.Update();
            SerializedProperty serializedProperty = serializedObject.FindProperty("localizationData");
            EditorGUILayout.PropertyField(serializedProperty);
            serializedObject.ApplyModifiedProperties();

            if (GUILayout.Button("Save data"))
            {
                SaveGameData();
            }
        }

        if (GUILayout.Button("Load data"))
        {
            LoadGameData();
        }

        if (GUILayout.Button("Create new data"))
        {
            CreateNewData();
        }
       
        GUILayout.EndScrollView();
    }

    private void LoadGameData()
    {
        string filePath = EditorUtility.OpenFilePanel("Select localization data file", Application.streamingAssetsPath, "json");

        if (!string.IsNullOrEmpty(filePath))
        {
            string dataAsJson = File.ReadAllText(filePath);

            localizationData = JsonUtility.FromJson<LocalizationData>(dataAsJson);
        }
    }

    private void SaveGameData()
    {
        string filePath = EditorUtility.SaveFilePanel("Save localization data file", Application.streamingAssetsPath, "", "json");

        if (!string.IsNullOrEmpty(filePath))
        {
            string dataAsJson = JsonUtility.ToJson(localizationData);
            File.WriteAllText(filePath, dataAsJson);
        }
    }

    private void CreateNewData()
    {
        localizationData = new LocalizationData();
    }

}
[System.Serializable]
public class LocalizationData
{
    public LocalizationItem[] items;
}

[System.Serializable]
public class LocalizationItem
{
    public string key;
    public string value;
}

Here are the screenshots
I typed test for the first field
And after clicking on the other field, or hitting Tab / Enter it is erased

THANK YOU!!! I tried for hours and didn’t get it to work the right way. That was my last attempt and after I changed my Code to your Soloution it worked flawlessly.

Sorry for bad england.

1 Like

Maybe this is a bad idea, but I managed to get this to work:

public static void CopyFromObject(this SerializedObject serializedObject, T target)
    where T : notnull, UnityEngine.Object
{
    var it = new SerializedObject(target).GetIterator();
    it.Next(true);
    while (it.Next(false))
    {
        serializedObject.CopyFromSerializedProperty(it);
    }

    serializedObject.ApplyModifiedProperties();
}

The nice thing about this is that it lets me edit the target object directly, then save those changes back into the serializedObject without having to go through all the standard SerializedObject hoops.

I think I love you <3

1 Like

Breaking news!

After hours spent debugging, t appears that if your SerializedProperty goes out of scope, the change is lost too.

In a custom editor, you should either store the serializedProperty on OnEnable, or call ApplyModifiedProperties before the property goes out of scope.

TLDR: If you have a function in your editor that uses FindProperty and modifies said property, it should call ApplyModifiedProperties itself

I ran into this problem and for my specific case I used this code to fix my problem.

[HideInInspector]
[SerializeField] GameObject partSys;

I needed the GameObject to be Serialized and I needed it to be hidden in the inspector for organizational sake.
It’s simple but it’s what worked for me.

My particular issue is that i was maintaining a reference for ‘baseClassSerializedObject’ and calling
serializedObject.Update()
DrawPropertiesExcluding(‘baseClassSerializedObject’, …)
serializedObject.ApplyModifiedProperties()

I should’ve either been passed serializedObjectr into the draw method OR called update and apply on the baseclassSO.

Hope this helps!