In this post, I went over the inventory and item system, and mentioned that unity can’t actually serialize types.

In this blog post, I will go over a solution that I found which lets me serialize a Type field like any other, with a nice dropdown.

Inspector view

Go to final code#


Generic TypeReference class#

The system works by having a wrapper class, which I’ll call SerializedType<T>. It takes one generic argument; a type that the value must derive from. For example in my case, if T is Item, you can assign it things like Weapon or Potion, but not Player.

// NOT FINAL CODE!
[System.Serializable]
public class SerializedType<T>
{
    [SerializeField] string fullTypeName = typeof(T).Name;

    public static implicit operator Type(SerializedType<T> t) => Type.GetType(t.fullTypeName);

    public static implicit operator SerializedType<T>(Type t)
    {
        SerializedType<T> s = new();
        s.fullTypeName = t.FullName;
        return s;
    }
}

The class stores the type as a string containing its fullName.

I’ve defined two implicit conversion operators; from Type to SerializedType, and vice versa, which means you can simply replace any field of Type with it in order to show it in the inspector.

I added caching so that Type.GetType() doesnt get called every time you access this as a Type, which looks like this:

[System.Serializable]
public class SerializedType<T>
{
    [SerializeField] string fullTypeName = typeof(T).Name;

    string cachedFullTypeName;
    Type cachedType;


    public static implicit operator Type(SerializedType<T> t)
    {
        if (t.cachedFullTypeName != t.fullTypeName)
        {
            t.cachedFullTypeName = t.fullTypeName;
            t.cachedType = Type.GetType(t.cachedFullTypeName);
        }

        return t.cachedType;
    }
    
    public static implicit operator SerializedType<T>(Type t)
    {
        SerializedType<T> s = new();
        s.fullTypeName = t.FullName;
        return s;
    }
}

Custom Property Drawer#

Now that the logic of the class is complete, it’s time to add a Property drawer so that it can be drawn nicely in the inspector.

[CustomPropertyDrawer(typeof(SerializedType<>))]
public class SerializedTypeDrawer : PropertyDrawer
{
    IEnumerable GetInheritedClasses(Type baseType)
    {
        return Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsClass && !t.IsAbstract && baseType.IsAssignableFrom(t));
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // get generic type
        Type t = fieldInfo.FieldType;
        if (t.IsGenericType)
        {
            t = t.GenericTypeArguments[0];
        }

        SerializedProperty prop = property.FindPropertyRelative("fullTypeName");
        string typeName = prop.stringValue;

        // dropdown button rect
        Rect dropdownRect = position;
        dropdownRect.x += EditorGUIUtility.labelWidth + 2;
        dropdownRect.width -= EditorGUIUtility.labelWidth + 2;
        dropdownRect.height = EditorGUIUtility.singleLineHeight;

        Type currentType = Type.GetType(typeName);

        EditorGUI.PrefixLabel(position, label);
        if (EditorGUI.DropdownButton(label.text != "" ? dropdownRect : position, new(currentType?.Name ?? "Unknown Type"), FocusType.Keyboard))
        {
            GenericMenu menu = new GenericMenu();

            // inherited types
            foreach (Type type in GetInheritedClasses(t))
            {
                menu.AddItem(new GUIContent(type.Name), typeName == type.FullName, () =>
                {
                    prop.stringValue = type.FullName;
                    property.serializedObject.ApplyModifiedProperties();
                });
            }
            menu.ShowAsContext();
        }
    }
}

I won’t explain this fully, but essentially the OnGUI method finds all classes that inherit from the generic parameter and puts them into a dropdown menu.


Final code#

using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

[System.Serializable]
public class SerializedType<T>
{
    [SerializeField] string fullTypeName = typeof(T).Name;

    string cachedFullTypeName;
    Type cachedType;


    public static implicit operator Type(SerializedType<T> t)
    {
        if (t.cachedFullTypeName != t.fullTypeName)
        {
            t.cachedFullTypeName = t.fullTypeName;
            t.cachedType = Type.GetType(t.cachedFullTypeName);
        }

        return t.cachedType;
    }

    public static implicit operator SerializedType<T>(Type t)
    {
        SerializedType<T> s = new();
        s.fullTypeName = t.FullName;
        return s;
    }
}

#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(SerializedType<>))]
public class SerializedTypeDrawer : PropertyDrawer
{
    IEnumerable GetInheritedClasses(Type baseType)
    {
        return Assembly.GetExecutingAssembly().GetTypes().Where(t => t.IsClass && !t.IsAbstract && baseType.IsAssignableFrom(t));
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // get generic type
        Type t = fieldInfo.FieldType;
        if (t.IsGenericType)
        {
            t = t.GenericTypeArguments[0];
        }

        SerializedProperty prop = property.FindPropertyRelative("fullTypeName");
        string typeName = prop.stringValue;

        // dropdown button rect
        Rect dropdownRect = position;
        dropdownRect.x += EditorGUIUtility.labelWidth + 2;
        dropdownRect.width -= EditorGUIUtility.labelWidth + 2;
        dropdownRect.height = EditorGUIUtility.singleLineHeight;

        Type currentType = Type.GetType(typeName);

        EditorGUI.PrefixLabel(position, label);
        if (EditorGUI.DropdownButton(label.text != "" ? dropdownRect : position, new(currentType?.Name ?? "wtf"), FocusType.Keyboard))
        {
            GenericMenu menu = new GenericMenu();

            // inherited types
            foreach (Type type in GetInheritedClasses(t))
            {
                menu.AddItem(new GUIContent(type.Name), typeName == type.FullName, () =>
                {
                    prop.stringValue = type.FullName;
                    property.serializedObject.ApplyModifiedProperties();
                });
            }
            menu.ShowAsContext();
        }
    }
}
#endif

Usage#

To use this code, in a place where you might have

public Type weaponType;

simply replace it with

public SerializedType<Weapon> weaponType;

with the generic parameter (in this case Weapon) being a base class the type must derive from. Use System.Object for any type.