Serialize Reference in Unity

Good afternoon everyone. This is about the relatively new Serialize Reference feature. It appeared in the 2019 version.

Table of contents:

  • What is it and why is it needed

  • Advantages and disadvantages

  • How to work with this

  • Examples of

Serialize Reference is an attribute that allows Unity to serialize data in the new format. The main hack of this serialization is that it allows data to be serialized in abstractions. Example:

public class ExampleFirstMonoBehaviour : MonoBehaviour
{
			[SerializeReference] private object _customObject = new ExampleFirst();
}

public class ExampleSecondMonoBehaviour : MonoBehaviour
{
			[SerializeReference] private object _customObject = new ExampleSecond();
}
  

public class ExampleFirst
{
  		[SerializeField][Range(0, 5)] private float _floatValue;
}

public class ExampleSecond
{
  		[SerializeField] private string _stringValue = "Hello";
}

In these examples, the object type hides the serialization of ExampleFirst and ExampleSecond.

pros

Minuses

  • Until the 2021 version of the unit, serialization breaks when renaming classes, because they were explicitly serialized.

,

How to work with it

We now know that our favorite unit can serialize abstract fields, but how to make it all work conveniently?

public class NotExampleMonobehaviour : MonoBehaviour
{
			// Данная сериализация работает, но если написать это без всяких махинаций
      // То редактор вам выведет только название этого поля
			[SerializeReference] private object _notYetWorkedSerialization;
}

What is the difference between this code and what was in the beginning? – Initialization

The bottom line is that in this case the editor serializes not only the field, but also the instance nested in it, but if nothing has been nested in it, then in fact it has nothing to serialize and display to you.

But how do you nest an instance in a given field? By default, out of the box only through default initialization. Uniteki did not provide a convenient toolkit for working with these fields. The only thing that was given to you was 3 data fields of the type SerializeProperty:

To draw these instances, the unit uses the standard PropertyDrawer mechanics. Those. if you need to override rendering, or make decoration using PropertyAttribute, then this also works in SerializeReference.

To initialize these fields, you can write a custom toolkit using the SerializeProperty fields described above, or use this [ассет](https://github.com/elmortem/serializereferenceeditor)

The most important thing is to understand what serialization can work with and what it can’t. This cheat sheet is in the dock. [юнитехов](https://docs.unity3d.com/ScriptReference/SerializeReference.html)

  • Field type must not inherit from UnityEngine.Object

  • Field type can be abstract class / interface

  • Applying the SerializeReference attribute to a leaf / array causes it to be applied to all of its elements. Those. List works.

  • These fields cannot be referenced by different UnityEngine.Object. Those. if you assigned the same instance in the editor, then after deserialization they will be different instances but with the same content.

  • The datatype you are trying to serialize must be attributed [Serializable]

  • The field value cannot be a custom generic type. Those. if you have datatype Foo, then when you try to write to any field of this type, data like Foo or something like that, an error will come out in the editor. If you need to write Foo , create an heir IntFoo

  • Examples of

    1. An article from Pixonic – In this case, they used this feature for reactive binding.

    2. I also tried to implement a similar feature for myself, but without some elements. There is nothing complicated about it. The code how it is easy to implement is below:

    Code
    public class ViewModelEditor : Editor
        {
            private static readonly Type[] _types;
            private static readonly GUIContent[] _keys;
            private ReorderableList _reorderableList;
            private SerializedProperty _propertyList;
    
            static ViewModelEditor()
            {
                _types = AppDomain.CurrentDomain.GetAssemblies()
                    .SelectMany(assembly => assembly.GetTypes())
                    .Where(t => t.IsSubclassOfGenericTypeDefinition(typeof(ReactiveProperty<>)) && t != typeof(ReactiveProperty<>))
                    .ToArray();
                _keys = _types.Select(t => new GUIContent(t.Name)).ToArray();
            }
            
            public override void OnInspectorGUI()
            {
                if (_reorderableList == null)
                {
                    _propertyList = serializedObject.FindProperty("_propertiesList");
                    _reorderableList = new ReorderableList(serializedObject, _propertyList, true, true, true, true);
                    _reorderableList.onAddDropdownCallback = ONAddCallbackHandler;
                    _reorderableList.elementHeightCallback = ElementHeightCallback;
                    _reorderableList.drawElementCallback = DrawElementCallback;
                }
                
                _reorderableList.DoLayoutList();
            }
    
            private void DrawElementCallback(Rect rect, int index, bool isactive, bool isfocused)
            {
                var prop = _propertyList.GetArrayElementAtIndex(index);
                EditorGUI.PropertyField(new Rect(rect.x + 40f, rect.y, rect.width - 40f, rect.height), prop, prop.isExpanded);
                serializedObject.ApplyModifiedProperties();
            }
    
            private float ElementHeightCallback(int index)
            {
                var prop = _propertyList.GetArrayElementAtIndex(index);
                return EditorGUI.GetPropertyHeight(prop);
            }
    
            private void ONAddCallbackHandler(Rect rect, ReorderableList list)
            {
                EditorUtility.DisplayCustomMenu(rect, _keys, -1, CallbackHandler, null);
            }
    
            private void CallbackHandler(object userdata, string[] options, int selected)
            {
                var count = _propertyList.arraySize;
                _propertyList.InsertArrayElementAtIndex(count);
                _propertyList.GetArrayElementAtIndex(count).managedReferenceValue =
                    new global::ViewModel.Pair()
                        {key = $"Element {count}", property = Activator.CreateInstance(_types[selected])};
                serializedObject.ApplyModifiedProperties();
            }
        }
        
        public static class TypeExtensions
        {
            public static bool IsSubclassDeep(this Type type, Type parenType)
            {
                while (type != null)
                {
                    if (type.IsSubclassOf(parenType))
                        return true;
                    type = type.BaseType;
                }
    
                return false;
            }
    
            public static bool TryGetGenericTypeOfDefinition(this Type type, Type genericTypeDefinition,
                out Type generictype)
            {
                generictype = null;
                while (type != null)
                {
                    if (type.IsGenericType && type.GetGenericTypeDefinition() == genericTypeDefinition)
                    {
                        generictype = type;
                        return true;
                    }
                    type = type.BaseType;
                }
    
                return false;
            }
            
            public static bool IsSubclassOfGenericTypeDefinition(this Type t, Type genericTypeDefinition)
            {
                if (!genericTypeDefinition.IsGenericTypeDefinition)
                {
                    throw new Exception("genericTypeDefinition parameter isn't generic type definition");
                }
                if (t.IsGenericType && t.GetGenericTypeDefinition() == genericTypeDefinition)
                {
                    return true;
                }
                else
                {
                    t = t.BaseType;
                    while (t !=null)
                    {
                        if (t.IsGenericType && t.GetGenericTypeDefinition() == genericTypeDefinition)
                            return true;
    
                        t = t.BaseType;
                    }
                }
    
                return false;
            }
        }

    Similar Posts

    Leave a Reply

    Your email address will not be published. Required fields are marked *