Editor Window Serialization not Working as Expected

Hello,

I though I understood Unity’s Serialization quite well, but it looks like I was wrong.

I have written some simple code to show the exact isssue bellow.

I am creating a List of Classes called Nodes which each contain a list of Nodes (which in turn each contain a List of Nodes etc) within a Unity Editor Window.
I am storing references to all of the created Nodes in a single static serialized List in the window’s class and using the indexes of Nodes in this list to store the lists of child nodes within each node, if that makes sense. This means there is only one Node List and each Node contains a int List where the integers are indexes for nodes in the Node List. I am doing it like this to avoid Serialization Depth issues.

When I open the window and create Nodes, everything works perfectly. But when I enter playmode (with the window still open) the List variable is suddenly empty. I think this is a problem with the Nodes in the List not being serialized correctly.

If this isn’t very clear follow the steps bellow to reproduce the issue:

  1. Create a C# script called SerializationTest within an ‘Editor’ folder

  2. Copy and paste the code bellow into the script and save it

  3. Open the Editor Window at: Window > Serialization Test

  4. Click some of the ‘Add Node’ buttons to add more nodes

  5. With the window still open, enter playmode. Make sure you click on the window to select it again.

  6. In the console you will see the Messages saying there are 0 created nodes and bellow them there will be ArgumentOutOfRangeException errors as all of the references to the nodes have been lost.

     using UnityEngine;
    

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

     [Serializable]
     public class SerializationTest : EditorWindow {
    

    //This is a list of ALL the created nodes including child nodes, child child nodes, child child child nodes, etc
    [SerializeField]
    public static List nodes = new List();

    //This is a list of the indexes of all the nodes (in the list above) which are a child of this window only (not nodes which are a child of other nodes)
    [SerializeField]
    public List nodeChildrenIndexes = new List();

    //Show the menu item to open the window
    [MenuItem(“Window/Serialization Test”)]
    public static void Init ()
    {
    SerializationTest window = (SerializationTest)EditorWindow.GetWindow() as SerializationTest;
    window.Show();
    }

    //Draw the GUI
    void OnGUI ()
    {
    //The very top line of the window
    GUILayout.BeginHorizontal();
    GUILayout.Label(“Nodes:”);
    GUILayout.FlexibleSpace();
    if (GUILayout.Button(“Add Node”)) // Button to add a new node (which is a child of the window)
    {
    Node newNode = new Node(); // Creates the new node
    nodes.Add(newNode); // Adds it to the list of all nodes
    nodeChildrenIndexes.Add(nodes.Count - 1); // Adds it’s index (in the list of all nodes) to this window’s nodes list
    }
    GUILayout.EndHorizontal();

     //Draws all of the child nodes bellow (this is where we get problems when the nodes list is not being serialized as the index is outside its range)
     Debug.Log("There are " + nodes.Count + " existing nodes");
     foreach (int i in nodeChildrenIndexes)
     {
         Debug.Log("Trying to find node at index " + i);
         DrawNode(nodes*);*
    

}
}

//This method draws a node and its children
static void DrawNode (Node nodeToDraw)
{
//Draw this node
GUILayout.BeginHorizontal();
nodeToDraw.nodeName = EditorGUILayout.TextField(nodeToDraw.nodeName); /// The text field to change the name of the node
if (GUILayout.Button(“Add Child”)) // Button to add a new node (which is a child of node currently being drawn)
{
Node newNode = new Node(); // Creates the new node
nodes.Add(newNode); // Adds it to the list of all nodes
nodeToDraw.nodeChildrenIndexes.Add(nodes.Count - 1); // Adds it’s index (in the list of all nodes) to this nodes’s child nodes list
}
GUILayout.EndHorizontal();
//Draw this node’s children by calling this method again in a cycle
GUILayout.BeginHorizontal();
GUILayout.Space(20);
GUILayout.BeginVertical();
foreach (int i in nodeToDraw.nodeChildrenIndexes)
{
DrawNode(nodes*);*
}
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
}
//This the class storing the data of each node
[Serializable]
public class Node
{
//Store the nodes name
[SerializeField]
public string nodeName = “Node Name”;

//Store the indexes of the child nodes
[SerializeField]
public List nodeChildrenIndexes = new List();
}
I think the int List variables are being Serialized correctly but the Node List variable isn’t.
Thank you.
Update:
I found this:
_*https://docs.unity3d.com/Manual/script-Serialization.html*_
which says static fields cannot be serialized. If I change the static fields to public in the code above, it works. But, I need to be able to access the static List in other editor windows so is there a way around this?

So the problem was, Unity won’t let you serialize static variables. But, Unity will let you serialize public variables. I require a static variables for this so I can’t just change it to public. There is quite an obvious way around this:

  1. Just before Unity serializes the object (editor window class), store a copy of the static list as a public list.
  2. When Unity serializes it, the static list becomes empty, but the public copy remains correct.
  3. So, just after Unity finsihes deserializing, correct the static list by making it a copy of the public list.

And, it turns out this is quite simple to do.

Change the Editor Window class declaration line to this:

public class SerializationTest : EditorWindow, ISerializationCallbackReceiver

This allows you to add OnBeforeSerialize and OnAfterDeserialize methods which are called by Unity.

Then add the public variable for the list which we will use as a copy:

[SerializeField]
public List<Node> copyOfNodes = new List<Node>();

Then add the following methods to copy the static list before serializing and correct it after deserializing:

public void OnBeforeSerialize()
{
    copyOfNodes = nodes;
}

public void OnAfterDeserialize()
{
    nodes = copyOfNodes;
}

Perfect!

I think you need to get rid of the initializers on the SerializeFields:

replace:
public static List nodes = new List();
with:
public static List nodes;

My guess is that the Editor is populating your list correctly and then it’s getting replaced with an empty list at runtime.

I haven’t reproduced the issue on my machine, so if that doesn’t work let me know and I’ll look further into it.