Unity Technologies Blog: Property Drawers in Unity 4

Unity Technologies Blog
A glimpse inside Unity Technologies...

Property Drawers in Unity 4
Sep 7th 2012, 12:45
A new exciting editor extension feature is coming for Unity 4 – Property Drawers!
What started out as a NinjaCamp project between Rune and I has now been included in Unity. Property Drawers
significantly reduce the amount of work you have to do in order to customize how your scripts look in the Inspector.
You no longer have to write an entire Custom Editor (although that is still supported as well). Instead, you can just
apply appropriate attributes to variables in your scripts to tell the editor how you want those properties to be drawn.
This allows you to enforce sensible settings on your properties, create “intelligent” properties that gather data from a
data source to show a set of valid options, make your scripts more developer friendly or just have a nice tidy up.
Let’s take a look at a script before and after applying some PropertyAttributes to spice things up.

Changing the appearance and behavior of the field controls is as easy as adding attributes to your fields as in the script
below.
public class Example : MonoBehaviour {      public string playerName = "Unnamed";        
[Multiline]      public string playerBiography = "Please enter your biography";        
[Popup ("Warrior", "Mage", "Archer", "Ninja")]      public string @class = "Warrior";        
[Popup ("Human/Local", "Human/Network", "AI/Easy", "AI/Normal", "AI/Hard")]      
public string controller;        [Range (0, 100)]      public float health = 100;        
[Regex (@"^(?:\d{1,3}\.){3}\d{1,3}$", "Invalid IP address!\nExample: '127.0.0.1'")]      
public string serverAddress = "192.168.0.1";        [Compact]      
public Vector3 forward = Vector3.forward;        [Compact]      
public Vector3 target = new Vector3 (100, 200, 300);        public ScaledCurve range;      
public ScaledCurve falloff;        [Angle]      public float turnRate = (Mathf.PI / 3) * 2;}
Here’s how it works. When the Inspector renders the component for your script, it will check for each property if there is a
ProperyDrawer defined for the type of the property or if the property has an attribute that inherits from PropertyAttribute.
If there is, then an associated PropertyDrawer will handle rendering of that single property. You are able to define both
attributes and drawers to customize rendering and data exchange for a given property.

Writing your own PropertyAttribute and PropertyDrawer

