List which is Serializable does not get values from prefab when instantiated

Hello Friends,

I created a script with a custom editor to fill a list with life.
The editor has a “add an element” button, the list contains a class which has a float and a GameObject.
When filling it out in the inspector everything works like a charm and the data is saved and everything is good.

BUT when I instantiate it, either per script or via drag & drop into the scene, the list behaves weird:

  • the length of the list is correct
  • all references I set in the editor with the list are gone (null)

I assume that there’s a mistake with handling the serialized class but I couldn’t figure out what’s wrong.

The script is complete and can be just copy/pasted to see “live” what’s weird.
Perhaps someone here has a tip?

[code disclaimer: having the editor in the same file as the class needs Unity 4.5 afaik]

[edit] context is: This is a level block for an endless scroller, managing where it spawns and which tiles could be potential successors - that’s why I’d love to solve it within an editor script and not hard code.


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

public class LevelBlock : MonoBehaviour
{
    [System.Serializable]
    public class LevelSuccessor
    {
        public GameObject SuccessorObject { get; set; }
        public float Probability { get; set; }
    }

    public GameObject startPoint;
    public GameObject endPoint;

    public List<LevelSuccessor> successorLevelBlocks;

    void Awake()
    {
        Debug.Log(successorLevelBlocks.Count);
    }

    //does not matter for this problem.
    public void InitialiseLevel(GameObject previousEndPoint)
    {
        Vector3 newPosition = previousEndPoint.transform.position + (transform.position - startPoint.transform.position);
        transform.position = newPosition;
    }

    public GameObject getRandomSuccessor()
    {
        Debug.Log(successorLevelBlocks[0].SuccessorObject + ", " + successorLevelBlocks[0].Probability);
        return successorLevelBlocks[Random.Range(0, successorLevelBlocks.Count)].SuccessorObject;
    }
}

[CustomEditor(typeof(LevelBlock))]
public class LevelBlockEditor : Editor
{
    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        LevelBlock controller = target as LevelBlock;

        float firstColumnWidth = (Screen.width * 0.55f);
        float secondColumnWidth = (Screen.width * 0.2f);
        float thirdColumnWidth = (Screen.width * 0.2f);

        GUIStyle headerLabelStyle = GUI.skin.GetStyle("boldLabel");
        headerLabelStyle.alignment = TextAnchor.MiddleCenter;

        EditorGUI.BeginChangeCheck();
        controller.startPoint = (GameObject)EditorGUILayout.ObjectField("Start Point: ", controller.startPoint, typeof(GameObject), true);
        controller.endPoint = (GameObject)EditorGUILayout.ObjectField("End Point: ", controller.endPoint, typeof(GameObject), true);

        EditorGUILayout.BeginHorizontal("Box");
        GUILayout.Label("Prefab", headerLabelStyle, GUILayout.Width(firstColumnWidth));
        GUILayout.Label("Prob.", headerLabelStyle, GUILayout.Width(secondColumnWidth));
        GUILayout.Label("Del", headerLabelStyle, GUILayout.Width(thirdColumnWidth));
        EditorGUILayout.EndHorizontal();

        for (int i = 0; i < controller.successorLevelBlocks.Count; i++)
        {
            EditorGUILayout.BeginHorizontal("Box");
            controller.successorLevelBlocks*.SuccessorObject =*
 _(GameObject)EditorGUILayout.ObjectField(controller.successorLevelBlocks*.SuccessorObject,*_
 _*typeof(GameObject),*_
 _*true,*_
 _*GUILayout*_
 _*.Width(firstColumnWidth));*_
 <em><em>controller.successorLevelBlocks_.Probability = EditorGUILayout.FloatField(controller.successorLevelBlocks*.Probability,*_</em></em>
 <em><em>_*GUILayout.Width(secondColumnWidth));*_</em></em>
 <em><em>_*if (GUILayout.Button("X", GUILayout.Width(thirdColumnWidth)))*_</em></em>
 <em><em>_*{*_</em></em>
 <em><em>_*controller.successorLevelBlocks.RemoveAt(i);*_</em></em>
 <em><em>_*}*_</em></em>
 <em><em>_*EditorGUILayout.EndHorizontal();*_</em></em>
 <em><em>_*}*_</em></em>
 <em><em>_*if (GUILayout.Button("add new potential successor"))*_</em></em>
 <em><em>_*{*_</em></em>
 <em><em>_*controller.successorLevelBlocks.Add(new LevelBlock.LevelSuccessor());*_</em></em>
 <em><em>_*}*_</em></em>
 <em><em>_*if (EditorGUI.EndChangeCheck())*_</em></em>
 <em><em>_*{*_</em></em>
 <em><em>_*serializedObject.ApplyModifiedProperties();*_</em></em>
 <em><em>_*EditorUtility.SetDirty(controller);*_</em></em>
 <em><em>_*}*_</em></em>
 <em><em>_*}*_</em></em>
<em><em>_*}*_</em></em>
<em><em>_*```*_</em></em>

Unity does not serialize custom serializable class properties, only fields. It works if you remove the default getters and setters you have put in your serializable class, I just tried it.

An easy way to see this is by disabling your custom editor, the list inspector is unable to show any of the contents of your class. The reason it works in your editor until you instantiate the prefab is that your custom editor has modified that particular instance of the list and then displays it directly without relying on the Unity serialization system, so until that instance is killed off it will have your data. As it is not serialized, it cannot be saved to disk and subsequently instantiated.

You should refer to the Serialization Best Practices - Megapost and the recent Unity blog post for more information, they are quite a good sources of documentation.