Use CustomEditor to customize your script inspector

When your scripts are getting bigger, then usually the number of properties is greater too. Unity, by default, is using the most generic way to display these properties because it does not know the context. You can use decorator attributes but that’s not the only way you can deal with it. In fact, Unity gives you complete freedom to choose how you want to display your scripts in the Inspector. All you need to do is to write an editor class with CustomEditor attribute!

Let’s consider this script:

using UnityEngine;

public class Hero : MonoBehaviour {
    public int health = 0;
    public int maxHealth = 100;
    public bool withShield;
    public int shield = 0;
    public int maxShield = 0;
}

The Inspector view of this script will look like this:

There are few things that we can improve here:

  1. Health cannot be under 0 and above Max Health value
  2. Health can be a slider
  3. When With Shield is disabled the Shield property is meaningless

Let’s try to make it more designer-friendly.

Creating an editor script

First thing you need to do, is to create an editor script. Editor scripts are special kinds of scripts that are not included in the build but exist only in the Unity editor.

In order to create an editor script, you have to put it somewhere inside the Editor directory. It does not matter where the Editor directory or where your script are placed, until the script somewhere inside the hierarchy that contains the Editor directory. Here are some examples:

  • Assets/Editor/MyScript.cs
  • Assets/MyGame/Editor/MyScript.cs
  • Assets/MyGame/Editor/SomethingElse/MyScript.cs

For our purpose, I will create a directory tree like this one:

custom editor directory structure

 

And fill HeroEditor.cs with this code:

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Hero))]
public class HeroEditor : Editor {

    private SerializedProperty health;
    private SerializedProperty maxHealth;
    private SerializedProperty withShield;
    private SerializedProperty shield;

    private void OnEnable() {
        health = serializedObject.FindProperty("health");
        maxHealth = serializedObject.FindProperty("maxHealth");
        withShield = serializedObject.FindProperty("withShield");
        shield = serializedObject.FindProperty("shield");
    }

    public override void OnInspectorGUI() {
        serializedObject.UpdateIfDirtyOrScript();

        EditorGUILayout.IntSlider(health, 0, maxHealth.intValue, "Health");

        EditorGUI.indentLevel++;
        EditorGUILayout.PropertyField(maxHealth, new GUIContent("Max Health"));
        EditorGUI.indentLevel--;

        EditorGUILayout.Space();

        EditorGUILayout.PropertyField(withShield, new GUIContent("With Shield"));

        if (withShield.boolValue) {
            EditorGUI.indentLevel++;
            EditorGUILayout.PropertyField(shield, new GUIContent("Shield"));
            EditorGUI.indentLevel--;
        }

        serializedObject.ApplyModifiedProperties();
    }
}

OK that’s a lot. Let’s take it…

Step by step

using UnityEditor;
using UnityEngine;

In addition to declaring that we will be using UnityEngine namespace, we now also use the UnityEditor namespace.

[CustomEditor(typeof(Hero))]
public class HeroEditor : Editor {

We’re creating a HeroEditor class (the class name can be anything else) that is extending the Editor class. This class must be annotated by CustomEditor that needs the type of Hero script to be passed as parameter. In this way Unity knows for what class this custom editor is written for.

    private SerializedProperty health;
    private SerializedProperty maxHealth;
    private SerializedProperty withShield;
    private SerializedProperty shield;

We’re defining 4 SerializedProperty types one for each script property. SerializedProperty is a special type that helps to change component properties handling undo and persistence.

    private void OnEnable() {
        health = serializedObject.FindProperty("health");
        maxHealth = serializedObject.FindProperty("maxHealth");
        withShield = serializedObject.FindProperty("withShield");
        shield = serializedObject.FindProperty("shield");
    }

It is required to connect SerializedProperty fields with actual object properties using serializedObject.FindProperty() method. We’re passing property name as argument.

    public override void OnInspectorGUI() {
        serializedObject.UpdateIfDirtyOrScript();

All the GUI is done in OnInspectorGUI() method. To start working on SerializedProperties properly we need to invoke serializedObject.UpdateIfDirtyOfScript().

EditorGUILayout.IntSlider(health, 0, maxHealth.intValue, "Health");

We decided that we will use a slider to render the health property. We’re using EditorGUILayout functions, because we don’t want to worry about GUI element positions (Layout classes are handling GUI elements positions by themselves).

One of IntSlider() method version requires:

  • SerializedProperty that are connected to int-type property
  • Min value (0)
  • Max value – we’re using here maxHealth.intValue because this is the correct way of referencing int values of other SerializedProperties
  • Label (“Health”)
        EditorGUI.indentLevel++;
        EditorGUILayout.PropertyField(maxHealth, new GUIContent("Max Health"));
        EditorGUI.indentLevel--;

EditorGUI.indentLevel is a property that helps us to add indentation for what we’re rendering. Increasing and decreasing it in this way will make the Max Health property displayed a bit to the right.

This time for rendering property we’re using PropertyField() function. This one requires:

  • SerializedProperty of any type – what will be rendered depends of the property type. This is the most generic method of rendering properties.
  • GUIContent object – Usually you want to use a string label, but it also allows you to use images.
EditorGUILayout.Space();

This function will add a space (horizontal in this case) to your layout. Thanks to it you can make a visual separation between groups of properties.

EditorGUILayout.PropertyField(withShield, new GUIContent("With Shield"));

Here we’re rendering With Shield property. Just like before generic PropertyField is used.

        if (withShield.boolValue) {
            EditorGUI.indentLevel++;
            EditorGUILayout.PropertyField(shield, new GUIContent("Shield"));
            EditorGUI.indentLevel--;
        }

This one is interesting. We’re rendering Shield property only if withShield.boolValue is true. This is because we don’t want the designer to be distracted by a property that makes no sense if our hero has no shield at all.

Again we’re indenting the layout and using generic PropertyField function.

        serializedObject.ApplyModifiedProperties();
    }
}

At the end of OnInspectorGUI() you have to invoke serializedObject.ApplyModifiedProperties() to force Unity to check what has been modified and what should be saved.

The result

Here’s how our script will look like in the Inspector after all these modification.

custom editor off

custom editor on

And just for the comparison the old version:

custom editor default

Files

As usual, you can download the example unitypackage here. Just double-click on it to import its contents into your Unity project. You will find there an example scene and the script from above.

related
IntermediateMultiplayerTutorial
Building a Turn-Based Multiplayer Game with GameSparks and Unity: Part 1
In this tutorial we’ll introduce GameSparks platform and we’ll teach you how to build a...
3
AdvancedTips
How to Implement a Game Cheats Subsystem Within Unity3D
When you're creating a game, you have to play it again and again, repeat some scenarios or...
2
AdvancedGuide
Artificial locomotion in Unity using Machine Learning Part 1
Recently Unity introduced a plugin called ML-Agents, which is meant to integrate Machine Learning...
0
Call The Knights!
We are here for you.
Please contact us with regards to a Unity project below.



The Knights appreciate your decision!
Expect the first news soon!
hire us!