- Home /
Serialization of generic abstract class
I'm currently working on something like a custom variable management system. I tried to make it as modular as possible, since I want to be able to scale the system in two different ways: - which datatypes I can represent with the system (e.g. bool, string, int and later on custom types) - How the effective value of the underlying datatype is stored.. e.g. as a raw field, in a ScriptableObject, in a dictionary with an according key etc.
Therefor I have the following class, which I want to reference to in MonoBehaviours later on (the generic gives the scalability for datatypes)
[System.Serializable]
public abstract class BaseVariable<T>
{
[SerializeField] private Variable<T> _variable;
public void OnGUI(Rect position)
{
... // Doing some stuff on how to store the variable
_variable.OnGUI(position);
}
}
WIth the corresponding class Variable. Again, the generic is the datatype I want to represent. The class Variable itself is abstract and can later on be implemented in different ways, as long as it can give me access to a value of the specified type. Those different implementations guarantee my scalability for the storage and access:
[System.Serializable]
public abstract class Variable<T>
{
public abstract T Value {get; set;}
public abstract void OnGUI(Rect position);
}
Those two classes have the following implementations. The datatype I want to store for now is bool and the storage / access of the value is a simple field which gets accessed by the required Value property:
[System.Serializable]
public class BoolVariable : BaseVariable<bool>
{ }
[System.Serializable]
public abstract class BaseVariableRaw<T> : Variable<T>
{
[SerializeField] private T _value;
public override T Value
{
get => _value;
set => _value = value;
}
}
[System.Serializabe]
public class BoolVariableRaw : BaseVariableRaw<bool>
{
public override void OnGUI(Rect position)
{
Value = UnityEditor.EditorGUI.Toggle(position, Value);
}
}
and to inspect everything I use a simple MonoBehaviour which holds my BaseVariable:
public class BoolVariableTester : MonoBehaviour
{
[SerializeField] private BoolVariable _boolVariable;
}
which will call the following PropertyDrawer:
public abstract class BaseVariablePropertyDrawer<T> : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
UnityEngine.Object target = property.serializedObject.targetObject;
BaseVariable<T> instance = fieldInfo.GetValue(target) as BaseVariable<T>;
DrawInstance(position, instance);
}
public static void DrawInstance(Rect position, BaseVariable<T> instance)
{
instance.OnGUI(positon);
}
}
[CustomPropertyDrawer(typeof(BoolVariable))]
public class BoolVariablePropertyDrawer : BaseVariablePropertyDrawer<bool>
{ }
Ooookay, so far so good. This system works with different kinds of datatypes and different kinds of implementations. I created such implementations for my bool datatype. I designed my OnGUI() method in BaseVariable so I can select between different implementations and then write them into the "Variable _varialbe" field of my "BaseVariable" class. In this case I only showed the implementation of the RawVariable, but there are (potentially) other access types too. But now I have the problem that I can't serialize that instance. I can't even serialize my instance of BoolVariable in my BoolVariableTester class. Any suggestions how I can make this work without writing my own serializer listening to ISerializeCallbackReceiver? I know I can use ScriptableObjects to some degree to serlialize abstract classes and even keep references to same instances, but I can't seem to figure out how to exactly apply those techniques to my special case with multiple levels of abstraction. I'd be really glad if somebody could help me with this!
Thanks in advance, BOTHLine
I eventually always end up writing my own serialization.. that supports generics, array of objects of different type, multiple variables referencing the same object. The most tricky part is creating some gui/inspector for all of this, but it can also be done
Answer by Bunny83 · May 26, 2019 at 10:45 PM
Unity does not support inheritance / polymorphism for custom serializable classes. Fields are serialized based on the field type, not the actual class instance type. The thing is, Unity does not even store any type information for those classes, it just serializes the field data. Deserialization happens based on the type of the fields. So if you want / need such a setup your only two ways are:
using the ISerializationCallbackReceiver and some custom serialization format
using ScriptableObjects.
Note that when using ScriptableObjects the most important class that need to derive from ScriptableObject would be your "Variable" class since you only need polymorphism for this class, at least based on your example.
Though your whole setup seems to be not well designed and i don't really see any advantage using such a setup. Note that generic classes do not represent any kind of polymorphism. A generic class with different types in the type parameter have absolutely nothing in common. So a List<int>
and a List<string>
are just as different as Vector3, DateTime or float. Generics just provide generic functionality for different implementations. Though those implementations are not compatible since the actual types are different.
Also note that your example classes could not be used at runtime in an actual build since you can not use the UnityEditor namespace in a runtime class without preprocessor directives which exclude those parts from the class. You should generally try to avoid mixing editor functionality with runtime code.
Hey Bunny83, first of all thanks for your reply!
Yeah I thought I only had those two options and probably have to write the serialization myself listening to the ISerializationCallbackReceiver. I tried inheriting from ScriptableObject already, but I couldn't manage to make that work. Today I heard that you can't store ScriptableObjects in $$anonymous$$onoBehaviours, just reference them when they already live in the project (that could explain why it didn't work). Would that be true?
Could you tell me why the setup seems not to be well designed? $$anonymous$$y current goal is to make something like this https://unity3d.com/how-to/architect-with-scriptable-objects (more detailed in his Unite talk: https://youtu.be/raQ3iHhE_$$anonymous$$k?t=1068 ) or like the Plugin "SmartData" for Unity. I just showed you a raw data field for simplicity reasons, which in itself doesn't make much sence, but as I said I want to store and access my data in many different ways (like evaluating two other values in runtime, accessing properties of other objects etc.).
So that would be my "advantage". I'm currently using my own lightweight version in one of my projects and it works like a charm. There were some bad design choices which I now try to eli$$anonymous$$ate and it's not quite as expandable as I'd like it to be!
And well yes, I know about the EditorGUI stuff, at the moment I have it all surrounded by #if UNITY_EDITOR and #endif (in both abstract classes and implementations). It's a hacky workaround for now, but I couldn't manage to make my PropertyDrawer work properly, because I had to nest two PropertyDrawers (the one of BaseVariable/BoolVariable and the one of Variable/VariableRaw/BoolVariableRaw) which gave me errors that such property BoolVariableRaw doesn't exist on my Object BoolVariableTester when using the EditorGUI.PropertyField().
Well ScriptableObjects are a tricky topic. It depends where you want them to be stored. ScriptableObjects are always serialized on their own. So they are top level (first class) serialized objects just like $$anonymous$$onoBehaviour or other UnityEngine.Object derived classes. References to such objects are actual serialized references (so just an instance id reference is stored). That means that ScriptableObjects are always stored on their own. When you have a GameObject in a scene and you create a ScriptableObject which you reference from a $$anonymous$$onoBehaviour on that gameobject, the scriptable object would be stored in the scene as well as first class citizen (look at the serialization format to understand what i mean).
However that means when you want to turn such a GameObject into a prefab the scriptable object reference would be lost since a prefab can't reference an object that lives in the scene. In this case you would need to store the ScriptableObject as actual asset in the project in order to reference it.
Custom serializable classes are not objects at all from the serialization system's point of view. They are just "sub properties" of the first class object (In your case the $$anonymous$$onoBehaviour class). Since no type information is stored (just data) there is no support for polymorphism. If you have another look at the example scene. There's an object of type "LightmapSettings" in the scene. It has a sub object (m_LightmapEditorSettings) which holds nested information. Though the type of that class or struct is not known. Only the fields of that nested class are stored.
So if you want to use the ISerializationCallbackReceiver your class(es) have to have at least one field that is serializable by Unity. Now you can collect the information(s) required to restore your actual object graph and store that information in that field that Unity can serialize (usually a string but depends on the context). This of course is not very efficient.
Furthermore your current setup won't work. $$anonymous$$eep in $$anonymous$$d that Unity serializes nested types based on the variable type. The variable type must not be generic since Unity does not support generic types. In your case the BoolVariable is derived from the abstract generic BaseVariable and represents a concrete non abstract type. So far so good. However the BaseVariable class contains a serialized field of type Variable<T>
which is a generic and abstract type. So this can't be serialized at all ($$anonymous$$eep in $$anonymous$$d the type used by the serialization system is the field type, not the actual instantiated object type).
So you would already need custom serialization for the BaseVariable as well.
Finally your class na$$anonymous$$g is very confusing. You named the storage part (BoolVariableRaw ) similar to the container part (BoolVariable). Same for the base classes.
Your answer
Follow this Question
Related Questions
(SOLVED) Serializing different JSON objects, contained in an array, in another JSON object. (C#) 2 Answers
Abstract classes in a ScriptableObject 1 Answer
Serialize a List of generic key value pair in a ScriptableObject 1 Answer
Problem using a data structure created in editor mode, will not serialize? 1 Answer