Learn Unity Editor Scripting: Property Drawers (Part 2)
In this post we will learn how to create custom property drawers for different types. You can tell Unity Editor how exactly you want the Inspector UI for certain fields look like!
This article is part of a series in which I explain all the techniques for extending the Unity Editor. See the overview post here.
Prerequisite: You need to know C# programming and be familiar with Unity Editor to benefit most from this post. You also need to be familiar with Unity’s IMGUI. I’ll make a tutorial on IMGUI soon. Until then, you can refer to Unity documentation.
There is an accompanying Unity project. I highly recommend downloading it and trying the topics as you read them here. Seeing the examples in action makes them stick to your brain.
For instructions on how to get started with the project, see the overview post.
Without further ado, let’s jump in!
Property Drawers
A property drawer tells Unity how to show a property in Inspector window. You can tell Unity to use a property drawers for:
- All properties of a certain type. Unity has default property drawers for primitive types (int, float, string, etc.) and uses them to create default property drawers for our custom classes and structs.
- All properties with a certain Property Attribute. I will explain what they are and will create one later in this post. The attributes that Unity uses like Range, Header, etc. are property attributes.
1. Property Drawers for a Type
Let’s start by writing a Property Drawer for the SpecialAbility
type. By default Unity draws each field of a class or struct with its default property drawer. In our case it is not too bad. For the purpose of this guide, let’s go ahead and write a property drawer that looks different.
Special ability can have 4 different types:
- None: The player cannot use the special ability.
- Dash: Sudden move in the direction of controller. Uses
power
for strength of dash. - Bounce: Reverse the direction of movement as if hitting an invisible wall. Uses
power
for strength of the bounce. - Invisibility: Become invisible for
duration
seconds.
The changes in our custom drawer:
- Instead of the dropdown for selecting the special ability, we are going to use toggle buttons.
- Instead of showing all the fields always, we only show the relevant fields depending on the type. For example in the upper pictures when the type of special ability is dash we don’t show the duration field which isn’t used by the dash ability. In the lower picture where the special ability is set to none, no additional fields are shown.
We are going to use IMGUI. I’m going to write a tutorial on IMGUI soon. Until then if you are not familiar with IMGUI refer to Unity Documentation.
1.1 Implement a Bare Bones Property Drawer
Let’s start by creating a Property Drawer that just shows a label. We will later complete this class.
- The class has to inherit from
PropertyDrawer
. $“{TypeName}PropertyDrawer”
is good naming convention for such property drawers but you could use any name you like.CustomePropertyDrawer
attribute tells Unity to use this class for showing properties of the given type in Inspector.OnGUI
is the callback that handles drawing the property using IMGUI API.- The code in
SpecialAbilityPropertyDrawer.cs
is Editor only code and should reside in an Editor folder. You don’t want to ship this class with your game.
1.2 Implement the Default Property Drawer
Now let’s implement the drawer so that it looks like Unity’s default property drawer. The one we would get before implementing our own. I’ve just added a HelpBox
to the end to make sure we see our property drawer not the default one.
Explanation of the code:
BeginProperty
andEndProperty
are needed to handle highlighting the UI elements that are changed from the base prefab.- We need to explicitly calculate the
Rect
for each field / control. A bit cumbersome but not complicated.
You cannot use
EditorGUILayout
namespace in property drawers.
EditorGUI.PropertyField(…)
shows Unity’s default property drawer for the given property.- Since our property drawer spans multiple lines, we need to override
GetPropertyHeight
method as well. Without it, the UI controls would overlap controls from other property drawers. Try it! Comment outGetPropertyHeight
method and see what happens.
1.3 Implement Our Own Property Drawer
Now that we are a bit familiar with how to implement property drawers, let’s get rid of some of the EditorGUI.PropertyField
calls and implement our own controls too.
Code explanation:
- In
GetPropertyHeight
we simply add the number of fields we want to show depending on the current selected type of the special ability. - In
OnGUI
in theif (property.isExpanded)
block, instead of showing all the 3cooldown
,power
andduration
fields, we show only the controls that apply to the current special ability type. ShowTypeField
helper shows the toggle buttons for selecting the special ability type.GetEnumValueIndex
andGetEnumValueFromIndex
are helpers for handling enum values with Unity’s serialized properties. Look at the post explaining why they are needed. (TODO link)
2. Property Drawers for properties with a certain Property Attributes
In some cases we want to draw the same type differently depending on the context. For example a float
value could represent a duration, mass, an angle or many other things. And we could have different Property Drawers for float
type depending on what that float
represents.
Here are a few examples of different drawers we could implement:
- For an angle value, we could snap the value to multiples of 30. Or make a toggle for converting between degrees and radians.
- For a duration value, we could make a drop down for converting between seconds, minutes and hours.
- For a date value (timestamp), we could create a fancy calendar control using IMGUI.
Let’s go ahead and create a property drawer for float
properties that represent duration. In our property drawer, we are going to let the programmer decide what units (seconds, minutes or hours) the value is in. We also let the developer choose the units from the Inspector UI.
2.1 Implement a Custom Property Attribute
PropertyAttribute
is a Unity class that is used for connecting fields to property drawers. We can create new classes that extend PropertyAttribute
and then use our attributes with fields to have Unity Inspector use our Property Drawers for those fields.
In the previous section our property drawer was used for EVERY property of SpecialAbility
type. This time, our drawer will be used for every property with a certain Property Attribute regardless of its type.
Here is the code for our property attribute.
We can have parameters in attributes. Header
, Tooltip
and Range
attributes have parameters. In this case we have one parameter: unitsMode
. It allows the developer to select a fixed unit for a field or show the UI for selecting the units in the Inspector window.
We haven’t implemented the property drawer yet. But here is how we want it to behave. If the developer selects a unit in the property attribute, then the UI shows that unit (seconds, minutes or hours) and doesn’t let the developer change it from Inspector. If the developer selects the flexible
unit mode, then the Inspector UI shows a dropdown for changing the unit.
Note that unlike the code for property drawers, the property attributes are not Editor code and should be accessible from your normal code.
2.2. Implement a Bare Bone Float Duration Property Drawer
Next, we need to create a PropertyDrawer
just like we did in the previous section. Let’s start with a bare-bone drawer that just checks the type of property to make sure it is a float property.
Note that in our drawer, we put our newly created attribute in the CustomePropertyDrawer
attribute. Now Unity knows to use our FloatDurationPropertyDrawer
for every fields that has FloatDurationPropertyAttribute
on it.
The check we do for the property is necessary! Because we can use the attribute
on fields of any type.
Here is how the Inspector looks like for the cooldown
property and the color
property.
Note that a non-float field will show the proper error message so that the developer can go fix the problem.
2.3 Implement our custom Float Duration Property Drawer
Now let’s get our hands dirty with the actual UI code.
The code is fairly straightforward, we convert the float value to the proper units for display and then convert it back to seconds when writing it to the property.
DrawFloatDurationField
helper handles the actual drawing of the field.ConvertToUnits
andConvertFromUnits
helpers convert the property value to display value and back.
Conclusion
In this post we learned how to create custom property drawers and tell unity to use them with the help of the CustomPropertyDrawer
attribute. We also saw how to create our own PropertyAttributes
and use them in our custom property drawers.
If you have any questions and suggestions about this post, feel free to leave a comment.
If you find this tutorial useful, please support me on Patreon. It takes a good amount of time to write these tutorials and your support will keep me going. Thank you!
In the next post, we will see how to create custom Editors for our components in the Inspector window (ETA Oct 21st 2020).