SerializeReference in Unity

Aleksander Trępała
4 min readJan 28, 2020

--

This post is based on this thread, this documentation, and my personal experience. Tested in Unity 2019.3.0b8.

Today Unity officially released version 2019.3 and new [SerializeReference] attribute. For someone like me, interested in bringing good software developer practices into Unity, it should be quite a news. In contract to old [SerializeField], this will allow us to serialize interfaces, abstract classes, and polymorphic data. There are also lesser, but still comfortable improvements (like having fields serialized to null in the inspector). Too good to be true? Well…

  • you cannot use [SerializeReference] on a type, that derives from UnityEngine.Object, so no MonoBehaviour, no ScriptableObjects. If you are interested in just that, check out this post
  • slight performance hit depending on a complexity of serialized type
  • referenced values cannot be shared between UnityEngine.Object instances (two MonoBehaviours cannot share an object that is serialized by reference)
  • no editor support for easily creating instances/adding elements to list

This mechanism is aiming to allow you to replace complexity of scriptable objects with something simpler and more flexible. This comes at the expense of performance. However, if your serialized objects are small enough this may still be useful (performance of deserialization will not be an issue) .

Let’s take a look at simple example: an interface, base class and derived class:

Simple interface type with two implementations, one derives from another

and even simpler TestBehaviour:

Our MonoBehaviour script, which will contain instances of INode

This results in the following inspector view:

List of INode in the inspector

As we can see, both serialization and inspector are fully supported (as long as we do not derive from UnityEngine.Object). This wouldn’t be possible in earlier versions, without [SerializeReference].

The first problem occurs when we try to insert another object to our list (and it is not only the fact, that my editor crashed during first try). Judging from my personal tests, Unity copies the last object on the list and inserts that copy. (if the list was previously empty, it inserts null).

The inspector view after adding new object to List<INode>

The main problem is that (from the inspector) we have no easy way to choose type of the object we want to insert/create. The workaround is to create implementation type picker by ourself. Luckily, we may easily achieve it with custom editor and System.Reflection module. Let’s simplify our TestBehaviour and create simple type picker for INode.

Simplified TestBehaviour (to demonstrate custom editor for SerializedReference)

We may create specified editor for this MonoBehaviour, or use more generic solution with PropertyAttribute. Let’s start with the first (simpler) one:

Custom editor for TestBehaviour

This solution is quite simple and works pretty well:

The inspector view with custom editor for TestBehaviour

Using PropertyAttribute and PropertyDrawer is quite harder, since (at least in Unity 2019.3.0b8) we have to use SerializedProperty to assign our value. This can be done with property.managedReferenceValue field, which is responsible for holding this type of referenced value.

Sadly, It is harder to obtain our interface type, because property.managedReferenceValue has no getter. (I also got an error with undo stack after assigning value of different type) Nevertheless, here is my proposed solution, that requires passing interface type into attribute manually, through constructor (for code simplicity):

PropertyAttribute and PropertyAttributeDrawer that creates simple implementation type picker

The result is no different to the one seen before.

In both cases above, there are similar parts, responsible for obtaining list of types, that implements our interface. First, we have to get all available types in our project:

var types = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(assembly => assembly.GetTypes());

Then we filter our results to match required interface and remove abstract types (because we need to create instance later):

types.Where(p => interfaceType.IsAssignableFrom(p) && !p.IsAbstract).ToArray();

We create instance by using*:

Activator.CreateInstance(type);

To sum things up, Unity claims that this whole system is created primarily for “expressing things like graphs without necessity of creating new ScriptableObject for each node”. In my opinion this is good enough for such purpose. If your project is so big, that number of files may be very large when you use ScriptableObject, switch to SerializeReference. However this whole solution is still very far from my perfect world where I would be able to reference SciprableObject or MonoBehaviour by interface and reduce the necessity of using DI frameworks for clean architectural solutions. (which will probably never happen).

*Note, that this solution require parameterless constructor from instance type. This is for simplicity of this example. Creating picker without this restraint is difficult. If you need something as complex, you may be interested in Odin

--

--