- Home /
Problem using a data structure created in editor mode, will not serialize?
Hey folks,
I searched and searched but couldn't find any solution yet. I have a custom EditorWindow script. Within this script, in Editor mode, I create a structure which is pretty much a linked list of nodes.
[Serializable]
public abstract class Node {
List<Node> g_Children;
...
}
[Serializable]
public class NodeA : Node {
// implementation of abstract methods and such
// some use of inherited protected members from base class
}
Some object, has a reference to this created List:
[Serializable]
public class SomeObject {
[SerializableField]
Node tree;
...
}
Within the EditorWindow, a Node is created, with children. This is set as the tree
member value of the SomeObject. On play, the tree
within SomeObject
turns to be null, which clearly indicates the tree wasn't serialized. I was wondering what is the "Unity" work around for this. Making the Node
class non-abstract is not an option, for the main implementation of this class is within its subclasses. The main idea is that tree
is a polymorphic linked-list. Any help will be greatly appreciated it. Thanks.
Update:
Ok, I changed the structure of the code a bit based on the ScriptableObject suggestion by @Bunny83
Holder class:
public class SomeClass : MonoBehaviour {
[SerializableField]
private Node tree;
...
}
[Serializable]
public abstract class Node : ScriptableObject {
[SerializableField]
List<Node> Children;
...
}
[Serializable]
public class NodeA : Node {
// implements abstract methods
}
I am creating the instances with ScriptableObject.CreateInstance(typeof(NodeA))
Here is a capture from my Inspector... NPCSequence is the subclass of NPCNode which is the scriptable object from my example:
Even after applying changes to my prefab, the ScriptableObject dissapears on play.
I am just following the examples here: Unity Serialization Blog
Update
Ok, I got it to persist on play mode. What I did:
1) Use ScriptableObject for the base (abstract) Node class. 2) Create each node in the tree with CreateInstance 3) Save the Asset using AssetDatabase.CreateAsset with unique name
That's it. Thanks.
Answer by Bunny83 · Feb 25, 2018 at 10:58 PM
Custom datastructures do not support polymorphism when it comes to serialization. Read the script serialization page carefully. Custom serializable classes or structs are always serialized based on the field type. So in your case your field type is Node. So when deserializing the field "tree" it would actually contain an instance of "Node". Since it's an abstract class you will run into countless trouble. Furthermore you can't have circular references to custom serializable classes. Custom serializable classes and structs are serialized "inlide" just as if they are structs. The only entities in the serialization system that do support inheritance and polymorphism are MonoBehaviours and ScriptableObjects. Those can be serialized as standalong assets. References to such objects can be serialized as actual references.
Following your advise and the Unity Serialization forum, I changed the implementation (updated above)... the serialized object keeps disappearing in play mode. Any advise?
Well, it's not clear how you actually create those objects. First of all ScriptableObject instances have to be created with ScriptableObject.CreateInstance. They can't be created with "new", just like $$anonymous$$onoBehaviours. Also when you want to store the scriptableobject instances inside a scene (so they have to be referenced from a $$anonymous$$onoBehaviour in the scene) you have to use Undo.RegisterCreatedObjectUndo to make Unity actually serialize the object with the scene.
If you want to reference the scriptableobject instances from an asset (a prefab or another scriptableobject asset) you have to save the scriptable object as asset using AssetDatabase.CreateAsset or AssetDatabase.AddObjectToAsset
I am indeed creating each node using CreateInstance(node.NodeType)
and then once finished, setting EditorUtility.SetDirty(tree)
. Does Undo.RegisterCreatedObjectUndo
work any different from SetDirty
? If I choose for persisting the asset using AssetDatabase
, do I need to load this on "play time" using a string with the asset's name? that feels like a hack.