PropertyDrawer that dynamically displays the selected inherited classes

Hello, there is any way do create a PropertyDrawer that show a enum pop up with the child classes and after select one show in the inspector that class. For example in the next codes:

'code'
public class Animal
{
    public virtual void Interact() { }
}
public class Dog: Animal
{
    [SerializeField] private int height;
    [SerializeField] private string name;
}
public class Cat : Animal
{
    [SerializeField] private int size;
    [SerializeField] private Vector3 target;
}
public class Farm: MonoBehaviour
{
    [SerializeField] private Animal[] animals;
}

I know that is not the best example case Animal class must have some generic variables but it is just for ilustration.
In the Farm class in the Inspector i wanna have the option to select in a popup the derived class (cat ordog) that i wannna use,draw that class and saved in the inspector, something like drag and drop from monobehaviour derived classes but for base class.
I want to do dynamically, beacasue i dont wannt to change the property drawer for each time that i create a new derived class.

Firstly, you need to serialize the field by-reference to enable polymorphism. This is done with the [SerializeReference] attribute: Unity - Scripting API: SerializeReference

Secondly, a PropertyField can draw a SerializedProperty automatically.

I wrote an example of a reusable drawer with UI Toolkit here: abstract SkillList that can be edited in Inspector - #15 by spiney199

Addons like Odin Inspector can handle this out of the box as well with much more features.

2 Likes

Thank you! That helps me a lot. However, my current issue is that I cannot display the class in the Inspector without creating a CustomPropertyDrawer for each class. Additionally, the classes with a CustomPropertyDrawer are not updating as expected.

Here is like i’m trying to draw the class

 public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
 {
     EditorGUI.BeginProperty(rect, GUIContent.none, property);
     float acumulativeHeigth = EditorGUIUtility.singleLineHeight;
     rect.height = EditorGUIUtility.singleLineHeight;
     property.isExpanded = EditorGUI.Foldout(rect, property.isExpanded, label, toggleOnLabelClick: true);
     SerializedProperty useMonobehaviourProperty = property.FindPropertyRelative("_useMonobehaviour");
     SerializedProperty commandMonobehaviourProperty = property.FindPropertyRelative("_commandMonoBehaviour");
     SerializedProperty commandClassProperty = property.FindPropertyRelative("_commandClass");
     string labelDescription = "Set a Step Command";
     if (useMonobehaviourProperty.boolValue)
     {
         if (commandMonobehaviourProperty.objectReferenceValue != null)
             labelDescription = commandMonobehaviourProperty.objectReferenceValue.GetType().ToString();
     }
     else
     {
         if (commandMonobehaviourProperty.objectReferenceValue != null)
             labelDescription = commandClassProperty.managedReferenceValue.ToString();
     }
     using (new EditorGUI.DisabledScope(true))
         EditorGUI.LabelField(rect, ".", labelDescription);
     
     if (property.isExpanded)
     {
         rect.y += acumulativeHeigth;
         EditorGUI.PropertyField(rect, useMonobehaviourProperty);
         rect.y += acumulativeHeigth;
         EditorGUI.indentLevel++;
         if (useMonobehaviourProperty.boolValue)
             EditorGUI.PropertyField(rect, commandMonobehaviourProperty);
         else
             ShowClassPropertyfield(rect, commandClassProperty);
         property.serializedObject.ApplyModifiedProperties();
         EditorGUI.indentLevel--;
     }
     EditorGUI.EndProperty();
 }
 private void ShowClassPropertyfield(Rect rect, SerializedProperty property)
 {
     StepCommandClass[] stepCommandClasses = GetStepCommandClasses();

     string[] options = new string[stepCommandClasses.Length];
     for (int i = 0; i < stepCommandClasses.Length; i++)
         options[i] = stepCommandClasses[i].ToString();
     string name = String.Empty;
     if (property.managedReferenceValue != null)  
         name = property.managedReferenceValue.ToString();
     int currentIndex = GetIndex(options, name);
     if (ParametersPopup(rect, "Step class command", currentIndex, options, out int newIndex))
     {
         property.managedReferenceValue = stepCommandClasses[newIndex];
         property.serializedObject.ApplyModifiedProperties();
         rect.y += EditorGUIUtility.singleLineHeight;
         ConstructorInfo constructor = stepCommandClasses[newIndex].GetType().GetConstructor(Type.EmptyTypes);
         if (constructor != null)
         {
             object value = constructor.Invoke(null);
             property.managedReferenceValue = value;
             property.serializedObject.ApplyModifiedProperties();
         }
         EditorGUI.PropertyField(rect, property, new GUIContent(name));
     }     
 }
 private StepCommandClass[] GetStepCommandClasses()
 {
     List<Type> StepCommandList = new List<Type>();
     Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
     foreach (Assembly assembly in assemblies)
     {
         Type[] types = assembly.GetTypes();
         IEnumerable<Type> filteredTypes = types
         .Where(type => typeof(StepCommandClass).IsAssignableFrom(type) && type.IsClass && !type.IsAbstract && type != typeof(StepCommandClass));
         StepCommandList.AddRange(filteredTypes);
     }
     StepCommandClass[] stepCommandClasses = new StepCommandClass[StepCommandList.Count];
     for (int i = 0; i < StepCommandList.Count; i++)
         stepCommandClasses[i] = (StepCommandClass)Activator.CreateInstance(StepCommandList[i]);
     return stepCommandClasses;
 }
 private int GetIndex(string[] parameters, string currentSelection)
 {
     for (int i = 0; i < parameters.Length; i++)
     {
         if (parameters[i] == currentSelection)
             return i;
     }
     return -1;
 }

There’s working code in the post I linked. Use or base your drawer on that instead.

You don’t need a custom drawer for every derived type.

I tried using an attribute as you did, but I couldn’t display the property (including the custom property drawer). Additionally, using UI Toolkit didn’t work since I’m only working with UGUI.

In my current code, I don’t understand why the value isn’t being updated in the Inspector. I resolved the issue with the property drawer by creating a default drawer for the BaseClass.