Skip to content

Property Drawer

Property Drawers are used to draw any field of a certain type with a custom VisualElement or IMGUI in all inspectors.

You can learn more about PropertyDrawers reading the documentation:

Since I focus mainly of VisualElements, I will explain a few tricks I use with that.

To avoid the issue mentioned above I prefer writing my property drawers like so:

1
[CustomPropertyDrawer( typeof(MyClass) )]
2
public class MyClassDrawerUIE : PropertyDrawer
3
{
4
public override VisualElement CreatePropertyGUI( SerializedProperty property )
5
{
6
var container = new View( property );
7
return container;
8
}
9
10
private class View : VisualElement
11
{
12
private SerializedProperty m_SerializedProperty;
13
14
public OptionalBlendCurveVisualElement ( SerializedProperty property )
15
{
16
m_SerializedProperty = property;
17
// Create property container element.
18
var container = new VisualElement();
19
Add(container);
20
21
//...Add stuff to my container here.
22
}
23
}
24
}

This ensures I get a unique view object for each property and cache the property in there.

Get context of the view

If I needed to get a parent VisualElement to get some context for my drawer I can now do so by checking the GeometryChangedEvent.

1
private class View : VisualElement
2
{
3
private SerializedProperty m_SerializedProperty;
4
5
public OptionalBlendCurveVisualElement ( SerializedProperty property )
6
{
7
m_SerializedProperty = property;
8
// Create property container element.
9
var container = new VisualElement();
10
Add(container);
11
12
RegisterCallback<GeometryChangedEvent>( OnGeometryChangedEvent );
13
14
//...Add stuff to my container here.
15
}
16
17
private void OnGeometryChangedEvent( GeometryChangedEvent evt )
18
{
19
var parentVisualElement = GetFirstAncestorOfType<MyParentVisualElement>();
20
//... Get the values you need from the parent visualElement like a custom Inspector or Editor Window.
21
}
22
}

There are other interesting events you can listen to. Most of them can be found on this documentation page.

Get the value of the Serialized Property

In many cases when using Property Drawers you’ll want to get the actual value of the Property you are drawing. Unity does not make this particularly easy.

So here are some static functions I recommend you add in a static utility class:

1
public static SerializedProperty GetParentProperty(SerializedProperty property)
2
{
3
var propertyParentParentPath =
4
property.propertyPath.Substring(0, property.propertyPath.LastIndexOf("."));
5
return property.serializedObject.FindProperty(propertyParentParentPath);
6
}
7
8
public static object GetSerializedPropertyParent(UnityEditor.SerializedProperty prop)
9
{
10
return GetSerializedPropertyValue(prop, 1);
11
}
12
13
public static object GetSerializedPropertyValue(UnityEditor.SerializedProperty prop, int inverseDepth = 0)
14
{
15
var path = prop.propertyPath.Replace(".Array.data[", "[");
16
object obj = prop.serializedObject.targetObject;
17
var elements = path.Split('.');
18
for (int i = 0; i < elements.Length; i++) {
19
if (elements.Length - i < inverseDepth) { return obj; }
20
string element = elements[i];
21
if (element.Contains("[")) {
22
var elementName = element.Substring(0, element.IndexOf("["));
23
var index = Convert.ToInt32(element.Substring(element.IndexOf("[")).Replace("[", "")
24
.Replace("]", ""));
25
obj = GetPropertyFieldValue(obj, elementName, index);
26
} else { obj = GetPropertyFieldValue(obj, element); }
27
}
28
return obj;
29
}
30
31
public static object GetPropertyFieldValue(object source, string name)
32
{
33
if (source == null) { return null; }
34
var type = source.GetType();
35
var f = type.GetField(name,
36
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public |
37
System.Reflection.BindingFlags.Instance);
38
39
if (f == null) {
40
var p = type.GetProperty(name,
41
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public |
42
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.IgnoreCase);
43
if (p == null) { return null; }
44
return p.GetValue(source, null);
45
}
46
return f.GetValue(source);
47
}
48
49
public static object GetPropertyFieldValue(object source, string name, int index)
50
{
51
var enumerable = GetPropertyFieldValue(source, name) as IEnumerable;
52
var enm = enumerable.GetEnumerator();
53
while (index-- >= 0)
54
enm.MoveNext();
55
return enm.Current;
56
}

Update the view when the property changes

Finally some additional extension functions that are quite useful to know about are the Binding Extensions. Used to Bind and keep track of the serialized object and properties. It’s particularly useful to know when the property has changed and the visual Element should be updated to reflect that change.

1
private class View : VisualElement
2
{
3
private SerializedProperty m_SerializedProperty;
4
5
public OptionalBlendCurveVisualElement ( SerializedProperty property )
6
{
7
m_SerializedProperty = property;
8
// Create property container element.
9
var container = new VisualElement();
10
Add(container);
11
12
TrackPropertyValue( property, OnValueChanged );
13
14
//...Add stuff to my container here.
15
}
16
17
private void OnValueChanged( SerializedProperty property )
18
{
19
// The Bound property has changed.
20
//...Refresh your Visual Elements
21
}
22
}