In Part 2 of this series, we looked at some Attributes built into Unity that allow us to customize how fields show up in the inspector. In this post, we’re going to look at how to write our own PropertyAttributes, which combined with PropertyDrawers will let us apply custom displays and custom logic to specific fields in the inspector.
To briefly recap, Attributes are ways to ‘tag’ pieces of code with data that can be accessed by other code using Reflection (aka ‘black magic’). We’ve already used some of these to format the Inspector a bit more nicely, but now let’s look at how to write our own to apply custom logic to fields in the inspector.
In order to write your own property attribute, you’ll want to do the following:
1) Create a new class in a like-named .cs file (note: these do NOT go in an ‘Editor’ folder). The class name should end in Attribute (so RangeAttribute.cs, HeaderAttribute.cs, etc)
2) Have your class extend UnityEngine.PropertyAttribute
You can apply your attribute to a field by using the [ ] notation used earlier (the attribute name is the class name MINUS the ‘Attribute’ at the end, and the parameters inside of the () are the parameters that get passed to your Attribute’s constructor.
PropertyAttributes work with PropertyDrawers to customize how fields are drawn in the inspector (see Part 6 for more on PropertyDrawers). Two things to note when working with PropertyAttributes in a PropertyDrawer instead of simply a Serializable Class:
1) The Attribute’s type should go inside of the [CustomPropertyDrawer(type)] at the top of the PropertyDrawer class definition
2) You can access the instance of your Attribute attached to the field through the attribute property of PropertyDrawer (you’ll likely want to cast it to the specific attribute type you’re using however)
All that being said, let’s dive into our first example – adding custom logic to edit ‘angles’ in our code and ensure that they are always between 0 and one full rotation (360 in degrees, 2 * PI in radians). Note that these will still be represented as floats (as you’ll see in a minute) – we’re not making a custom class, just giving the editor a specific way to edit floats that we tell it are ‘angles’. Here is the code for AngleAttribute.cs:[gist https://gist.github.com/ryanmeier/98efc9dd9035f056ba36]
As you can see, the attribute itself is super simple, we extend PropertyAttribute, add a readonly boolean value to specify if you’re working in radians or degrees, and a constructor which takes a value for radians. Pretty simple stuff, the interesting part comes in when we look at AngleAttributeDrawer.cs:[gist https://gist.github.com/ryanmeier/f0a7db430410b7508ee6]
At the top we have the attributeValue property, which just gets the generic attribute (of type Attribute) and casts it to an AngleAttribute so we can actually use it. In OnGUI, we edit the floatValue of the property as usual, and then call clampAngle() on it, which is probably the most complicated part of this setup (and it’s not even editor code!). Inside of clampAngle, we use the radians property of our attribute to determine what constitutes a ‘full rotation’, and then add/subtract that value until our angle’s value is between 0 and fullRotation.
When we want to define a float as an ‘angle’, we just apply the Angle attribute to it, just like any other attribute:[gist https://gist.github.com/ryanmeier/f53c4ea5681685a9b356]
And what that looks like in the inspector:
As you can see, these look like normal float fields, but if you edit them and they go above 2PI or 360 respectively, they’ll roll over to 0 (and vice versa).
Here’s one more example just to help show off what you can do with Attributes. This post is already getting somewhat long so I won’t get into too much detail, but this should be a good example of adding UI, logic, and functionality with a PropertyAttribute/PropertyDrawer, and also how to use the propertyType property of a SerializedProperty to interact with multiple data types in the same PropertyDrawer.[gist https://gist.github.com/ryanmeier/ad0aaebeeddb0839418e] [gist https://gist.github.com/ryanmeier/c827c47d7ab286e48542]
Essentially, by adding [Incrementable(incrementableBy)] before an int or float, instead of being able to edit it directly, we’ll have + and – buttons which increment the value passed into the constructor. Here’s a screenshot of Incrementable in the inspector:
Next time, we’ll look at writing CustomEditors for MonoBehaviors to completely replace the default implementation with one of our choosing!
If you have any questions, comments, or suggestions about anything in this post or other things you’d like to see covered, feel free to leave them in the comments below!