In the latest LTS release, polymorphic serialization offers improved user collaboration and API access, plus more granular handling of missing types.
With the SerializeReference attribute, you can serialize an object assigned to a field as a reference, instead of serializing it by value. The objects being referenced are called “managed references.” Fields with the SerializeReference attribute serve to support polymorphism and null values – and most recently, Unity 2021 LTS introduced Stable IDs for managed references, which provide granular handling of missing types and improved API access. In this blog, we share more about these changes and how they can benefit you directly.
Managed references are saved within the serialization data of their host, where the host is a Unity object (such as a class derived from MonoBehaviour or ScriptableObject). To do this, we assign a unique ID to each managed reference object.
A field with the SerializeReference attribute is serialized by saving the ID of the referenced object. The managed references themselves are saved in a list called the ManagedReferenceRegistry, which is included within the host’s serialized data.
In Unity 2019 and 2020 LTS, IDs were assigned at save time via a depth-first traversal of the content. The main drawback of this approach is that small actions, like reordering the elements of an array, could result in a significant change in the affected file. Large changes within the files could thereby lead to merge conflicts, which are difficult to resolve when working in a collaborative environment.
That’s why we introduced Stable ID. Stable ID ensures that once an object is assigned its own unique ID, then that same ID is retained through successive save and load cycles. In other words, changing the field assignment of managed references on the host, and then saving again, won’t alter the ID.
To illustrate the value of Stable ID, consider the following example:
This example creates an array of managed reference objects, populated with interleaved instances of the Sandwich and Fruit class. You can view the contents of the array by inspecting the LunchBox1.asset file.
Moving the first entry to the end of the list will result in a change in the underlying asset file. The following screenshots from a diff tool demonstrate how simple the diff from 2021.3 is, as the objects in the array now have IDs that are independent of the array ordering.
In addition to reducing changes within Unity files, the Stable ID feature is designed to address common collaboration challenges. In previous versions, two users who added managed reference objects on the same host would end up with the same ID, making it difficult to merge (especially since a single managed reference object can be referenced by more than one field). As of Unity 2021, IDs are now virtually guaranteed to avoid such a conflict, because they are generated based on a hash of time and system information. For more advanced scenarios, you can even override the default ID assignment system by calling SerializationUtility.SetManagedReferenceIdForObject.
SerializeReference includes support for polymorphism, which means that a field can be assigned to an instance of a class that derives from the field type. In fact, we support the field type as “System.Object” which is the root base class of every C# class. But this opens up the possibility of a successfully compiled project missing the definitions of classes that were previously available and had been saved into a scene or asset file. In some cases, classes can go missing when source files are removed, classes are renamed, or classes are moved to another assembly.
When loading a SerializedReference host object, the fully qualified type name of each managed reference object is examined and must be resolved back to a valid class type in order to instantiate it. In previous versions of Unity, missing classes could put the entire “host” object into an error state without loading any of the valid managed reference objects. So if you had a “host” with an array of 15 managed reference objects, but a single object could not be resolved, then you would not see any of them in the Inspector. There would be an error logged in the console – even though the host object was not visually marked as being in an error state when inspected – and all the edits made would be silently discarded.
In Unity 2021, we now instantiate all loadable managed reference objects and replace the missing ones by null. This gives users an opportunity to see more of the state of the host object, and to facilitate the resolution of missing types. Meanwhile, if the missing type is restored while the host object is loaded, then the triggered Domain Reload will restore the managed reference objects, and all fields referencing it will be updated properly.
This is an example of how objects with missing types appear in the Inspector:
In 2020.3, the Fruit class is missing yet the Inspector does not show any array elements, and there is no indication of an error:
In 2021.3, the Inspector warns you that the missing Fruit objects appear as null entries, whereas the Sandwich objects continue to be displayed:
The error messages in the console related to missing types have also been updated so that they’re less repetitive – they simply identify which host objects have missing types.
Here’s an error message in 2020.3:
Compare it to this warning message in 2021.3:
Leveraging those improvements to IDs, plus changes made to managed reference objects in Prefabs are now sticky to those managed reference objects. In the past, PropertyModifications targeted fields based on the first property path leading to that field. This meant that if the path changed (by reordering an array, for instance), then the PropertyModification would lose track of the intended managed reference and fail to resolve properly. As of Unity 2021, there are PropertyModifications that reference managed reference objects using a path incorporating the Stable ID, i.e., managedReferences.myString. This ensures that a managed reference object maintains its overridden values, no matter where you move it on the host.
A new class, SerializationUtility, has been added to the Unity API to expose functionality related to SerializeReference. For example, SerializationUtility.ClearAllManagedReferencesWithMissingTypes() can be used to remove references to missing types, such as removing the warning state from a host if no recovery is planned for a missing type.
We’ve refined the API for working with managed references in the context of CustomEditors, including the option for read access to SerializedProperty.managedReferenceValue.
The reference for the new methods also offer sample code, and we’ve incorporated more detail for the reference topics related to serialization.
Your existing projects that use SerializeReference should load smoothly in the new version of Unity, as the serialization code is compatible with the older managed reference format. Often, the use of SerializeReference does not require in-depth knowledge of the Stable ID concept, or the new API methods. However, even when left “under the hood,” these improvements are beneficial for typical usage, especially in a collaborative environment.
We hope that this article will encourage you to further explore the feature. As our serialization team continues to enhance capabilities for all Unity users, we appreciate your ongoing feedback and discussion on this dedicated forum thread.