Serialize lists that save values within the editor

So I am trying to serialize a list object so the values within the custom editor stay the same the whole time. But I am having trouble working the logistics of it out :frowning:

Here is the base code:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

[System.Serializable]
public class ObjectiveClass {
    public string title = "";
    public string description = "";
    public bool show = true;
}

[AddComponentMenu("Objective Manager"), ExecuteInEditMode]
public class ObjectiveManager : MonoBehaviour {

    [SerializeField]
    public List<ObjectiveClass>oClass = new List<ObjectiveClass>();
}

Here is the editor code:

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor(typeof(ObjectiveManager))]
public class ObjectiveManager_Editor : Editor {

    ObjectiveManager om;

    void OnEnable() {
        om = target as ObjectiveManager;
    }

    public override void OnInspectorGUI() {
        //UI
        foreach (ObjectiveClass oc in om.oClass) {
            oc.show = EditorGUILayout.Foldout(oc.show, oc.title.ToString());
            if (co.show) {
               co.title = EditorGUILayout.TextField("Title", co.title);
               co.description = EditorGUILayout.TextField("description", co.description);
            }
        }
    }
}

I always use this:

using UnityEngine;
using UnityEditor;
using System;
using System.Collections;

public static class EditorList {

    [Flags]
    public enum EditorListOption {
        None = 0,
        ListSize = 1,
        ListLabel = 2,
        Default = ListSize | ListLabel
    }

    public static void Show (SerializedProperty list, EditorListOption options = EditorListOption.Default) {
        bool showListLabel = (options & EditorListOption.ListLabel) != 0, showListSize = (options & EditorListOption.ListSize) != 0;
      
        if (showListLabel) {
            EditorGUILayout.PropertyField(list);
            EditorGUI.indentLevel += 1;
        }

        if (!showListLabel || list.isExpanded) {
            if (showListSize)
                EditorGUILayout.PropertyField(list.FindPropertyRelative("Array.size"));

            for (int i = 0; i < list.arraySize; i++)
                EditorGUILayout.PropertyField(list.GetArrayElementAtIndex(i));
        }

        if (showListLabel)
            EditorGUI.indentLevel -= 1;
    }
}

Then just in your inspector GUI

serializedObject.Update();
EditorList.Show(serializedObject.FindProperty("oClass"));
serializedObject.ApplyModifiedProperties();

SerializedProperty is generally the way to go since you get undo/redo for free!

Otherwise you will need to explicitly mark your object as dirty:

public override void OnInspectorGUI() {
    EditorGUI.BeginChangeCheck();

    //UI
    foreach (ObjectiveClass oc in om.oClass) {

        oc.show = EditorGUILayout.Foldout(oc.show, oc.title.ToString());
        if (co.show) {
           co.title = EditorGUILayout.TextField("Title", co.title);
           co.description = EditorGUILayout.TextField("description", co.description);
        }
    }

    if (EditorGUI.EndChangeCheck())
        EditorUtility.SetDirty(target);
}

You might find my reorderable list control useful:
https://bitbucket.org/rotorz/reorderable-list-editor-field-for-unity

With this you could define a custom property drawer for “ObjectiveClass” and then just use the SerializedObject overload of the reorderable list:

[CustomPropertyDrawer(typeof(ObjectiveClass))]
public class ObjectiveClassPropertyDrawer : PropertyDrawer {
    // See Unity docs...
}

private SerializedProperty listProperty;

private void OnEnable() {
    listProperty = serializedObject.FindProperty("oClass");
}

public override void OnInspectorGUI() {
    serializedObject.Update();

    ReorderableListGUI.Title("Objective List");
    ReorderableListGUI.ListField(listProperty);

    serializedObject.ApplyModifiedProperties();
}

Above code not tested, just proof of concept.

A problem relating to this which I am also having is an error that pops up

There are several possible locations for this error… you have the line number, 18 which will pinpoint the exact culprit for you.

Or I could offer a shameless plug for the Advanced Inspector… So you would never need to write a custom editor again. :stuck_out_tongue:

The culprit line is on line 18 as you said. Its the same line of code as the one in the original post above I made above.

