- Home /
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:
Create a C# script called SerializationTest within an 'Editor' folder
Copy and paste the code bellow into the script and save it
Open the Editor Window at: Window > Serialization Test
Click some of the 'Add Node' buttons to add more nodes
With the window still open, enter playmode. Make sure you click on the window to select it again.
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<Node> nodes = new List<Node>(); //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<int> nodeChildrenIndexes = new List<int>(); //Show the menu item to open the window [MenuItem("Window/Serialization Test")] public static void Init () { SerializationTest window = (SerializationTest)EditorWindow.GetWindow<SerializationTest>() 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[i]); } } //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[i]); } 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<int> nodeChildrenIndexes = new List<int>(); }
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?
Answer by the_genius · Sep 01, 2016 at 06:10 PM
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:
Just before Unity serializes the object (editor window class), store a copy of the static list as a public list.
When Unity serializes it, the static list becomes empty, but the public copy remains correct.
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!
Answer by RambleHouseGames · Sep 01, 2016 at 11:24 PM
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.