Hello,
For some reason lots of people favor creating custom editors over property drawers, however these just don’t scale, creating lots of custom drawers in a large project is a time sink. Luckily this is where property drawers come in, tagging a field with an attribute (such as [range(…)] ), allows you to draw that field in a custom manner. without having to re-create a custom editor.
The Problem
You can only have one property drawer per variable. Annotating a variable with multiple property Drawers, results in a random property drawer, drawing that variable in the inspector. There are Decorator Drawers, of which you can have multiple per variable. However, with these you do not get access to the property itself, just the location on screen which means it has very limited use.
This means creating something like the following image, is more difficult than it needs to be, and ill show a better way to do this in this thread.
The Solution
I have created a property drawer which allows multiple other attribute to alter how the variable is drawn. The one caveat is that these other attribute must inherit from my new attribute MultiPropertyAttribute
The Attribute
[AttributeUsage(AttributeTargets.Field)]
public abstract class MultiPropertyAttribute : PropertyAttribute
{
public List<object> stored = new List<object>();
public virtual GUIContent BuildLabel(GUIContent label)
{
return label;
}
public abstract void OnGUI(Rect position, SerializedProperty property, GUIContent label);
internal virtual float? GetPropertyHeight( SerializedProperty property, GUIContent label)
{
return null;
}
}
The Drawer which controls the other attributes drawing
[CustomPropertyDrawer(typeof(MultiPropertyAttribute),true)]
public class MultiPropertyDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
MultiPropertyAttribute @Attribute = attribute as MultiPropertyAttribute;
float height = base.GetPropertyHeight(property, label);
foreach (object atr in @Attribute.stored)//Go through the attributes, and try to get an altered height, if no altered height return default height.
{
if (atr as MultiPropertyAttribute != null)
{
//build label here too?
var tempheight = ((MultiPropertyAttribute)atr).GetPropertyHeight(property, label);
if (tempheight.HasValue)
{
height = tempheight.Value;
break;
}
}
}
return height;
}
// Draw the property inside the given rect
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
MultiPropertyAttribute @Attribute = attribute as MultiPropertyAttribute;
// First get the attribute since it contains the range for the slider
if (@Attribute.stored == null || @Attribute.stored.Count == 0)
{
@Attribute.stored = fieldInfo.GetCustomAttributes(typeof(MultiPropertyAttribute), false).OrderBy(s => ((PropertyAttribute)s).order).ToList() ;
}
var OrigColor = GUI.color;
var Label = label;
foreach (object atr in @Attribute.stored)
{
if (atr as MultiPropertyAttribute != null)
{
Label = ((MultiPropertyAttribute)atr).BuildLabel(Label);
((MultiPropertyAttribute)atr).OnGUI(position, property, Label);
}
}
GUI.color = OrigColor;
}
}
These two classes are the base of being able to have multiple attributes affect how the variable is drawn.
If for instance, we want to color a field and have this field be a range field we need to implement the two attributes, Color and NewRange
public class ColorAttribute : MultiPropertyAttribute
{
Color Color;
public ColorAttribute(float R, float G, float B)
{
Color = new Color(R, G, B);
}
// Draw the property inside the given rect
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.color = Color;
}
}
public class NewRangeAttribute : MultiPropertyAttribute
{
float min;
float max;
public NewRangeAttribute(float min, float max)
{
this.min = min;
this.max = max;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
if (property.propertyType == SerializedPropertyType.Float)
EditorGUI.Slider(position, property, min, max, label);
else
EditorGUI.LabelField(position, label.text, "Use Range with float or int.");
}
}
Note: How these classes don’t have a drawer class, instead these attributes hold the ONGUI inside them.
So, How do you declare a variable which we want to color red, and be a range slider?
[Color(1, 0, 0, order = 0)]
[NewRange(0, 2.0f,order = 1)]
public float ColorRange = 0.1f;
Note: the order = sent in controls which order the attributes will be drawn in
And now we have two attributes drawing one variable. Something unity does not stock stupport.
You may notice that I have a LabelBuilder function. This is so you can alter how the label looks. For example, you may want an icon in your variables label, the following attribute shows you how this is achieved.
public class Prefab : MultiPropertyAttribute
{
GUIContent Icon;
public Prefab()
{
Icon = EditorGUIUtility.IconContent("Prefab Icon");
}
public override GUIContent BuildLabel(GUIContent label)
{
Icon.text = label.text;
return Icon;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
}
}
And that can be declared like:
[Prefab]
[Color(0,0,1)]
[NewRange(0,1,order = 1)]
public float PrefabColorRange = 0.1f;
**Note:**prefab and color don’t have an order, by default they will be order = 0, however it doesn’t matter which way around these two would get applied
Here we see the prefab icon has been prefixed to the label, it is color blue and it is a ranged slider.
Lastly, I support Changing the height of the variable in the inspector. The example for this is a foldout, where the height changes depending on if it is collapsed or open.
(quick code, find property will not always work)
public class FoldOutAttribute : MultiPropertyAttribute
{
string[] VarNames;
bool Foldout = false;
GUIContent Title;
public FoldOutAttribute(string title,params string[] varNames)
{
VarNames = new string[varNames.Length];
VarNames = varNames;
Title = new GUIContent(title);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
position.height = EditorGUIUtility.singleLineHeight;
Foldout = EditorGUI.Foldout(position, Foldout, Title);
if (Foldout)
{
++EditorGUI.indentLevel;
Rect newpos = position;
newpos.y += EditorGUIUtility.singleLineHeight + 2;
EditorGUI.PropertyField(newpos, property);
for (int i = 0; i < VarNames.Length; i++)
{
var a = property.serializedObject.FindProperty(VarNames[i]);
newpos.y += EditorGUIUtility.singleLineHeight +2;
newpos.height = EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(newpos, a);
}
}
}
internal override float GetPropertyHeight( SerializedProperty property, GUIContent label)
{
if (Foldout)
{
return (float)((EditorGUIUtility.singleLineHeight + 2) * (VarNames.Count() +2));
}
else return float.NegativeInfinity;
;
}
}
Here I alter the drawer height, and what is being drawn when the foldout is open.
you can define your variable like this:
[Color(0, 1, 0, order = 0)]
[FoldOut("My Multi Property Drawers", "PrefabColorRange", "ColorRange", order = 1)]
public float ColorFoldOut;
closed
open
I hope somebody will find this useful, it has changed the game of how I create properties now.
Thanks





