Categories & Tags
Archive

Property Drawers in Unity 4

September 7, 2012 in Tech by

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!

Share this post

Comments (32)

Comments are closed.

asancio
7 Sep 2012, 6:55 am

Amazing! One of the best new features!

7 Sep 2012, 8:03 am

Mind blowing, no more need to write custom inspectors (or almost)! Keep coming those killer features! XD

Ayrik
7 Sep 2012, 10:22 am

Wow that’s great and unexpected. One question, will this “just work” with the new prefab exposed property sheet?

Lux
7 Sep 2012, 11:16 am

Really nice!

7 Sep 2012, 11:34 am

This is awesomesaucestuff!!!

7 Sep 2012, 11:54 am

Great Job. These were certainly needed.

Alex
7 Sep 2012, 12:04 pm

This is just efing awesome.

Joachim Ante
7 Sep 2012, 12:05 pm

> Wow that’s great and unexpected. One question, will this “just work” with the new prefab exposed property sheet?

Yes. It’s magic.

Milad
7 Sep 2012, 12:14 pm

Very Useful.thanks.

7 Sep 2012, 4:16 pm

Fantastic guys..I love the ninjacamp projects. I wish more come into unity. Like the CodeEditor for example :-P
Again great work and usefull.

7 Sep 2012, 8:13 pm

One of the best features of unity4. You know, mechanim and dx11 are great but those are not useful for all projects. these usability features like custom inspectors in this new ways and prefab property exposing and component managment features are really great additions to any project.

Robert Duffill
7 Sep 2012, 11:49 pm

I would have to join the consensus that this is one of the most fundamental changes coming with Unity4. The scope of larger projects and not being a programmer myself this is a true blessing. Great work and thankyou!

BigBulle
8 Sep 2012, 12:47 am

This looks really great but it would be wonderfull if inspector properties were created from C# properties instead of public fields. This would really clean the design of the scripts….

Gregory Pierce
8 Sep 2012, 10:25 am

Brilliant!

8 Sep 2012, 2:23 pm

Thats an absolutely fantastic addition :)

Joachim Ante
9 Sep 2012, 3:11 am

@This looks really great but it would be wonderfull if inspector properties were created from C# properties instead of public fields. This would really clean the design of the scripts….

You can serialize private variables using [SerializeField].

Properties can unfortunately not be serialized since we would be C# code from other threads. It is very easy to introduce hard to find crashes and bugs this way.

eddy
9 Sep 2012, 1:15 pm

What happens if attribute and property are mismatched ?
In this case, what is the best practice ?

9 Sep 2012, 1:33 pm

> What happens if attribute and property are mismatched ? In this case, what is the best practice ?

Make your PropertyDrawer check the type of the SerializedProperty and make it show an error label if it’s not the right type.

Divide
9 Sep 2012, 11:58 pm

Awesome! This is a really great feature! I’m betting this will also be clever and for prefab instances correctly bold your property, and support Undo in the correct way?

Do you think you could also support buttons? For instance, you can use [ContextItem()] to add a function, but for the level designer its rather hidden away and requires 2 clicks. A button would be more easily accessible.

10 Sep 2012, 4:48 am

> I’m betting this will also be clever and for prefab instances correctly bold your property, and support Undo in the correct way?

Yes, if you follow the guidelines on how to work with SerializedProperties. The PropertyDrawers are no different from creating custom editors in that regard.

> Do you think you could also support buttons?

The PropertyDrawers can display any GUI you want for your properties (script variables). It can’t show GUI that’s not associated with any properties. If you need to do that, write a custom editor.

Kermitt
10 Sep 2012, 5:51 pm

CustomPropertyDrawers seem to support multiple object editing by default. Is there any way to tell them not to?

Kermitt
10 Sep 2012, 9:08 pm

Another thing I’m stuck on… I have a custom property drawer which is displaying an array of items. The items in the array also have custom property drawers. How can I get the height of the property drawers for the items, so I can calculate the overall height of the array?
I almost succeeded in doing this via reflection but got stumped when I found the type is not exposed on the CustomPropertyDrawer attribute so I have no way of finding what types a PropertyDrawer handles.

11 Sep 2012, 1:45 am

> CustomPropertyDrawers seem to support multiple object editing by default. Is there any way to tell them not to?

No. Multi-object editing support is decided on a per-component basis, not a per-property basis. The best you can do for a property is detect if there’s multiple objects (property.serializedObject.isEditingMultipleObjects) and if so display a label saying that multi-object editing is not supported for the property.

> How can I get the height of the property drawers for the items, so I can calculate the overall height of the array?

Hmm, we’ll expose a function to get the height of a property. It’ll be in one of the next betas.

Japtar
11 Sep 2012, 12:43 pm

Nice feature. Quick question, but can C#’s dictionaries appear as an editable variable on the inspector for Unity 4? Or will we have to spin our own?

Kermitt
12 Sep 2012, 10:51 pm

Another small bug… When the animationCurveValue is changed on a property (while in PropertyDrawer.OnGUI), the small image of the curve doesn’t change in the inspector. The correct curve does appear up in the curve editor, and the small image appears correctly after selecting a different object then back again (i.e. forcing the inspector to refresh).

Kermitt
12 Sep 2012, 11:04 pm

Well I seem to have found a workaround by using a CurveField directly instead of a PropertyField. The problem seems to be with PropertyField.

13 Sep 2012, 4:57 am

Please report bugs using the bug reporter tool if you want them to actually get fixed. We don’t track bug reports in blog comments. :)

15 Sep 2012, 4:36 am

That’s great!!! I really look forward unity4 pro

José Augusto Thomas
17 Sep 2012, 3:31 pm

Amazing feature! Really helpful.
Some change to implement an attribute to assign comments to our variables?
Something like:

[VariableCommentary("Controls the speed of the character in meters per second")]
public float speed = 2;

If not, this is possible to be written by our own with a custom attribute?
This would be just awesome!

22 Sep 2012, 1:11 pm

Yet another main distinction between an genuine NFL jersey by using a replica jersey is that genuine jerseys occasionally have a very player’s signature or autograph. In stores that offer them, they frequently include other freebies.

9 Oct 2012, 5:08 am

Yes!!! I’m totally OSD with custom editors, and always create tons of them. This feature is so smart and useful: love it! :)

Benjamin David
3 Dec 2012, 8:05 pm

Ah, we don’t have to create new enums for popping up in the editor anymore :)

Leave a Reply

Comments are closed.