Custom Property Drawers
Design your own GUI to modify Unity’s inspector
Welcome! This tutorial is about the implementation of so called Custom Property Drawers in Unity. With them you can add functionality to the Unity editor. By specifying the appearance of your own classes in the Unity inspector you make it easier to configure your game objects. Therefore you can make the whole game development process with Unity more comfortable.
Thanks for reading and I hope you’ll enjoy the article!
Introduction
For the enemy selection we need to consider many different parameters: camera viewing angle, player position, rotation and more. Additionally these are continuous parameters which means that there are – philosophically speaking – infinite number of possible values. For example the camera viewing angle can be anything between 0° and 360° – even values like 156,468396820° are possible. So the question is how to consider all this endless number of values in a neat and comfortable way.
For this we’re using curves
Unity’s Animation Curves
Unity already comes with curves – the so called Animation Curves. Here’s a short overview of their (default) properties:

Key
is an XY-coordinate and defines a tangent
Presets
Choose from predefined curves
New
Store curve into presets-list
Then you’re able to evaluate the value (y) of the curve on a given time (x).
Activatable Interval Curves
Unity’s animation curves are really awesome, but we need some additional functionality:
Let’s start coding!
We decided to seperate the interval’s code from the curve’s code. That’s why we firstly create a new script „Interval.cs“ in the project view by right-clicking on your Scripts-directory and selecting „Create -> C# script“ from the context menu.
Then copy the code from below and insert it into the new file. The comments in the snippet describe each code block in detail.
using UnityEngine; using System.Collections; namespace TFGames { // Even though we'll write our own Property Drawer later, we make the class // serializable to temporarily use the default inspector for debug reasons // // Additionally the class doesn't need to inherit from MonoBehaviour since // it represents a property itself. It will be used by other scripts, but is // never attached to GameObjects directly // [System.Serializable] public class Interval { [Tooltip("Minimum value of the interval")] public float min; [Tooltip("Maximum value of the interval")] public float max; [Tooltip("Check if a value should be mirrored at the zero point")] public bool mirrored; [Tooltip("The value which schould be mirrored at the zero point")] public float mirrorValue; // The absolute size of the interval // Examples: // - size(0;3) = 3; // - size(-2;5) = 7; // - size(5;10) = 5; // public float Size { get { return Max - Min; } } // Returns the minimum value of the interval considering whether the interval is mirrored or not // public float Min { get { if (!mirrored) { return min; } else { // In mirrored-mode the minimum is always a negative value return mirrorValue >= 0 ? -mirrorValue : mirrorValue; } } } // Returns the maximum value of the interval considering whether the interval is mirrored or not // public float Max { get { if (!mirrored) { return max; } else { // In mirrored-mode the maximum is always a positive value return mirrorValue >= 0 ? mirrorValue : -mirrorValue; } } } // Some different constructors // public Interval(float mirrorValue) { this.mirrored = true; this.mirrorValue = mirrorValue; } public Interval(float min, float max) { this.mirrored = false; this.min = min; this.max = max; } public Interval(float min, float max, bool mirrored) { this.mirrored = mirrored; this.mirrorValue = min; this.min = min; this.max = max; } // The method maps the given value onto a (0;1)-interval // with open ends // // Examples: // - Interval ( 1; 3) -> normalize(2) = 0.5; // - Interval ( 0; 1) -> normalize(0.5) = 0.5; // - Interval (-2; 2) -> normalize(-3) = -0.25; // - Interval ( 2; 4) -> normalize(5) = 1.5; // public float Normalize(float value) { return (value - min) / Size; } // The method maps the given value onto a (0;1)-interval. // The interval's boundaries are considered and the result // is clamped between 0 and 1 // // Examples: // - Interval ( 1; 3) -> normClamped(2) = 0.5; // - Interval ( 0; 1) -> normClamped(0.5) = 0.5; // - Interval (-2; 2) -> normClamped(-3) = 0; // - Interval ( 2; 4) -> normClamped(5) = 1; // public float NormalizeClamped(float value) { return Mathf.Clamp01(Normalize(value)); } } }
Now we have to combine Unity’s animation curve with the currently created Interval-class. Add a new script named „ActivatableIntervalCurve.cs“ by right-clicking onto your Scripts-directory in the project view and selecting „Create -> C# script“ from the context menu.
Then copy the code below and insert it into the new file. The comments in the snippet describe each code block in detail.
using UnityEngine; using System.Collections; namespace TFGames { // Even though we'll write our own Property Drawer later, we make the class // serializable to temporarily use the default inspector for debug reasons // // Additionally the class doesn't need to inherit from MonoBehaviour since // it represents a property itself which will be used by other scripts, but is // never attached to GameObjects directly // [System.Serializable] public class ActivatableRatioCurve { [Tooltip("Check if the curve should be evaluated n" + "Uncheck if the default value should be returned instead")] public bool curveEnabled; [Tooltip("The default value is returned when the curve is disabled")] public float defaultValue; [Tooltip("The curve is evaluated when 'Curve Enabled' is checked")] public AnimationCurve curve; [Tooltip("The interval to which the x-axis of the curve should be mapped n" + " 0 is mapped to the interval's min-value" + " 1 is mapped to the interval's max-value")] public Interval interval; // The method evaluates the curve on the given time or - if the curve // is disabled - returns the default value // public float Evaluate(float time) { return curveEnabled ? EvaluateCurve(time) : defaultValue; } // The given time in interval-space is transformed into a value between // 0 and 1, which is used to evaluate Unity's animation curve. // private float EvaluateCurve(float time) { float normalizedValue = interval.NormalizeClamped(time); return curve.Evaluate(normalizedValue); } } }
Custom Property Drawers
Currently the inspector of our activatable interval curve looks like this:
This doesn’t look very convenient, right? The data isn’t well-structured, all information is shown at once, and the curve and interval don’t seem to belong together. Ok, let’s make some considerations about how this can be improved…
What is your policy on using your tutorials while developing my own game? It seems the tutorial was made for general use, but the lawyer-people have me scared to use any tutorial without first checking to make sure there are no ’strings attached‘.
Thanks,
KnightOwl
(aka: overly-cautious fellow game developer)
Hey KnightOwl,
thanks for your advice that we have to add a policy-remark to our tutorials!
The „Property-Drawer“-tutorial and all its attached images and project files are for private and commercial use. So don’t worry about that 🙂
Thanks for the comment and have a nice day,
Severin
Now that I have more closely looked through the tutorial, it is actually pretty good!
Drawers seem like a lesser used but very powerful feature of Unity. Sadly it looks like there has not been much good documentation on how to implement this feature in a successful manner in the past. Thank you for covering this well, it has already sparked some ideas on how to better use Drawers while developing my own game.
Good luck with all your endeavors and hope to see more insightful tutorials in the future. If you ever need insight implementing engineering simulations in the Unity platform, let me know 😛
Awesome! Keep your hard work going <3
Thank you! We will 🙂
This single tutorial lead to a 3 week rewrite to some of the most fundamental code in our game… Now that it is done, we are much happier with the foundation we are building our game on. The visualizations we now see, provided through property drawers and custom editors, helps out tremendously in making sure our simulation is as accurate as possible. Thanks for providing the details that make this Unity feature truly useful, like nesting and incorporating the animation curve.
Great! We’re really happy that you found our tutorial helpful 🙂
Thanks that you shared your thought with us – it feels good to get (positive) feedback.
We wish you all the best for your projects