Okay, so stick the following line above the culprit to confirm which reference is null:

if (oc == null) Debug.Log("oc is null!");

I recommend removing the CustomEditor for now.
Make sure your Serialization is working properly, from the code above it looks ok.
After that you start on the CustomEditor. As @numberkruncher mentioned, you propabbly want to use SerializedProperties to write your CustomEditor since it will give you Undo functionality and also multi-object selection.

I have searched for a few days over the internet on serialization inside an editor script, that incorporates foreach loops. But I haven’t really found anything, and the stuff I do find is scarcely documented. To be honest i dont really know how to go about using seralization within the script, I have just never needed to use it in an editor script. So the basic understanding I have come across of what I need to do is…

Use SerializedObject on the om.oClass
then Use SerializedProperty for all the values inside om.oClass?

List, Hashtable and Dictionary .NET objects are dynamic memory elements.

To serialize in Unity Inspector, you need a Array (fixed length element).

I created a Table and ListTable Class: [CodeSource]Table and ListTable editable from Inspector, similar to Dictionary.

These classes use array elements to create a list or tables. You can create database game , or list of objects from Inspector before running a game, or view objects list at runtime.

Unity explicitly supports serialization of Lists since 2.6. Check out the documentation for [SerializeField] and also this official blog post.

You’ve tried before?

You can view at runtime, but not modify in edit mode from Inspector.

You need create a list from a array, for editing from the inspector.

I use Lists all the time, and modified one from the Inspector in Edit mode literally just a moment ago. As far as the Inspector is concerned they’re essentially a drop-in replacement for an array.

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ListTester : MonoBehaviour {

   [SerializeField]
   private List<int> testList;
 
}

If you drop that on a GameObject, does it work? I wonder if you’re not running into issues with one of the other restrictions imposed by Unity’s serialization system.

I pay first, so that your code will work.

For free I do not think nobody works.

Luck.

Lists works just fine. They are serialized as if they were arrays.

And mine look cooler than yours. :stuck_out_tongue:

Not sure what you mean here, but I wrote that snippet on my home PC where I still use Unity Free.

Had the same problem, which lead to days of coding ugly code and bad design.
Now I revisited the problem and pieces magically fell into place.
This works like a charm:

In your script:

[System.Serializable]
public class GO
{
    public string key; // key and name
    public GameObject go;
}

[SerializeField]
List<GO> gos = new List<GO>();

In your Editor:

    int lineHeight = 16;
    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        BlackBoard bb = (BlackBoard)target;
        //------------------------------- gos----------------------------------//
        var gosProp = serializedObject.FindProperty("gos"); // the list

        GUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("Add New GameObj");
        if (GUILayout.Button("+", GUILayout.Height(lineHeight), GUILayout.Width(20)))
        {
            bb.AddNew();
        }
        GUILayout.EndHorizontal();

        ShowGOList(gosProp);
        serializedObject.ApplyModifiedProperties();
    }

    void ShowGOList(SerializedProperty list)
    {
        //
        for (int i = 0; i < list.arraySize; i++)
        {
            EditorGUILayout.BeginHorizontal();

            var goProp = list.GetArrayElementAtIndex(i);
            var keyProp = goProp.FindPropertyRelative("key");
            var gogoProp = goProp.FindPropertyRelative("go");

            keyProp.stringValue = EditorGUILayout.TextField(keyProp.stringValue);

            gogoProp.objectReferenceValue = EditorGUILayout.ObjectField(gogoProp.objectReferenceValue, typeof(GameObject), true) as GameObject;

            if (GUILayout.Button("-", GUILayout.Height(lineHeight), GUILayout.Width(20)))
            {
                list.DeleteArrayElementAtIndex(i);
                i -= 1;
            }

            EditorGUILayout.EndHorizontal();

        }
    }

Looks like (I left the ‘More Information’ out of code):
3574089--288472--Capture.JPG

Keypoints are:
In Editor, treat your List<> as if it is an Array (e.g. use list.DeleteArrayElementAtIndex(i);).
Don’t change properties in List directly (aside from creating new Objs). Use FindPropertyRelative() instead and change the property.

1 Like