Unity property drawers

Unity’s Property Drawers: Customizing Inspector Appearance

The Unity Inspector is the command center for any game developer. It’s where you tweak values, assign assets, and configure components. But let’s be honest: the default Inspector can sometimes feel a bit… generic. As your projects grow in complexity, you might find yourself wrestling with long lists of numbers, confusing variable names, and a layout that’s more cumbersome than convenient.

What if you could transform that generic interface into a tailor-made toolkit, designed specifically for your game’s needs? What if you could add sliders, colors, validation, and custom layouts to make your workflow faster, more intuitive, and less prone to errors?

That’s where Unity’s Property Drawers come in. These powerful yet often-overlooked tools allow you to take control of how your script variables (or “properties”) are displayed in the Inspector, opening up a world of customization. This guide will walk you through everything you need to know, from the absolute basics to advanced techniques that will make your team—and your future self—thank you.

What Exactly Are Unity’s Property Drawers?

In simple terms, a Property Drawer is a special type of script that tells Unity how to visually represent a specific property in the Inspector. Instead of letting Unity use its default drawer (like a text box for a string or a simple field for a number), you get to define your own custom GUI.

The key thing to understand about Property Drawers is their scope: they customize the appearance of a single property, not the entire component. This makes them incredibly modular and reusable.

You can trigger a Property Drawer in two main ways:

  1. By creating a custom Attribute: You can create an attribute like [ReadOnly] or [MinMaxSlider] and then write a Property Drawer that targets any field marked with that attribute.
  2. By targeting a custom data type: If you have a custom serializable class, like a CharacterStats struct, you can write a Property Drawer that defines how that entire class is displayed every time it appears in the Inspector.

Crucially, all Property Drawer scripts must be placed inside a special folder named Editor. This tells Unity that the code is for the editor only and should not be included in your final game build.

Why Bother with Property Drawers? The Big Benefits

Investing a little time in creating Property Drawers can pay off massively in the long run. Here’s why they are an essential tool for any serious Unity developer:

  • Improved Workflow: A well-designed Inspector is simply faster to use. Imagine replacing two separate min and max float fields with a single, intuitive range slider. This saves time and mental energy for everyone on your team, especially designers and artists.
  • Data Validation and Clarity: You can use drawers to provide immediate visual feedback. For example, a property could turn red if its value is invalid, or you could display a warning message directly in the Inspector if a field is left empty.
  • Richer Visual Representation: Data isn’t always best represented by a number. A Property Drawer can turn a simple enum representing an enemy’s state (Patrolling, Chasing, Attacking) into a color-coded label, making it instantly recognizable at a glance.
  • Reduced Human Error: By constraining input with sliders, hiding fields that shouldn’t be edited, or providing helpful tooltips, you can design an Inspector that guides users toward correct usage and prevents common mistakes.
  • Incredible Reusability: This is their superpower. Once you write a drawer for a [ReadOnly] attribute, you can use that attribute on any variable in any script across your entire project. This modularity is far more efficient than rewriting the same custom logic in every component editor.
Image

Property Drawers vs. Custom Editors: What’s the Difference?

This is a common point of confusion for developers learning to customize the editor. While both tools change the Inspector, they operate at different levels. Understanding the difference is key to choosing the right tool for the job.

FeatureProperty DrawerCustom Editor
ScopeTargets a single property (field).Targets an entire component (MonoBehaviour or ScriptableObject).
TriggerPropertyAttribute or a specific serializable class/struct type.The component type itself (e.g., PlayerController).
Use CaseCustomizing how a type of data (e.g., a MinMaxRange) appears everywhere.Creating a completely unique layout for a specific component.
ReusabilityHigh. A drawer for an attribute can be used in hundreds of different components.Low. An editor for PlayerController only works for PlayerController components.
ComplexityGenerally simpler and more focused.Can be much more complex, but offers total control over the component’s GUI.

Rule of thumb: If you want to change how a piece of data looks everywhere, use a Property Drawer. If you want to change the entire layout for one specific script, use a Custom Editor.

Getting Started: Your First Property Drawer (The Attribute Way)

Let’s dive in and build our first Property Drawer. We’ll create a simple but useful [ReadOnly] attribute that makes a field visible in the Inspector but not editable.

Step 1: Create the Attribute

First, create a new C# script. It doesn’t need to be in an Editor folder. This script will define the attribute itself.

ReadOnlyAttribute.cs

using UnityEngine;

/// <summary>
/// A property attribute to make a field read-only in the Inspector.
/// </summary>
public class ReadOnlyAttribute : PropertyAttribute
{
    // This attribute is just a marker, so it doesn't need any properties.
}

