Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  3. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question CustomPropertyDrawer for polymorphic class

Discussion in '2020.1 Beta' started by EngineArtist, Feb 8, 2020.

  1. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Hello,

    How does the SerializedProperty-system work with these recently allowed serializable polymorphic classes? In order to get some kind of reference to one through a SerializedProperty, I need to use the objectReferenceValue-field, which is of type UnityEngine.Object. That means my polymorphic class has to be a subclass of that type. When I do that, I get this error:

    type is not a supported pptr value
    UnityEditor.SerializedProperty:get_objectReferenceValue()
    AttributeDrawer:OnGUI(Rect, SerializedProperty, GUIContent) (at Assets/Scripts/Attributes.cs:54)
    UnityEngine.GUIUtility:processEvent(Int32, IntPtr)

    Here's what my interface looks like, short and sweet:

    Code (CSharp):
    1. public interface IAttribute {
    2.     string Name {get; set;}
    3.     AttributeType Type {get;}
    4.     int Int {get; set;}
    5.     float Float {get; set;}
    6.     string String {get; set;}
    7. }
    Then, a few subclasses in their own separate files, named properly according to their class names:

    Code (CSharp):
    1.  
    2. [Serializable]
    3. public class AttributeNone: UnityEngine.Object, IAttribute {
    4.     public string Name {get => name; set => name = value;}
    5.     public AttributeType Type {get => AttributeType.None;}
    6.     public int Int {get => int.MinValue; set {}}
    7.     public float Float {get => float.NaN; set {}}
    8.     public string String {get => null; set {}}
    9. }
    10.  
    11. [Serializable]
    12. public class AttributeInt: UnityEngine.Object, IAttribute {
    13.     public int _value;
    14.  
    15.     public string Name {get => name; set => name = value;}
    16.     public AttributeType Type {get => AttributeType.Int;}
    17.     public int Int {get => _value; set => _value = value;}
    18.     public float Float {get => (float)_value; set => _value = (int)value;}
    19.     public string String {get => null; set {}}
    20. }
    21.  
    22. [Serializable]
    23. public class AttributeFloat: UnityEngine.Object, IAttribute {
    24.     public float _value;
    25.  
    26.     public string Name {get => name; set => name = value;}
    27.     public AttributeType Type {get => AttributeType.Float;}
    28.     public int Int {get => (int)_value; set => _value = (float)value;}
    29.     public float Float {get => _value; set => _value = value;}
    30.     public string String {get => null; set {}}
    31. }
    32.  
    33. [Serializable]
    34. public class AttributeString: UnityEngine.Object, IAttribute {
    35.     public string _value;
    36.  
    37.     public string Name {get => name; set => name = value;}
    38.     public AttributeType Type {get => AttributeType.String;}
    39.     public int Int {get => int.MinValue; set {}}
    40.     public float Float {get => float.NaN; set {}}
    41.     public string String {get => _value; set => _value = value;}
    42. }
    43.  
    There's also a MonoBehaviour in the mix, sporting an entire list of them IAttributes:

    Code (CSharp):
    1. public class Attributes: MonoBehaviour {
    2.     [SerializeReference] public List<IAttribute> attributes;
    3. }
    And finally, here's what my CustomPropertyDrawer looks like:

    Code (CSharp):
    1.  
    2. [CustomPropertyDrawer(typeof(IAttribute))]
    3. public class AttributeDrawer: PropertyDrawer {
    4.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
    5.         return 16f;
    6.         /*if (attr._value == null) return 16f;
    7.         switch (attr.Type) {
    8.             case AttributeType.None: return 32f;
    9.             default: return 48f;
    10.         }*/
    11.     }
    12.  
    13.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    14.         var attr = (IAttribute)(object)property.objectReferenceValue;
    15.         Debug.Log(attr);
    16.         if (attr == null) {
    17.             var newAttr = new AttributeInt();
    18.             Debug.Log(newAttr);
    19.             attr = newAttr;
    20.             property.objectReferenceValue = (UnityEngine.Object)(object)attr;
    21.             Debug.Log(attr);
    22.             Debug.Log(property.objectReferenceValue);
    23.         }
    24.         /*EditorGUI.BeginProperty(position, label, property);
    25.         var selName = EditorGUI.TextField(new Rect(position.x, position.y, position.width, 16f), attr.Name);
    26.         if (selName != attr.Name) {
    27.             attr.Name = selName;
    28.         }
    29.         var selType = (AttributeType)EditorGUI.EnumPopup(new Rect(position.x, position.y + 16f, position.width, 16f), "Type", attr.Type);
    30.         if (attr.Type != selType) {
    31.             switch (selType) {
    32.                 case AttributeType.None: {
    33.                     attr = new AttributeNone();
    34.                     property.objectReferenceValue = (UnityEngine.Object)(object)attr;
    35.                     break;
    36.                 }
    37.                 case AttributeType.Int: {
    38.                     attr = new AttributeInt();
    39.                     property.objectReferenceValue = (UnityEngine.Object)(object)attr;
    40.                     break;
    41.                 }
    42.                 case AttributeType.Float: {
    43.                     attr = new AttributeFloat();
    44.                     property.objectReferenceValue = (UnityEngine.Object)(object)attr;
    45.                     break;
    46.                 }
    47.                 case AttributeType.String: {
    48.                     attr = new AttributeString();
    49.                     property.objectReferenceValue = (UnityEngine.Object)(object)attr;
    50.                     break;
    51.                 }
    52.             }
    53.         }
    54.         switch (attr.Type) {
    55.             case AttributeType.None: {
    56.                 break;
    57.             }
    58.             case AttributeType.Int: {
    59.                 var i = EditorGUI.IntField(new Rect(position.x, position.y + 32f, position.width, 16f), "Value", attr.Int);
    60.                 if (i != attr.Int) {
    61.                     attr.Int = i;
    62.                 }
    63.                 break;
    64.             }
    65.             case AttributeType.Float: {
    66.                 var f = EditorGUI.FloatField(new Rect(position.x, position.y + 32f, position.width, 16f), "Value", attr.Float);
    67.                 if (f != attr.Float) {
    68.                     attr.Float = f;
    69.                 }
    70.                 break;
    71.             }
    72.             case AttributeType.String: {
    73.                 var s = EditorGUI.TextField(new Rect(position.x, position.y + 32f, position.width, 16f), "Value", attr.String);
    74.                 if (s != attr.String) {
    75.                     attr.String = s;
    76.                 }
    77.                 break;
    78.             }
    79.         }
    80.         EditorGUI.EndProperty();*/
    81.     }
    82. }
    83.  
    Most of it is commented but it's enough to get that whole "type is not a supported pptr value" error. It points to line 54, which is the first thing inside the OnGUI-method. The error is coming from the property.objectReferenceValue dereferencing. I'm using Unity version 2020.1.0a22.2822, so the latest alpha at the time of writing this. A helpful nudge in the right direction would be much appreciated, thanks!

    Kind regards,
    Miika Vihersaari
     
    Last edited: Feb 8, 2020
  2. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    AFAIK you should be using
    managedReferenceValue
    , the
    objectRefenceValue
    was always there for things like Transform and GameObject fields.
     
  3. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    The problem with
    managedReferenceValue
    is that it's write-only. How do I check for null? Also, if I try to write to that field, the editor crashes.
     
  4. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    You can read using the fieldInfo property. Also, I recall seeing somewhere that SerializeReference is not compatible with UnityEngine.Object. Either you use an UnityEngine.Object field (with objectReferenceValue) or you use SerializedReference (with managedReferenceValue).
     
  5. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Thanks! I managed to get one step forward with your help. Now it seems that the Undo-system is somehow holding a grudge against me. I get this error:

    Generating diff of this object for undo because the type tree changed.
    This happens if you have used Undo.RecordObject when changing the script property.
    Please use Undo.RegisterCompleteObjectUndo

    Yep, it's an error, although it doesn't say it is. It has that red octagon with an exclamation mark in the middle. Everything seems to work though...

    Once again, Undo.RegisterCompleteObjectUndo takes a
    UnityEngine.Object
    as parameter. Not sure how I can comply with the instructions in that error, since my field doesn't inherit from UnityEngine.Object. Any ideas?

    Thanks!
     
    brunocoimbra likes this.
  6. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
    RegisterCompleteObjectUndo takes the Object that suffers the modification, you generally send the
    serializedProperty.serializedObject.targetObject
    (you are modifying the value of the
    serializedProperty
    , but the object being modified is the one open in the inspector, which is the
    serializedObject.targetObject
    )
     
  7. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    I tried putting
    Undo.RegisterCompleteObjectUndo(property.serializedObject.targetObject, "Attribute modification");
    at the beginning of the OnGUI, but I keep getting that same error about diff generation. Could this be a bug?
     
  8. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
  9. EngineArtist

    EngineArtist

    Joined:
    Nov 3, 2016
    Posts:
    29
    Sure:

    Attributes.cs (in its full might and glory :))
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEditor;
    3. using UnityEngine;
    4. using System;
    5.  
    6.  
    7. public enum AttributeType {
    8.     None,
    9.     Int,
    10.     Float,
    11.     String,
    12.     Vector2,
    13.     Vector3,
    14.     Vector4,
    15.     Color,
    16.     Rect,
    17.     //Matrix4x4,
    18.     Object,
    19.     GameObject,
    20.     Component,
    21. }
    22.  
    23.  
    24. public static class AttributeTypeExtension {
    25.     public static Type ToType(this AttributeType t) {
    26.         switch (t) {
    27.             case AttributeType.Int: return typeof(int);
    28.             case AttributeType.Float: return typeof(float);
    29.             case AttributeType.String: return typeof(string);
    30.             case AttributeType.Vector2: return typeof(Vector2);
    31.             case AttributeType.Vector3: return typeof(Vector3);
    32.             case AttributeType.Vector4: return typeof(Vector4);
    33.             case AttributeType.Color: return typeof(Color);
    34.             case AttributeType.Rect: return typeof(Rect);
    35.             //case AttributeType.Matrix4x4: return typeof(Matrix4x4);
    36.             case AttributeType.Object: return typeof(UnityEngine.Object);
    37.             case AttributeType.GameObject: return typeof(GameObject);
    38.             case AttributeType.Component: return typeof(Component);
    39.             default: return null;
    40.         }
    41.     }
    42.  
    43.     public static AttributeType ToAttributeType(this Type t) {
    44.         if      (t == null) return AttributeType.None;
    45.         else if (t == typeof(int)) return AttributeType.Int;
    46.         else if (t == typeof(float)) return AttributeType.Float;
    47.         else if (t == typeof(string)) return AttributeType.String;
    48.         else if (t == typeof(Vector2)) return AttributeType.Vector2;
    49.         else if (t == typeof(Vector3)) return AttributeType.Vector3;
    50.         else if (t == typeof(Vector4)) return AttributeType.Vector4;
    51.         else if (t == typeof(Color)) return AttributeType.Color;
    52.         else if (t == typeof(Rect)) return AttributeType.Rect;
    53.         //else if (t == typeof(Matrix4x4)) return AttributeType.Matrix4x4;
    54.         else if (t == typeof(UnityEngine.Object)) return AttributeType.Object;
    55.         else if (t == typeof(GameObject)) return AttributeType.GameObject;
    56.         else if (t == typeof(Component)) return AttributeType.Component;
    57.         else return AttributeType.None;
    58.     }
    59. }
    60.  
    61.  
    62. [Serializable]
    63. public struct Attribute {
    64.     public string name;
    65.     public AttributeType type;
    66.     [SerializeReference] public IAttribute _value;
    67.  
    68.     public string Name {get => name; set => name = value;}
    69.     public AttributeType Type {get => _value.Type;}
    70.     public object Value {get => _value.Value; set => _value.Value = value;}
    71.     public int Int {get => _value.Int; set => _value.Int = value;}
    72.     public float Float {get => _value.Float; set => _value.Float = value;}
    73.     public string String {get => _value.String; set => _value.String = value;}
    74.     public Vector2 Vector2 {get => _value.Vector2; set => _value.Vector2 = value;}
    75.     public Vector3 Vector3 {get => _value.Vector3; set => _value.Vector3 = value;}
    76.     public Vector4 Vector4 {get => _value.Vector4; set => _value.Vector4 = value;}
    77.     public Color Color {get => _value.Color; set => _value.Color = value;}
    78.     public Rect Rect {get => _value.Rect; set => _value.Rect = value;}
    79.     //public Matrix4x4 Matrix4x4 {get => _value.Matrix4x4; set => _value.Matrix4x4 = value;}
    80.     public UnityEngine.Object Object {get => _value.Object; set => _value.Object = value;}
    81.     public GameObject GameObject {get => _value.GameObject; set => _value.GameObject = value;}
    82.     public Component Component {get => _value.Component; set => _value.Component = value;}
    83. }
    84.  
    85.  
    86. public interface IAttribute {
    87.     AttributeType Type {get;}
    88.     object Value {get; set;}
    89.     int Int {get; set;}
    90.     float Float {get; set;}
    91.     string String {get; set;}
    92.     Vector2 Vector2 {get; set;}
    93.     Vector3 Vector3 {get; set;}
    94.     Vector4 Vector4 {get; set;}
    95.     Color Color {get; set;}
    96.     Rect Rect {get; set;}
    97.     //Matrix4x4 Matrix4x4 {get; set;}
    98.     UnityEngine.Object Object {get; set;}
    99.     GameObject GameObject {get; set;}
    100.     Component Component {get; set;}
    101. }
    102.  
    103.  
    104. public class Attributes: MonoBehaviour {
    105.     public List<Attribute> attributes;
    106.  
    107.     public IAttribute Get(string name) {
    108.         for (int i = 0; i < attributes.Count; ++i) {
    109.             if (attributes[i].name == name) {
    110.                 return attributes[i]._value;
    111.             }
    112.         }
    113.         return null;
    114.     }
    115.  
    116.     public T Get<T>(string name) {
    117.         for (int i = 0; i < attributes.Count; ++i) {
    118.             if (attributes[i].name == name) {
    119.                 return (T)attributes[i].Value;
    120.             }
    121.         }
    122.         return default(T);
    123.     }
    124.  
    125.     public void Set(string name, object value) {
    126.         for (int i = 0; i < attributes.Count; ++i) {
    127.             if (attributes[i].name == name) {
    128.                 attributes[i]._value.Value = value;
    129.             }
    130.         }
    131.     }
    132.  
    133.     public void Set<T>(string name, T value) {
    134.         Set(name, value);
    135.     }
    136. }
    137.  
    138. public static class AttributeExtensions {
    139.     public static IAttribute Get(this GameObject gobj, string name) {
    140.         var attr = gobj.GetComponent<Attributes>();
    141.         if (attr == null) return null;
    142.         return attr.Get(name);
    143.     }
    144.  
    145.     public static T Get<T>(this GameObject gobj, string name) {
    146.         var attr = gobj.GetComponent<Attributes>();
    147.         if (attr == null) return default(T);
    148.         return attr.Get<T>(name);
    149.     }
    150.  
    151.     public static void Set(this GameObject gobj, string name, object value) {
    152.         var attr = gobj.GetComponent<Attributes>();
    153.         if (attr == null) return;
    154.         attr.Set(name, value);
    155.     }
    156.  
    157.     public static void Set<T>(this GameObject gobj, string name, T value) {
    158.         var attr = gobj.GetComponent<Attributes>();
    159.         if (attr == null) return;
    160.         attr.Set<T>(name, value);
    161.     }
    162. }
    163.  
    164.  
    165. [CustomPropertyDrawer(typeof(Attribute))]
    166. public class AttributeDrawer: PropertyDrawer {
    167.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
    168.         var attrType = (AttributeType)property.FindPropertyRelative("type").enumValueIndex;
    169.         switch (attrType) {
    170.             case AttributeType.Int: {return 64f;}
    171.             case AttributeType.Float: {return 64f;}
    172.             case AttributeType.String: {return 64f;}
    173.             case AttributeType.Vector2: {return 64f;}
    174.             case AttributeType.Vector3: {return 64f;}
    175.             case AttributeType.Vector4: {return 64f;}
    176.             case AttributeType.Color: {return 64f;}
    177.             case AttributeType.Rect: {return 80f;}
    178.             //case AttributeType.Matrix4x4: {return 80f;}
    179.             case AttributeType.Object: {return 64f;}
    180.             case AttributeType.GameObject: {return 64f;}
    181.             case AttributeType.Component: {return 64f;}
    182.             default: {return 48f;}
    183.         }
    184.     }
    185.  
    186.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
    187.         EditorGUI.BeginProperty(position, label, property);
    188.         var _name = property.FindPropertyRelative("name");
    189.         _name.stringValue = EditorGUI.TextField(new Rect(position.x, position.y, position.width, 16f), _name.stringValue);
    190.         var _type = property.FindPropertyRelative("type");
    191.         var attrType = (AttributeType)_type.enumValueIndex;
    192.         var selType = (AttributeType)EditorGUI.EnumPopup(new Rect(position.x, position.y + 17f, position.width, 16f), "Type", attrType);
    193.         var _value = property.FindPropertyRelative("_value");
    194.         if (selType != attrType) {
    195.             _type.enumValueIndex = (int)selType;
    196.             switch ((AttributeType)_type.enumValueIndex) {
    197.                 case AttributeType.None: {
    198.                     _value.managedReferenceValue = null;
    199.                     break;
    200.                 }
    201.                 case AttributeType.Int: {
    202.                     _value.managedReferenceValue = new AttributeInt();
    203.                     break;
    204.                 }
    205.                 case AttributeType.Float: {
    206.                     _value.managedReferenceValue = new AttributeFloat();
    207.                     break;
    208.                 }
    209.                 case AttributeType.String: {
    210.                     _value.managedReferenceValue = new AttributeString();
    211.                     break;
    212.                 }
    213.                 case AttributeType.Vector2: {
    214.                     _value.managedReferenceValue = new AttributeVector2();
    215.                     break;
    216.                 }
    217.                 case AttributeType.Vector3: {
    218.                     _value.managedReferenceValue = new AttributeVector3();
    219.                     break;
    220.                 }
    221.                 case AttributeType.Vector4: {
    222.                     _value.managedReferenceValue = new AttributeVector4();
    223.                     break;
    224.                 }
    225.                 case AttributeType.Color: {
    226.                     _value.managedReferenceValue = new AttributeColor();
    227.                     break;
    228.                 }
    229.                 case AttributeType.Rect: {
    230.                     _value.managedReferenceValue = new AttributeRect();
    231.                     break;
    232.                 }
    233.                 //case AttributeType.Matrix4x4: {
    234.                 //    _value.managedReferenceValue = new AttributeMatrix4x4();
    235.                 //    break;
    236.                 //}
    237.                 case AttributeType.Object: {
    238.                     _value.managedReferenceValue = new AttributeObject();
    239.                     break;
    240.                 }
    241.                 case AttributeType.GameObject: {
    242.                     _value.managedReferenceValue = new AttributeGameObject();
    243.                     break;
    244.                 }
    245.                 case AttributeType.Component: {
    246.                     _value.managedReferenceValue = new AttributeComponent();
    247.                     break;
    248.                 }
    249.             }
    250.         }
    251.         _value = property.FindPropertyRelative("_value");
    252.         switch (selType) {
    253.             case AttributeType.None: {
    254.                 break;
    255.             }
    256.             case AttributeType.Int: {
    257.                 var i = _value.FindPropertyRelative("_value");
    258.                 i.intValue = EditorGUI.IntField(new Rect(position.x, position.y + 36f, position.width, 16f), "Value", i.intValue);
    259.                 break;
    260.             }
    261.             case AttributeType.Float: {
    262.                 var f = _value.FindPropertyRelative("_value");
    263.                 f.floatValue = EditorGUI.FloatField(new Rect(position.x, position.y + 36f, position.width, 16f), "Value", f.floatValue);
    264.                 break;
    265.             }
    266.             case AttributeType.String: {
    267.                 var str = _value.FindPropertyRelative("_value");
    268.                 str.stringValue = EditorGUI.TextField(new Rect(position.x, position.y + 36f, position.width, 16f), "Value", str.stringValue);
    269.                 break;
    270.             }
    271.             case AttributeType.Vector2: {
    272.                 var vec2 = _value.FindPropertyRelative("_value");
    273.                 vec2.vector2Value = EditorGUI.Vector2Field(new Rect(position.x, position.y + 36f, position.width, 16f), "Value", vec2.vector2Value);
    274.                 break;
    275.             }
    276.             case AttributeType.Vector3: {
    277.                 var vec3 = _value.FindPropertyRelative("_value");
    278.                 vec3.vector3Value = EditorGUI.Vector3Field(new Rect(position.x, position.y + 36f, position.width, 16f), "Value", vec3.vector3Value);
    279.                 break;
    280.             }
    281.             case AttributeType.Vector4: {
    282.                 var vec4 = _value.FindPropertyRelative("_value");
    283.                 vec4.vector4Value = EditorGUI.Vector4Field(new Rect(position.x, position.y + 36f, position.width, 16f), "Value", vec4.vector4Value);
    284.                 break;
    285.             }
    286.             case AttributeType.Color: {
    287.                 var col = _value.FindPropertyRelative("_value");
    288.                 col.colorValue = EditorGUI.ColorField(new Rect(position.x, position.y + 36f, position.width, 16f), "Value", col.colorValue);
    289.                 break;
    290.             }
    291.             case AttributeType.Rect: {
    292.                 var rect = _value.FindPropertyRelative("_value");
    293.                 rect.rectValue = EditorGUI.RectField(new Rect(position.x, position.y + 36f, position.width, 32f), "Value", rect.rectValue);
    294.                 break;
    295.             }
    296.             case AttributeType.Object: {
    297.                 var obj = _value.FindPropertyRelative("_value");
    298.                 obj.objectReferenceValue = EditorGUI.ObjectField(new Rect(position.x, position.y + 36f, position.width, 16f), "Value", obj.objectReferenceValue, typeof(UnityEngine.Object), true);
    299.                 break;
    300.             }
    301.             case AttributeType.GameObject: {
    302.                 EditorGUI.PropertyField(new Rect(position.x, position.y + 36f, position.width, 16f), _value.FindPropertyRelative("_value"));
    303.                 break;
    304.             }
    305.             case AttributeType.Component: {
    306.                 var comp = _value.FindPropertyRelative("_value");
    307.                 comp.objectReferenceValue = EditorGUI.ObjectField(new Rect(position.x, position.y + 36f, position.width, 16f), "Value", comp.objectReferenceValue, typeof(Component), true);
    308.                 break;
    309.             }
    310.         }
    311.         EditorGUI.EndProperty();
    312.     }
    313. }
    And here's one of the implementations of IAttribute:
    AttributeGameObject.cs
    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3.  
    4.  
    5. [Serializable]
    6. public class AttributeGameObject: IAttribute {
    7.     public GameObject _value;
    8.  
    9.     public AttributeType Type {get => AttributeType.GameObject;}
    10.     public object Value {get => (object)_value; set => _value = (GameObject)value;}
    11.     public int Int {get => int.MinValue; set {}}
    12.     public float Float {get => float.NaN; set {}}
    13.     public string String {get => null; set {}}
    14.     public Vector2 Vector2 {get => default(Vector2); set {}}
    15.     public Vector3 Vector3 {get => default(Vector3); set {}}
    16.     public Vector4 Vector4 {get => default(Vector4); set {}}
    17.     public Color Color {get => default(Color); set {}}
    18.     public Rect Rect {get => default(Rect); set {}}
    19.     public Matrix4x4 Matrix4x4 {get => default(Matrix4x4); set {}}
    20.     public UnityEngine.Object Object {get => _value; set {}}
    21.     public GameObject GameObject {get => _value; set => _value = value;}
    22.     public Component Component {get => null; set {}}
    23. }
    I really really appreciate your help on this! Many thanks!

    Kind regards,
    Miika Vihersaari
     
  10. valentingurkov

    valentingurkov

    Joined:
    Jun 17, 2019
    Posts:
    13
  11. brunocoimbra

    brunocoimbra

    Joined:
    Sep 2, 2015
    Posts:
    679
  12. valentingurkov

    valentingurkov

    Joined:
    Jun 17, 2019
    Posts:
    13
    From my time with it - CTRL+Z worked fine but the editor was not so responsive. It could depend on the scenario
     
  13. Chazmundo

    Chazmundo

    Joined:
    Oct 4, 2016
    Posts:
    3
    A bit late to the party, but I have created a custom, reusable solution for the problem of Polymorphic drawers

    Note: I've tested this only briefly and on Unity 2019.4 only but it seems to do its job well. If you see any issues, ping me and I'll fix it up.

    APolymorphicPropertyDrawer.cs (copy into any "Editor" folder)
    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using UnityEditor;
    4. using UnityEngine;
    5.  
    6. public interface IPolymorphicPropertyDrawerInstance<TScriptableObject> where TScriptableObject : ScriptableObject
    7. {
    8.     void OnGUI(Rect position, SerializedObject propertyObj, GUIContent label);
    9.     float GetPropertyHeight(SerializedObject propertyObj, GUIContent label);
    10. }
    11.  
    12. public abstract class APolymorphicPropertyDrawer<TScriptableObject, TPropertyDrawer> : PropertyDrawer where TScriptableObject : ScriptableObject where TPropertyDrawer : PropertyDrawer, IPolymorphicPropertyDrawerInstance<TScriptableObject>
    13. {
    14.     private static Dictionary<Type, TPropertyDrawer> m_TypeToPropertyDrawerMappings;
    15.  
    16.     protected void TryInitPropertyMappings()
    17.     {
    18.         if (m_TypeToPropertyDrawerMappings == null)
    19.         {
    20.             m_TypeToPropertyDrawerMappings = new Dictionary<Type, TPropertyDrawer>();
    21.             DoInitPropertyMappings();
    22.         }
    23.     }
    24.  
    25.     protected abstract void DoInitPropertyMappings();
    26.  
    27.     protected void RegisterPropertyDrawer(Type type, TPropertyDrawer propertyDrawer)
    28.     {
    29.         if (type.IsAbstract)
    30.         {
    31.             throw new Exception($"Unable to register {type.Name} because it is abstract");
    32.         }
    33.  
    34.         m_TypeToPropertyDrawerMappings.Add(type, propertyDrawer);
    35.     }
    36.  
    37.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    38.     {
    39.         TryInitPropertyMappings();
    40.  
    41.         // Using BeginProperty / EndProperty on the parent property means that
    42.         // prefab override logic works on the entire property.
    43.         EditorGUI.BeginProperty(position, label, property);
    44.  
    45.         TPropertyDrawer drawer = GetPropertyDrawer(property);
    46.         if (drawer != null)
    47.         {
    48.             SerializedObject propertyObj = GetSerializedObject(property);
    49.             drawer.OnGUI(position, propertyObj, label);
    50.         }
    51.         else
    52.         {
    53.             EditorGUI.LabelField(position, "Unsupported type");
    54.         }
    55.  
    56.         EditorGUI.EndProperty();
    57.     }
    58.  
    59.     public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
    60.     {
    61.         TryInitPropertyMappings();
    62.  
    63.         SerializedObject propertyObj = GetSerializedObject(property);
    64.         TPropertyDrawer drawer = GetPropertyDrawer(property);
    65.         if (drawer != null)
    66.         {
    67.             return drawer.GetPropertyHeight(propertyObj, label);
    68.         }
    69.  
    70.         return EditorGUIUtility.singleLineHeight;
    71.     }
    72.  
    73.     private SerializedObject GetSerializedObject(SerializedProperty property)
    74.     {
    75.         SerializedObject newObj = new SerializedObject(property.objectReferenceValue);
    76.         return newObj;
    77.     }
    78.  
    79.     private TPropertyDrawer GetPropertyDrawer(SerializedProperty property)
    80.     {
    81.         Type propertyType = property.objectReferenceValue.GetType();
    82.         m_TypeToPropertyDrawerMappings.TryGetValue(propertyType, out var drawer);
    83.  
    84.         return drawer;
    85.     }
    86. }
    That's it! You're ready to go :)

    Looking for an example on how to use it? Check out these two files (data + drawers)

    ExamplePolymorphicData.cs (put anywhere in your project)
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public abstract class AAnimal : ScriptableObject
    4. {
    5.     public string Name;
    6. }
    7.  
    8. public class Lion : AAnimal
    9. {
    10.     public int NumRoars;
    11. }
    12.  
    13. public class Elephant : AAnimal
    14. {
    15.     public bool IsScaredOfMice;
    16. }
    AExamplePolymorphicDrawer.cs (put under an "Editor" folder)
    Code (CSharp):
    1. using UnityEditor;
    2. using UnityEngine;
    3.  
    4. [CustomPropertyDrawer(typeof(AAnimal), true)]
    5. public class AnimalPolymorphicPropertyDrawer : APolymorphicPropertyDrawer<AAnimal, BaseAnimalPropertyDrawer>
    6. {
    7.     protected override void DoInitPropertyMappings()
    8.     {
    9.         RegisterPropertyDrawer(typeof(Lion), new LionPropertyDrawer());
    10.         RegisterPropertyDrawer(typeof(Elephant), new ElephantPropertyDrawer());
    11.     }
    12. }
    13.  
    14. public abstract class BaseAnimalPropertyDrawer : PropertyDrawer, IPolymorphicPropertyDrawerInstance<AAnimal>
    15. {
    16.     private const string ANIMAL_PROPERTY_NAME = nameof(AAnimal.Name);
    17.  
    18.     protected static readonly float SINGLE_PROPERTY_HEIGHT = EditorGUIUtility.singleLineHeight;
    19.  
    20.     public void OnGUI(Rect position, SerializedObject propertyObj, GUIContent label)
    21.     {
    22.         Rect nameRect = new Rect(position.xMin, position.yMin, position.width, SINGLE_PROPERTY_HEIGHT);
    23.  
    24.         // Again being lazy with not caching this for simplicity of the example :)
    25.         GUIContent animalType = new GUIContent(propertyObj.targetObject.GetType().Name);
    26.  
    27.         SerializedProperty nameProperty = propertyObj.FindProperty(ANIMAL_PROPERTY_NAME);
    28.         EditorGUI.PropertyField(nameRect, nameProperty, animalType, true);
    29.  
    30.         float remainingHeight = position.height - SINGLE_PROPERTY_HEIGHT;
    31.         Rect remainingRect = new Rect(position.xMin, nameRect.yMax, position.width, remainingHeight);
    32.         DoOnGUI(remainingRect, propertyObj, label);
    33.     }
    34.  
    35.     protected abstract void DoOnGUI(Rect position, SerializedObject propertyObj, GUIContent label);
    36.  
    37.     public virtual float GetPropertyHeight(SerializedObject propertyObj, GUIContent label)
    38.     {
    39.         // I'm being lazy with not doing this properly per PropertyDrawer override ;)
    40.         return SINGLE_PROPERTY_HEIGHT * 2f;
    41.     }
    42. }
    43.  
    44. public class LionPropertyDrawer : BaseAnimalPropertyDrawer
    45. {
    46.     private const string NUM_TIMES_ROARED_PROPERTY_NAME = nameof(Lion.NumRoars);
    47.  
    48.     protected override void DoOnGUI(Rect position, SerializedObject propertyObj, GUIContent label)
    49.     {
    50.         Rect nameRect = new Rect(position.xMin, position.yMin, position.width, SINGLE_PROPERTY_HEIGHT);
    51.         SerializedProperty numTimesRoaredProperty = propertyObj.FindProperty(NUM_TIMES_ROARED_PROPERTY_NAME);
    52.         EditorGUI.PropertyField(nameRect, numTimesRoaredProperty, true);
    53.     }
    54. }
    55.  
    56. public class ElephantPropertyDrawer : BaseAnimalPropertyDrawer
    57. {
    58.     private const string IS_SCARED_OF_MICE_PROPERTY_NAME = nameof(Elephant.IsScaredOfMice);
    59.  
    60.     protected override void DoOnGUI(Rect position, SerializedObject propertyObj, GUIContent label)
    61.     {
    62.         Rect nameRect = new Rect(position.xMin, position.yMin, position.width, SINGLE_PROPERTY_HEIGHT);
    63.         SerializedProperty isScaredOfMiceProperty = propertyObj.FindProperty(IS_SCARED_OF_MICE_PROPERTY_NAME);
    64.         EditorGUI.PropertyField(nameRect, isScaredOfMiceProperty, true);
    65.     }
    66. }
    67.  

    Proof it works (also using the internal Reorderable List for this example):
    upload_2021-1-8_13-33-22.png

    Pros:
    • Easy to use
    • Type safe
    • Allows for as many dynamic changes as you'd like
    • (mostly) Perf friendly
    • You can even choose to reuse the same PropertyDrawer for multiple classes
    • Will correctly work in collections (including the hidden, internal ReorderableList)

    Cons:
    • APolymorphicPropertyDrawer.GetSerializedObject() is hacky due to underlying Unity serialization problems
    • We do "new SerializedObject" every tick :/ We could write more code to avoid that but it's not a big enough concern to complicate the code further (especially given the above solutions have the exact same problem)
    • Requires custom registration of each PropertyDrawer type*

    *We could improve this to automatically find the property drawers for given types via Reflection in
    APolymorphicPropertyDrawer.TryInitPropertyMappings()
    but I ommitted it here for simplicity and perf reasons. If you'd be interested in a reflection-powered version of this system which automatically finds the properties, just ping me and I'll add it.

    As you can (hopefully) see, it's pretty easy to use. If it's missing any functionality, let me know and I'll adjust it when I have time :)
     

    Attached Files:

    Last edited: Mar 18, 2021
  14. AArtlone

    AArtlone

    Joined:
    Dec 3, 2018
    Posts:
    10
    Hi!. I have just found your example and tried to use it in my project. I am using Unity 2020.2.1f1.

    I am getting an error, because property.objectReferenceValue in the GetSerializedObject(SerializedProperty property) is null.

    Do you know why I am getting this error? Thanks in advance for any help :)