Learn Unity Editor Scripting: Property Drawers (Part 2)

Image for post
Image for post

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:

  1. 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.
  2. 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.
Image for post
Image for post

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.

Image for post
Image for post
Showing a label with “TODO” text.
  • 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.

Image for post
Image for post

Explanation of the code:

  • BeginProperty and EndProperty 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 out GetPropertyHeight 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 the if (property.isExpanded) block, instead of showing all the 3 cooldown, power and duration 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 and GetEnumValueFromIndex 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.

Image for post
Image for post

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.

FloatDurationPropertyAttribute.cs

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.

Image for post
Image for post
Inspector UI doesn’t allow changing the time units for cooldown field.
Image for post
Image for post
Inspector UI allows changing the time units for cooldown field.

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.

Image for post
Image for post
Our property for a “cooldown” float field and “color” non-float field.

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 and ConvertFromUnits 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).

Written by

Software Engineer | Indie Game Developer | Founder of No Such Studio. Follow me to learn how to make video games with Unity. http://www.nosuchstudio.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store