This class inherits from PropertyAttribute, which is what allows it to be used on script fields like [ReadOnly].

Step 2: Create the Editor Folder

In your Project window, right-click in the Assets folder and go to Create > Folder. Name this new folder Editor. This exact name is important. Unity knows not to include any scripts inside this folder in your game’s final build.

Step 3: Write the Drawer Script

Inside the Editor folder, create another C# script. This will contain the logic for our drawer.

ReadOnlyDrawer.cs

using UnityEngine;
using UnityEditor;

// This tells Unity that this drawer is for fields with the [ReadOnly] attribute.
[CustomPropertyDrawer(typeof(ReadOnlyAttribute))]
public class ReadOnlyDrawer : PropertyDrawer
{
    // This is the magic method where we customize the GUI.
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // We start by disabling the GUI. This makes everything that follows non-interactive.
        GUI.enabled = false;

        // Now we draw the default property field. Because the GUI is disabled, it will be grayed out.
        EditorGUI.PropertyField(position, property, label, true);

        // It's crucial to re-enable the GUI, otherwise everything else in the Inspector will also be disabled.
        GUI.enabled = true;
    }
}

Let’s break this down:

  • [CustomPropertyDrawer(typeof(ReadOnlyAttribute))]: This is the link. It tells Unity to use this ReadOnlyDrawer class for any property that has our ReadOnlyAttribute.
  • OnGUI(Rect position, SerializedProperty property, GUIContent label): This is the core method. Unity calls it whenever it needs to draw the property.
    • position: A rectangle defining the space on the screen where you should draw your GUI.
    • property: A special SerializedProperty object that represents the field you’re drawing. It’s a wrapper that handles things like Undo/Redo and prefab modifications automatically.
    • label: The name and tooltip of the property (e.g., “Player Health”).
  • GUI.enabled = false;: This is the key to our drawer. It temporarily disables user interaction for any GUI elements drawn after this line.
  • EditorGUI.PropertyField(...): This handy method draws the property using Unity’s default logic. Since we disabled the GUI, it will appear grayed out.
  • GUI.enabled = true;: We always clean up after ourselves! This re-enables the GUI so that other properties in the Inspector draw correctly.

Step 4: Use It in a Script

Now for the fun part! Go to any of your MonoBehaviour scripts and add the attribute to a field.

Player.cs

using UnityEngine;

public class Player : MonoBehaviour
{
    public float health = 100f;
    public float maxSpeed = 10f;

    [Tooltip("This ID is assigned at runtime and cannot be changed in the Inspector.")]
    [ReadOnly]
    public string playerID;

    void Start()
    {
        // Imagine we generate a unique ID here
        playerID = "Player_" + System.Guid.NewGuid().ToString();
    }
}

Attach this Player script to a GameObject. When you select it, you’ll see the Player ID field is now grayed out and un-editable, just as we wanted!

Image

Leveling Up: Creating a Drawer for a Custom Class

Attributes are great, but the other major use for Property Drawers is to define the GUI for a reusable, serializable class. Let’s create a common structure: a MinMaxRange that holds a minimum and a maximum value, and then create a drawer to show it on a single, neat line.

Step 1: Create the Serializable Class

First, create the data class. This can go anywhere in your Assets folder (but not in the Editor folder).

MinMaxRange.cs

using UnityEngine;

// [System.Serializable] is essential! It tells Unity to save this class's data.
[System.Serializable]
public class MinMaxRange
{
    public float min;
    public float max;
}

Without [System.Serializable], Unity wouldn’t know how to save the min and max values, and it wouldn’t show up in the Inspector at all.

Step 2: Write the Drawer Script

Now, head back to your Editor folder and create the drawer script.

MinMaxRangeDrawer.cs

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(MinMaxRange))]
public class MinMaxRangeDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // Start the property drawing process.
        EditorGUI.BeginProperty(position, label, property);

        // Draw the main label for our MinMaxRange.
        position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);

        // Don't make child fields be indented
        var indent = EditorGUI.indentLevel;
        EditorGUI.indentLevel = 0;

        // Calculate rects for the two float fields
        var minLabelRect = new Rect(position.x, position.y, 30, position.height);
        var minRect = new Rect(position.x + 35, position.y, 50, position.height);
        var maxLabelRect = new Rect(position.x + 90, position.y, 30, position.height);
        var maxRect = new Rect(position.x + 125, position.y, 50, position.height);
        
        // Find the 'min' and 'max' properties within our MinMaxRange class.
        SerializedProperty minProp = property.FindPropertyRelative("min");
        SerializedProperty maxProp = property.FindPropertyRelative("max");

        // Draw the labels and fields.
        EditorGUI.LabelField(minLabelRect, "Min");
        EditorGUI.PropertyField(minRect, minProp, GUIContent.none);
        EditorGUI.LabelField(maxLabelRect, "Max");
        EditorGUI.PropertyField(maxRect, maxProp, GUIContent.none);

        // Set indent back to what it was
        EditorGUI.indentLevel = indent;

        EditorGUI.EndProperty();
    }
}

This is more complex, so let’s examine the new concepts:

  • [CustomPropertyDrawer(typeof(MinMaxRange))]: This time, we target the class type directly.
  • EditorGUI.BeginProperty / EndProperty: These are important bookends. They ensure that things like prefab overrides and right-click context menus work correctly on your custom GUI.
  • EditorGUI.PrefixLabel: This draws the main label (e.g., “Spawn Time Range”) and gives us the remaining rectangle to work with.
  • property.FindPropertyRelative("min"): This is how you access the child properties of your custom class. You pass the exact variable name as a string.
  • Manual Rect Calculation: We manually define the positions and sizes (Rect) for our labels and float fields to place them side-by-side on one line. GUIContent.none is used in the PropertyField calls to prevent Unity from drawing the default labels for “min” and “max”.

Step 3: Use it in a Script

Using our new class is simple.

EnemySpawner.cs

using UnityEngine;

public class EnemySpawner : MonoBehaviour
{
    public GameObject enemyPrefab;
    public MinMaxRange spawnTimeRange;
    public MinMaxRange spawnAmountRange;
}

Now, in the Inspector, instead of seeing an expandable foldout for each MinMaxRange with indented “Min” and “Max” fields, you’ll see a perfectly compact, single-line editor for each. It’s cleaner, saves space, and is much easier to read.

Advanced Techniques and Best Practices

Once you’ve mastered the basics, you can start adding more advanced features to your drawers.

Overriding GetPropertyHeight

So far, our drawers have only taken up a single line. But what if you want to create a taller control, like a help box or a multi-line editor? If you just draw more stuff in OnGUI, it will overlap with the properties below it.

To fix this, you must override the GetPropertyHeight method. Unity calls this method before OnGUI to figure out how much vertical space to reserve.

Example: A [Note] attribute that displays a help box.

// In NoteDrawer.cs (inside the Editor folder)
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
    // We want the height of the default property field, PLUS the height for our help box.
    // Let's say our help box will be 2 lines tall.
    float defaultHeight = base.GetPropertyHeight(property, label);
    float helpBoxHeight = EditorGUIUtility.singleLineHeight * 2 + EditorGUIUtility.standardVerticalSpacing;
    return defaultHeight + helpBoxHeight;
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
    // The 'position' Rect Unity gives us is now the full height we requested.
    // We need to slice it up for our different controls.

    // Define rect for the property field
    Rect propertyRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
    
    // Define rect for the help box below it
    Rect helpBoxRect = new Rect(position.x, position.y + propertyRect.height + EditorGUIUtility.standardVerticalSpacing, position.width, EditorGUIUtility.singleLineHeight * 2);

    // Draw the actual controls in their respective rects
    EditorGUI.PropertyField(propertyRect, property, label);
    EditorGUI.HelpBox(helpBoxRect, "This is an important note!", MessageType.Info);
}

Working with SerializedProperty

It’s tempting to try and get the raw object and modify its fields directly. Don’t do it! Always work with the SerializedProperty object Unity provides. This is what ensures Undo/Redo, multi-object editing, and prefab modifications all work flawlessly.

SerializedProperty has accessors for all common types:

  • property.floatValue
  • property.intValue
  • property.boolValue
  • property.stringValue
  • property.objectReferenceValue (for components, GameObjects, assets, etc.)
  • And more!

Always use these to read and write values within your drawer.

Using EditorGUIUtility

Avoid using “magic numbers” (like 20f for height) in your layout calculations. The EditorGUIUtility class provides static values that match Unity’s current theme and DPI settings, ensuring your GUI looks native and consistent.

  • EditorGUIUtility.singleLineHeight: The standard height of a single line in the Inspector.
  • EditorGUIUtility.standardVerticalSpacing: The standard gap between vertical controls.

[YOUTUBE_VIDEO: Unity custom inspector tutorial]

Another Powerful Example: A Custom Enum Drawer

