Search code examples
c#unity-game-engine

Prefab changes not saved for Custom Editor fields that store data for a complex object


I created a custom component that uses a custom editor.

In this editor I set a number of fields. When saving the prefab, only some of those fields are saved. Specifically, normal fields like int, string, float save just fine. But I have some fields that store data for the following structure:

    [System.Serializable]
    public class MovementVariableModifierData
    {
        [ItemType]
        public string modifierItemID = "";
        public int modifierItemIdx;

        [SerializeField]
        public Dictionary<string, float> multipliers = new Dictionary<string, float>();

        [SerializeField]
        public Dictionary<string, float> addends = new Dictionary<string, float>();


    }

Furthermore, the class for which the editor is being used is actually storing data in an array of that structure, so into a MovementVariableModifierData[] Modifiers array. e.g.:

public class MyClass
{
    Modifiers = new MovementVariableModifierData[0];
}

In the custom inspector, the fields are modified like so inside the OnInspectorGUI method:

// Custom Editor Code:

// read old values and copy them to new list
tmpModifiers = new List<MovementVariableModifierData>(Modifiers)

for (int i = 0; i < tmpModifiers.Count; i++)
{
    // MODIFY Int and String - these get saved
    tmpModifiers[i].modifierItemIdx = EditorGUILayout.Popup(new GUIContent("Item ID:"), tmpModifiers[i].modifierItemIdx, itemTypes);
    tmpModifiers[i].modifierItemID = itemTypes[tmpModifiers[i].modifierItemIdx];

    // MODIFY Dictionary - Doesn't get saved
    // Set default value
    if (!tmpModifiers[i].multipliers.ContainsKey("Key1")) tmpModifiers[i].multipliers["Key1"] = 1.0f;

    tmpModifiers[i].multipliers["Key1"] = EditorGUILayout.FloatField(new GUIContent("Key 1:"), tmpModifiers[i].multipliers["Key1"]);

    // There's many more of these in the loop but they all use this exact pattern.

}

Modifiers = tmpModifiers.ToArray();

So basically the Custom Editor's target class has a MovementVariableModifierData[] array, and it contains some Dictionary<string, float> fields that I'm trying to update and save via the custom editor.

The data seems to update just fine when I Debug.Log() it, and it shows up correctly in the inspector, but when i save the prefab and then close and reopen it, the values stored in the dictionaries reset. The value stored in the int/string field of modifierItemID and modifierItenIdx, and adding new MovementVariableModifierData entries to the Modifiers array also saves successfully (but again, not their dictionary values).

One thing I noticed is that while the returned values are correct, on the next call to OnInspectorGUI() the dictionaries have reset.

I am wracking my brain and can't for the life of me figure out why only the dictionaries reset but not the int and string. I have marked them and the class they're part of as [Serializable]. I have also tried using List<KeyValuePair<string, float>> instead of dictionaries to store the data and still have the issue.


Solution

  • As derHugo mentioned Dictionary and KeyValuePair are not Serializable. Create custom modifiers and addends using a record that are Serializable.

    [Serializable]
    public class MovementVariableModifierData
    {            
        private string _modifierItemID = "";
        public string ModifierItemID => _modifierItemID;
        private int _modifierItemIdx;
        public int ModifierItemIdx => _modifierItemIdx;
        private Multiplier[] _multipliers;
        public Multiplier[] Multipliers => _multipliers;
        private Addend[] _addends;
        public Addend[] Addends => _addends;
        public MovementVariableModifierData(string modifierItemID, 
    int modifierItemIdx, Multiplier[] multipliers, Addend[] addends)
        {
            _modifierItemID = modifierItemID;
            _modifierItemIdx = modifierItemIdx;
            _multipliers = multipliers;
            _addends = addends;
        }
    }
    [Serializable]
    public record class Addend(string Key, float Value) { }
    
    [Serializable]
    public record class Multiplier(string Key, float Value) { }