Let’s take a closer look at RegexAttribute and its PropertyDrawer to show how it fits together. First off, we should define
an attribute that derives from PropertyAttribute. This attribute can store any kind of data you wish and it is intended to
later be used by the PropertyDrawer.
public class RegexAttribute : PropertyAttribute {      public readonly string pattern;      
public readonly string helpMessage;      public RegexAttribute (string pattern, string 
helpMessage) {          this.pattern = pattern;          this.helpMessage = helpMessage; }  
}
The new RegexAttribute can be applied to a field in a script as shown below.
using UnityEngine;    public class RegexExample : MonoBehaviour {      
[Regex (@"^(?:\d{1,3}\.){3}\d{1,3}$", "Invalid IP address!\nExample: '127.0.0.1'")]      
public string serverAddress = "192.168.0.1";  }
So far we have just created an attribute which holds data. However this alone will not cause anything to render. We
need to define a drawer for the attribute, so let’s implement a RegexDrawer. It has to derive from PropertyDrawer and it
must have a CustomPropertyDrawer attribute which tells Unity which kinds of attributed fields it can draw controls for.
In our case we want it to draw the regex controls for any strings with the RegexAttribute attached to it, so we specify that
here. using UnityEditor; using UnityEngine; using System.Text.RegularExpressions; [CustomPropertyDrawer
(typeof (RegexAttribute))] public class RegexDrawer : PropertyDrawer { // These constants describe the height of the
help box and the text field. const int helpHeight = 30; const int textHeight = 16; // Provide easy access to the
RegexAttribute for reading information from it. RegexAttribute regexAttribute { get { return ((RegexAttribute)attribute); } } //
Here you must define the height of your property drawer. Called by Unity. public override float
GetPropertyHeight (SerializedProperty prop, GUIContent label) { if (IsValid (prop)) return base.GetPropertyHeight
(prop, label); else return base.GetPropertyHeight (prop, label) + helpHeight; }     // Here you can define the GUI for
your property drawer. Called by Unity.     public override void OnGUI (Rect position,             SerializedProperty prop,        
 GUIContent label) {         // Adjust height of the text field         Rect textFieldPosition = position;        
textFieldPosition.height = textHeight;         DrawTextField (textFieldPosition, prop, label);         // Adjust the help
box position to appear indented underneath the text field.         Rect helpPosition = EditorGUI.IndentedRect (position); 
helpPosition.y += textHeight; helpPosition.height = helpHeight; DrawHelpBox (helpPosition, prop);     }    
void DrawTextField (Rect position, SerializedProperty prop, GUIContent label) {         // Draw the text field control
GUI. EditorGUI.BeginChangeCheck ();         string value = EditorGUI.TextField (position, label, prop.stringValue);        
if (EditorGUI.EndChangeCheck ())             prop.stringValue = value;     }     void DrawHelpBox (Rect
position, SerializedProperty prop) {         // No need for a help box if the pattern is valid.         if (IsValid (prop))   return;         EditorGUI.HelpBox (position, regexAttribute.helpMessage, MessageType.Error); } // Test if the propertys string value
matches the regex pattern. bool IsValid (SerializedProperty prop) { return Regex.IsMatch
(prop.stringValue, regexAttribute.pattern); } }

Writing a PropertyDrawer for a custom Serializable Class

Sometimes you have a custom serializable class that is used in multiple different scripts, and you want to use some specific
GUI for that class everywhere it’s used. Consider this ScaledCurve class:
// Custom serializable class  [System.Serializable]  public class ScaledCurve {      public 
float scale = 1;      public AnimationCurve curve = AnimationCurve.Linear (0, 0, 1, 1);  }
Normally every instance of this class would be shown in the Inspector with a foldout, but you can create
a PropertyDrawer to show the class with custom GUI. This GUI is then used everywhere the class is used. The
Property Drawer used here places all the controls in one line. Here’s the image again so you can compare the look
of the Range and Falloff properties without and with the custom PropertyDrawer:

To make a PropertyDrawer for a class, pass the type of that class to the CustomPropertyDrawer attribute of the
PropertyDrawer:
using UnityEngine;  using UnityEditor;    [CustomPropertyDrawer (typeof (ScaledCurve))]  
public class ScaledCurveDrawer : PropertyDrawer {      const int curveWidth = 50;      
const float min = 0;      const float max = 1;      public override void OnGUI (Rect pos, 
SerializedProperty prop, GUIContent label) {          
SerializedProperty scale = prop.FindPropertyRelative ("scale");          
SerializedProperty curve = prop.FindPropertyRelative ("curve");            // Draw scale  
EditorGUI.Slider (              new Rect (pos.x, pos.y, pos.width - curveWidth, pos.height), 
scale, min, max, label);            // Draw curve          
int indent = EditorGUI.indentLevel;          EditorGUI.indentLevel = 0;          
EditorGUI.PropertyField (              new Rect (pos.width - curveWidth, pos.y, curveWidth, 
pos.height),              curve, GUIContent.none);          EditorGUI.indentLevel = indent;
      }  }

Wrapping Up

Finally a few words about how to structure these classes. Your scripts remain in the asset folder as before. Your new
attributes should also be placed in the asset folder just like any scripts, however a recommendation is to keep your asset
folder organized, so consider creating an Attributes folder. Your new property drawers should be placed in an editor
folder, inside your assets folder. Check out this image, hopefully it should make it pretty clear.

If you have open beta access to Unity 4, you can learn more about PropertyDrawers in your local copy of the Unity 4
Script Reference – just search for PropertyDrawer. Once Unity 4 is released, the information will be in the online version
of the Script Reference as well.
That’s all! We’re excited to see what kinds of cool extensions you will create with Unity 4!
Facebook LinkedIn StumbleUpon Twitter

No comments: