In the last post I went over a few simple and ready-available C# Attributes to make better inspectors. This article will cover how you can extend the Unity Editor and make a few attributes yourself.


Create a C# attribute from scratch

Attributes is a language construct in C# that's used to add metadata to classes, methods, and variables. They all inherits from the abstract base class

Attribute
. When working with Unity properties, they have their own abstract base class
PropertyAttribute 
that you should inherit from.


https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/

https://docs.unity3d.com/ScriptReference/PropertyAttribute.html


In the last post I showed how

[TooltipAttribute]
can be used in your projects. That attribute is very simple to recreate and will serve as an example for how to create Unity property attributes with custom property drawers.


Somewhere in your Unity project, create a new C# file named CustomToolTipAttribute.cs. By inheriting from

PropertyAttribute
, which in turn inherits from
System.Attribute
, this is now an attribute. Add public fields to hold whatever data you want to store in the attribute. For this example, we want to display a tool tip which will be a
string

using UnityEngine;

public class CustomToolTipAttribute: PropertyAttribute
{
   public string Tooltip;

   public CustomToolTipAttribute(string toolTip = "")
   {
       Tooltip = toolTip;
   }
}


You can use this attribute in your project by writing

[CustomToolTip]
above a public variable in a MonoBehaviour.

Note: you don't write out the "Attribute" part of the class name.

[CustomToolTip("I am a tool tip")]
public string TestString;
[CustomToolTip("So am I")]
public int TestInt;

We've added the Attribute type to the project but Unity don't know what to do with it yet and no tool tips will be displayed. We also have to add a custom property drawer to tell how Unity should display these variables.


Property drawers are tiny pieces of code in Unity that tells the Inspector how a single property (variable) will be displayed. You can create your own by having classes inherit from

PropertyDrawer
and override its
OnGUI()
method.


Create a new C# file named CustomToolTipPropertyDrawer.cs and make sure it resides in a folder named Editor.

Note: Please refer to this post as to why the filed needs to be in a folder named "Editor"

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(CustomToolTipAttribute))]
public class CustomToolTipPropertyDrawer: PropertyDrawer
{
   public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
   {
       base.OnGUI(position, property, label);
   }
}


The

PropertyDrawer
class can be found in the UnityEditor namespace instead of the normal UnityEngine.
[CustomPropertyDrawer(System.Type)]
is the attribute Unity uses to associate a class or attribute with a property drawer. Without this line your new property drawer will never be shown.


Lets replace the

OnGUI()
method so this CustomToolTip attribute does what it's supposed to do and shows us a tooltip.

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
  var customTooltipAttribute = attribute as CustomToolTipAttribute;
  label.tooltip = customTooltipAttribute.Tooltip;
  EditorGUI.PropertyField(position, property, label);
}

When displaying text in its Inspectors is will use a

GUIContent 
object that supports a tool tip. All we're doing is making sure that field is set and the inspector will now correctly show you the tool tip.

https://docs.unity3d.com/ScriptReference/GUIContent.html


This is how you make a custom PropertyAttribute and associated PropertyDrawer for it. Let us use this knowledge and create something that's not supported out-of-the-box.


NullWarningAttribute

We're making a new Attribute for reference fields (like a variable of a

GameObject
type) that will display a red warning when no value is set. Useful for fields that should never be null and you quickly want to be noticed when they are.


First we'll create a file for this somewhere in the Unity folder named NullWarningAttribute.cs.


using UnityEngine;

public class NullWarningAttribute : PropertyAttribute {}

Next it we'll create a property drawer for this attribute. Create a new file named NullWarningPropertyDrawing and place in within an editor folder so it can access the UnityEditor namespace. Remember the

CustomPropertyDrawer(System.Type)
attribute so Unity know when it should be used.


using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(NullWarningAttribute))]
public class NullWarningPropertyDrawer : PropertyDrawer  {
   base.OnGUI(position, property, label);
}


First of, this property drawer only works with reference fields so we need to check for that. Add the following check to the top of the

OnGUI() 
method.

if(property.propertyType != SerializedPropertyType.ObjectReference) {
 base.OnGUI(position, property, label);
 return;
}

If this attribute is placed on any other type of variable it will be displayed as normal.


Next we need a new

GUIStyle
object to draw a red label with. Create a new one and send in the normal label
GUIStyle
as a template and then make the text red and bold.

var displayStyle = new GUIStyle(GUI.skin.label);
displayStyle.fontStyle = FontStyle.Bold;
displayStyle.normal.textColor = Color.red;


Check if the referenced value is null and use this new GUIStyle to display the label with. Otherwise, display it as normal.

if (property.objectReferenceValue == null) {
  position = EditorGUI.PrefixLabel(position, label, displayStyle);
} else {
  position = EditorGUI.PrefixLabel(position, label);
}


Finally display the property field as normal.

EditorGUI.PropertyField(position, property, GUIContent.none);


If everything went smooth you should have a property drawer file that looks like this.

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(NullWarningAttribute))]
public class NullWarningPropertyDrawer : PropertyDrawer
{
   public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
   {
      if(property.propertyType != SerializedPropertyType.ObjectReference) {
         base.OnGUI(position, property, label);
         return;
      }

      var displayStyle = new GUIStyle(GUI.skin.label);
      displayStyle.fontStyle = FontStyle.Bold;
      displayStyle.normal.textColor = Color.red;

      if (property.objectReferenceValue == null) {
         position = EditorGUI.PrefixLabel(position, label, displayStyle);
      } else {
         position = EditorGUI.PrefixLabel(position, label);
      }

      EditorGUI.PropertyField(position, property, GUIContent.none);
   }
}


Lets try it out.

public class TestScript : MonoBehaviour
{
   [NullWarning]
   public GameObject TestPrefab;
}

As long as the TestPrefab variable is null, the label will scream at you with a red color, but when given a value it's displayed as normal.


LimitFloatAttribute

This attribute will ensure that a variable of the type

float
will remain within a certain range.

Note: This attribute works similar to the [Range] attribute. The difference is when using Range the field will be displayed as a slider. Our field will still be displayed as a normal text box.https://docs.unity3d.com/ScriptReference/RangeAttribute.html


Create a file named LimitAttribute.cs and create two variables, one for the lower bound and one for the upper bound. Make sure that both values can be set directly in the constructor.

using UnityEngine;

public class LimitAttribute: PropertyAttribute
{
   public float MinValue;
   public float MaxValue;

   public LimitAttribute(float minValue = float.MinValue, float maxValue = float.MaxValue)
   {
      MinValue = minValue;
      MaxValue = maxValue;
   }
}

Note: It can be a good idea to set default values to the parameters in the constructor. Here we're using C#'s built in

float.MinValue
and
float.MaxValue
, basically saying that any possible float value is allowed if nothing is specified.


Now create a LimitAttributePropertyDrawer.cs in an Editor folder.

using UnityEditor;
using UnityEngine;

[CustomPropertyDrawer(typeof(LimitAttribute))]
public class LimitAttributePropertyDrawer: PropertyDrawer
{
   public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
   {
      if(property.propertyType != SerializedPropertyType.Float) {
         base.OnGUI(position, property, label);
         return;
      }

      var limitAttribute = attribute as LimitAttribute;

      var editorValue = EditorGUI.FloatField(position, label, property.floatValue);
      property.floatValue = Mathf.Clamp(editorValue, limitAttribute.MinValue, limitAttribute.MaxValue);
   }
}

If the variable is not a float, its displayed as normal. When the variable is a float a normal float field is displayed with

EditorGUI.FloatField()
, and its returned value stored in a temporary variable. The value of the variable is then passed to
Mathf.Clamp()
, ensuring its withing the bounds of MinValue and MaxValue.

https://docs.unity3d.com/ScriptReference/EditorGUILayout.FloatField.html

https://docs.unity3d.com/ScriptReference/Mathf.Clamp.html

Note: This attribute only makes sure that any editing done from the inspector stays within the range. It can't make sure that the variable is changed from somewhere else and no guarantee that the limits are respected. If the limit may not be exceeded, make sure to check the value of the variable at runtime.


Finally we can test this attribute. It will appear as a normal float field, but when you've entered a value that's outside the allowed range it will correct it.

public class TestScript : MonoBehaviour
{
   [Limit(0, 10)]
   public float TestFloat;
}


UnityEditor.EditorGUI

With these examples as a base, you can create all kinds of property drawers to help you and your future game projects. Checkout Unity's documentation for EditorGUI for the available helper methods you can use and go nuts.

https://docs.unity3d.com/ScriptReference/EditorGUI.html


If you've created a useful PropertyDrawer, make sure to tag me on Twitter at @BonaFyrvall and show me you're work.