Let’s build a drawer that adds instant visual clarity. We’ll create a [ColoredEnum] attribute that changes the background color of an enum field based on its value. This is perfect for status fields.

Step 1: The Enum and Attribute

First, define a status enum and a simple marker attribute.

Task.cs

public enum TaskStatus { Todo, InProgress, Done, Blocked }

// A simple marker attribute for our drawer
public class ColoredEnumAttribute : UnityEngine.PropertyAttribute { }

Step 2: The Drawer

In your Editor folder, create the drawer.

ColoredEnumDrawer.cs

using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(ColoredEnumAttribute))]
public class ColoredEnumDrawer : PropertyDrawer
{
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        // First, check if the property is actually an enum. If not, draw an error.
        if (!property.isEnum)
        {
            EditorGUI.LabelField(position, label.text, "Error: [ColoredEnum] must be used on an enum.");
            return;
        }

        // Store the original GUI background color
        Color originalColor = GUI.backgroundColor;

        // Get the current enum value as an index
        TaskStatus status = (TaskStatus)property.enumValueIndex;

        // Determine the color based on the enum value
        switch (status)
        {
            case TaskStatus.Todo:
                GUI.backgroundColor = Color.gray;
                break;
            case TaskStatus.InProgress:
                GUI.backgroundColor = new Color(1.0f, 0.9f, 0.4f); // A nice yellow
                break;
            case TaskStatus.Done:
                GUI.backgroundColor = Color.green;
                break;
            case TaskStatus.Blocked:
                GUI.backgroundColor = Color.red;
                break;
        }

        // Draw the enum dropdown field. It will now have our custom background color.
        EditorGUI.PropertyField(position, property, label);

        // IMPORTANT: Restore the original background color.
        GUI.backgroundColor = originalColor;
    }
}

Step 3: Usage

Now, just add the attribute to any TaskStatus field.

Quest.cs

using UnityEngine;

public class Quest : MonoBehaviour
{
    public string questName;

    [ColoredEnum]
    public TaskStatus status;
}

In the Inspector, the Status dropdown will now be colored, giving you an immediate visual cue about the state of your quest.

Image

Common Pitfalls and How to Avoid Them

As you create more complex drawers, you might run into a few common issues.

  1. Forgetting the Editor Folder: If you get compiler errors saying it can’t find UnityEditor classes, it’s almost certainly because your drawer script is not in an Editor folder.
  2. Forgetting [System.Serializable]: If your custom class doesn’t show up in the Inspector or its values don’t save, you likely forgot to add the [System.Serializable] attribute to the class definition.
  3. Modifying Raw Values Directly: As mentioned before, avoid accessing the raw object. Always use the SerializedProperty methods to ensure editor features like Undo work correctly.
  4. Ignoring GetPropertyHeight: If your multi-line drawer is overlapping with other fields, you need to implement GetPropertyHeight to reserve the correct amount of space.
  5. Hardcoding UI Dimensions: Don’t use magic numbers for heights and spacing. Use EditorGUIUtility.singleLineHeight and EditorGUIUtility.standardVerticalSpacing to make your GUI adapt to different editor settings.

The Future: UI Toolkit and Property Drawers

It’s worth noting that the OnGUI system, often called IMGUI (Immediate Mode GUI), is the classic way of building editor tools in Unity. The modern approach is UI Toolkit, which uses a structure more like web development with UXML (for layout) and USS (for styling).

You can build Property Drawers using UI Toolkit by overriding the CreatePropertyGUI method instead of OnGUI. This offers more power, flexibility, and a better separation of structure and style, especially for very complex UIs.

However, for the vast majority of simple-to-medium complexity drawers (like everything we’ve built here), IMGUI is still faster to write, easier to understand, and perfectly effective. It is by no means obsolete and remains an essential skill for any Unity developer.

Conclusion

Unity’s Property Drawers are a gateway to a more efficient, intuitive, and error-proof development process. By moving beyond the default Inspector, you can craft a user experience that is perfectly tuned to the unique needs of your project.

You’ve learned that Property Drawers target single fields, making them modular and reusable, whereas Custom Editors overhaul entire components. You’ve seen how to build them for both attributes and custom classes, and you’ve explored advanced techniques for layout and data handling.

Start small. Find a repetitive or confusing part of your Inspector and think about how a custom drawer could improve it. The [ReadOnly] attribute is a fantastic first project. Before you know it, you’ll be building a library of custom drawers that not only speeds up your own workflow but empowers your entire team to build better games, faster.

Leave a Reply

Shopping cart

0
image/svg+xml

No products in the cart.

Continue Shopping