Serialized Objects, Generic Arrays and Inheritance

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 :wink:

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

Thanks

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();
	}
}

I think it’s because you have to add “hideFlags = HideFlags.HideAndDontSave;” to OnEnable. See this: Unity Blog

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

Works perfectly with me.

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.

Thanks for the help, think I might give up on these for now. Just doesn’t seem possible

http://feedback.unity3d.com/suggestions/serialization-of-polymorphic-dat

maybe when serialze callbacks appear

I think you shouldn’t give up, because it does work. Hell, I use it daily!

Have you tried to cast your type and directly access the variable?

http://www.codingjargames.com/blog/2012/11/30/advanced-unity-serialization/

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.

You can add Undo to just about everything…

yeah, serialized properties just seemed like a nice simple way to do so.

i had this in the past, unfortunately unity doesn’t like serializing classes that have a custom class or enum inside.

Seriously, what are you talking about? I have dozen of them at work and all of them work fine.

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.

From your example above;

// instead of doing this...
SerializedProperty item = list.GetArrayElementAtIndex(i);
Debug.Log(item.FindPropertyRelative("name")); // returns Null

// try this...
SerializedObject genericObjectInstance = new SerializedObject (list.GetArrayElementAtIndex(idx).objectReferenceValue);
Debug.Log(genericObjectInstance.FindProperty("name")); // returns SerializedProperty

Hope that helps someone :slight_smile:

To those who might stumble across this problem: take a look at [SerializeReference] attribute, this might be a solution you are searching for