So I was searching for a way to have polymorphic sub-objects as a properties of my MonoBehaviors (they should contain different data and logic). By default there 2 ways to have sub-objects within MB’s:
Regular C# classes with [Serializable] attribute. The most convenient option as Unity Editor creates an instance of the class for you so you just expand it and edit sub-properties directly. However there is no polymorphism this way, because of how Unity serialization works. It basically looks at the property type (let’s say we have MySubObjectBase and MySubObjectDerived) and if it’s a Base class, it will always instantiate the object of the base class after deserialization, regardless of which derived class you used before.
ScriptableObjects. Those cannot be edited directly by default, but expected to be used as Data Assets that you create in your Assets folder and assign to an inspector. I don’t like this approach for certain cases when I’m not concerned about memory (I don’t have million of GameObjects containing this nested data), but I’m more concerned about flexibility (whenever I need unique set of data for specific scene object, I have to create another SO asset and assign it). I wish I could just have this data within my MonoBehavior, yet having polymorphism to be able to extend the logic.
I’ve read the following articles (which were very helpful for general understanding):
So I created my own attribute and property drawer that allows to select from the set of classes and creates a “transient” ScriptableObject (via ScriptableObject.CreateInstance()) for you that you can edit directly as if it were a regular [Serializable] class.
I used this code as base:
Everything worked fine as long as the containing MonoBehavior remains only in the scene (SO gets serialized as a separate object in the scene). However when I save this GameObject as a prefab, the SO reference gets emptied and my SO is not serialized into the prefab asset file.
Does anyone have any tips to overcome this? Some way to have Prefab asset contain the SO, or maybe completely different approach to my problem that achieves the same goal?
My immediate thought is that you can create a custom function for turning the SO into a prefab, and have that function add the SO to the prefab though AssetDatabase.AddObjectToAsset.
That will (probably) cause each instance of the prefab to reference the prefab’s SO instead of having their own copy. That may or may not be what you want. In addition, you code for editing this SO now has to care if the container is a prefab or not, which could get clunky.
So it’s doable, but not very nice?
A better option is probably to get the open-source ODIN serializer, as it supports polymorphism. You can get it here. I haven’t tried integrating it into anything myself, but I’ve heard Good Things, so it’s probably worth a shot.
Thanks for the tip, but I try to stick to built-in solutions for now, as I build my own little library for my works. I think I’d rather revert to just using base abstract MonoBehavior class and have my polymorphism this way. It will violate the meaning of MonoBehavior a bit (they should use event functions and interact with gameobject) but at least it’s very easy to use. Basically MBs that just contain some data and methods but don’t do anything on their own.
Just for completeness sake, tried a different approach today. While I ditched the idea of using runtime-created ScriptableObjects I though if it’s possible to have a field that can potentially reference either a MonoBehavior or a ScriptableObject depending on what is more convenient for each sub class.
Here is what I came up with:
It seems to work on the scene, just like the first solution. However it still breaks down whenever I try to save GameObject as a Prefab. I get “Unsupported type” error in console and reference don’t get saved properly. It seems that Unity serializer has to know exactly if the field is either GameObject or ScriptableObject to properly handle the serialization of the reference.
I’ve typically approached this by serializing scriptable objects into the base asset, often another scriptable object. I streamline the display and workflow by drawing everything in the base objects custom inspector. This also fits my preference to have little to no scene specific data.
In your case which seems like it needs to cover a lot of avenues… it seems like you could serialize a list of interfaces. That’d let you mix types.
Updated the gist, now it works! (see previous post) I can now have pure interfaces as fields, drag&drop MonoBehaviors and ScriptableObjects alike and it is saved correctly both in scene and prefab assets. The key was to use UnityEngine.Object as the type of serialized field and then do all the casting in generic wrapper-class. Not exactly what I wanted in the OP, but at least now I can use interfaces in more cases than before.
I think approach you describe fits well for stuff like dialogue trees. In my case I want to have more flexibility in scenes, so storing everything in scene objects is better. For example, I have a set of spawners that can be connected to some spawn pool on the scene. Which spawners connect to which pools is a level-design decision, so it shouldn’t be stored in assets.