I’ve been struggling with trying to create a proper serialized editor for my objects. Specifically, generic arrays/lists which contain different sub classes of a base abstract class.
If it’s not possible at all to have a serialized generic array which contains different classes, then that’s fine. I’ll move on.
If it is, I’d love to hear how
I would love to be able to:
Add custom instances to my serialized array *Not InsertNewElement()
Have a abstract base class with child classes properly serialized
Base class that support polymorphism and that you have access;
ScriptableObject
MonoBehaviour
Anything else will revert to its base class when deserialized.
Be careful with ScriptableObject, as they do not follow when you create a prefab or duplicate a GameObject. ScriptableObject should be use mostly when saving Asset on the disk.
MonoBehaviour should be used for the rest, and yes, it means adding component to your GameObject. Here, we solved that by rewriting the Inspector so that “components” added in a list or other polymorphic fields are not displayed as script, but as “child” of its owner.
Still doesn’t work for me. Here’s some test code, all values are always “Null”
using System;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
[Serializable]
class TestBaseClass : ScriptableObject {
public enum Type {
None, Type1, Type2
}
public string name = "my name";
public virtual Type type {
get { return Type.None; }
}
}
[Serializable]
class TestSubClass1 : TestBaseClass {
public bool myBool = true;
public override Type type {
get { return Type.Type1; }
}
}
[Serializable]
class TestSubClass2 : TestBaseClass {
public int myInt = 1;
public override Type type {
get { return Type.Type2; }
}
}
class Container : MonoBehaviour {
public List<TestBaseClass> list;
}
[CustomEditor(typeof(Container))]
class ContainerEditor : Editor {
void OnEnable() {
if ((target as Container).list == null) {
(target as Container).list = new List<TestBaseClass>();
}
}
public override void OnInspectorGUI() {
serializedObject.Update();
if (GUILayout.Button("Add Class 1")) {
(target as Container).list.Add(TestSubClass1.CreateInstance<TestSubClass1>());
}
if (GUILayout.Button("Add Class 2")) {
(target as Container).list.Add(TestSubClass2.CreateInstance<TestSubClass2>());
}
SerializedProperty list = serializedObject.FindProperty("list");
for (int i = 0; i < list.arraySize; i++) {
SerializedProperty item = list.GetArrayElementAtIndex(i);
Debug.Log(item.FindPropertyRelative("name")); //Null
SerializedProperty type = item.FindPropertyRelative("type"); //Null
if (type != null) {
TestBaseClass.Type typeEnum = (TestBaseClass.Type)item.FindPropertyRelative("type").enumValueIndex;
switch (typeEnum) {
case TestBaseClass.Type.Type1:
Debug.Log(item.FindPropertyRelative("myBool")); //Null
break;
case TestBaseClass.Type.Type2:
Debug.Log(item.FindPropertyRelative("myInt")); //Null
break;
}
}
}
serializedObject.ApplyModifiedProperties();
}
}
Still no luck, added the hideFlags, removed and re-attached test component. Values are still Null.
The only thing that works is creating a new SerializedObject for the SerializedProperty:
SerializedProperty item = list.GetArrayElementAtIndex(i);
SerializedObject itemObject = new SerializedObject(item.objectReferenceValue);
itemObject .FindPropertyRelative("myBool"); //Returns true. BUT, can never be changed because the above line creates a new SerializedObject
It looks like Unity is just putting that class into an object reference rather than in the serialized array.
I believe the problem is Adding a new object to the array:
(target as Container).list.Add(TestSubClass2.CreateInstance<TestSubClass2>()); //this seems wrong
list.InsertArrayElementAtIndex(list.arraySize); //proper way, but you can't set the type, will always be the BaseClass
The above screenshot was taken after a save/reload of a map with a GameObject having the Container script.
However, I think I spotted your error…
EACH class deriving from ScriptableObject or MonoBehaviour MUST exist in its own file, and that file MUST be named with the same name as the class.
Ex.: Container must be inside Container.cs, TestSubClass2 must be in TestSubClass2.cs, and so on.
When Unity serialize an object, it cannot serialize its type (it could save its AssemblyQualifiedName, but it doesn’t because it would only work for C#). Instead, it serialize only its class name, and retrieve the proper type by finding the code file with the same name. Yes, it’s horribly dumb, but it’s how it works.
yeah, my files are in separate classes named correctly. My running example looks like yours, but it’s public fields are all Null.
If “myBool” and “myInt” are public shouldn’t they show up when calling this function?:
EditorGUILayout.PropertyField(item, true); //This only shows the script name
Debug.Log(item.FindPropertyRelative("myBool")); //should say true in the example above if the item is a TestSubClass1 instance
Frankly, no idea… Since I don’t really use SerializedProperty/SerializedObject.
Just to be sure that serialization was occuring correctly, I plugged in my own Inspector;
This was after a save/reload… And the Int and Bool stayed the way they were.
Maybe you could stick with the default inspector, save your ScriptableObject on the disk as .Asset, or replace them with MonoBehaviour, which would show up as component you could still add to a list.
This guy explains a fix, much like what you suggested with adding MonoBehaviours.
I’m only wanting to use SerializedObjects for the undo/redo stuff. But I can live without it.
I can’t cast the type because item is a SerializedProperty, unless there’s some way to find the object it references.
Accessing the item directly from the list doesn’t help as modifying that doesn’t add undo/redo commands Or even notify the prefab of a change.
I’m not prepared to hack my code to support this feature just yet. Hopefully the unity team will take enough notice and fix it.
I had this same issue @CDF , and found a solution.
Basically, you need to create a new SerializedObject instance for the referenced generic instances, before you can then access their serialised properties.