Using custom property drawers with polymorphism

Hello, I can’t get the CustomPropertyDrawer to work with polymorphism.
Here’s a very simple example code:

Models

[Serializable]
public class Animal: ScriptableObject
{
    public string animalName;
}

[Serializable]
public class Monkey : Animal
{
}

[Serializable]
public class Lion : Animal
{
}

[Serializable]
[CreateAssetMenu(fileName = "myZoo", menuName = "Zoo", order = 0)]
public class Zoo : ScriptableObject
{
    public string title;
    public List<Animal> animals = new List<Animal>();

    private void OnEnable()
   {
   }

   public void AddLion()
   {
       Lion lion = ScriptableObject.CreateInstance<Lion>();
       AssetDatabase.AddObjectToAsset(lion, this);
       AssetDatabase.SaveAssets();
       animals.Add(lion);
   }

   public void AddMonkey()
   {
       Monkey monkey = ScriptableObject.CreateInstance<Monkey>();
       AssetDatabase.AddObjectToAsset(monkey, this);
       AssetDatabase.SaveAssets();
       animals.Add(monkey);
   }
}

Zoo Editor

[CustomEditor(typeof(Zoo))]
[CanEditMultipleObjects]
public class ZooEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        if(GUILayout.Button("Add lion"))
        {
            (serializedObject.targetObject as Zoo).AddLion();
        }

        if (GUILayout.Button("Add monkey"))
        {
            (serializedObject.targetObject as Zoo).AddMonkey();
        }
    }
}

Property Drawers

[CustomPropertyDrawer(typeof(Animal))]
public class AnimalPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.LabelField(new Rect(position.x, position.y, position.width, 20), "Hello, I'm an animal");
        SerializedObject serializedObject = new SerializedObject(property.objectReferenceValue as Animal);
        EditorGUI.PropertyField(new Rect(position.x, position.y + 20, position.width, 20), serializedObject.FindProperty("animalName"));
    }
}

[CustomPropertyDrawer(typeof(Lion))]
public class LionPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.LabelField(new Rect(position.x, position.y, position.width, 20), "Hello, I'm a lion");
        SerializedObject serializedObject = new SerializedObject(property.objectReferenceValue as Lion);
        EditorGUI.PropertyField(new Rect(position.x, position.y + 20, position.width, 20), serializedObject.FindProperty("animalName"));
    }
}

[CustomPropertyDrawer(typeof(Monkey))]
public class MonkeyPropertyDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        EditorGUI.LabelField(new Rect(position.x, position.y, position.width, 20), "Hello, I'm a monkey");
        SerializedObject serializedObject = new SerializedObject(property.objectReferenceValue as Monkey);
        EditorGUI.PropertyField(new Rect(position.x, position.y + 20, position.width, 20), serializedObject.FindProperty("animalName"));
    }
}

115889-property-drawer.png

Now, as you can see, even if the zoo has a Lion and a Monkey in the animals list, only the AnimalPropertyDrawer is used to draw both the list items.
I know this is a common problem in Unity.

Is there a way to let the child classes use their own property drawer?

What’s your best solution to this problem?

The problem is that the Zoo class has a List<Animal>. So it seems that it will do the property drawing using “the property drawer for the Animal type”, rather than trying to look deeper into the type specified for each instance.

What you can do is implement the polymorphism handling within the AnimalPropertyDrawer, like this:

using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(Animal))]
public class AnimalPropertyDrawer : PropertyDrawer
{
    private static Dictionary<Type, PropertyDrawer> _polyPropertyDrawers;

    public AnimalPropertyDrawer()
    {
        if (_polyPropertyDrawers == null)
        {   // creating the static variable in the constructor so that it can be cached and reused
            _polyPropertyDrawers = new Dictionary<Type, PropertyDrawer>();

            _polyPropertyDrawers[typeof(Lion)] = new LionPropertyDrawer();
            _polyPropertyDrawers[typeof(Monkey)] = new MonkeyPropertyDrawer();
            // add other types here
        }
    }

    public PropertyDrawer GetPolyPropertyDrawer(SerializedProperty property)
    {
        PropertyDrawer drawer;

        if (!_polyPropertyDrawers.TryGetValue(property.objectReferenceValue.GetType(), out drawer))
        {
            return null;
        }

        return drawer;
    }

    public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    {
        var polyDrawer = GetPolyPropertyDrawer(property);

        if (polyDrawer == null)
        {
            return base.GetPropertyHeight(property, label);
        }

        return polyDrawer.GetPropertyHeight(property, label);
    }

    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        var polyDrawer = GetPolyPropertyDrawer(property);

        if (polyDrawer == null)
        {
            EditorGUI.HelpBox(position, "Cannot find drawer for type " + property.objectReferenceValue.GetType(), MessageType.Error);
        }
        else
        {
            polyDrawer.OnGUI(position, property, label);   
        }
    }
}

115929-prop-drawer-poly.png

This has nothing to do with property drawers at all. Your data classes can’t be serialized by Unity’s serializer since it doesn’t support polymorphism for custom serializable classes. To support polymorphism your classes need to be derived either from MonoBehaviour or ScriptableObject.

PropertyDrawers do not influence what get serialized and what not. They just change the way how the serialized data is presented / visualized.

Make sure you read this page carefully

Hi, I know I’m a little late to the party, but inspired by this answer and my own frustration:

I’ve created a generic and easy to use solution for Polymorphic PropertyDrawers in Unity.

I’ve posted the full details here (ready to Copy & Paste into your own project): https://forum.unity.com/threads/custompropertydrawer-for-polymorphic-class.824667/#post-6702670

P.S. I am posting here as it’s one of the top search results when looking for “unity polymorphic property drawer” so hopefully it’ll help more people