Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Gamasutra: The Art & Business of Making Gamesspacer
View All     RSS
September 16, 2019
arrowPress Releases







If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 

Getting Started Creating Editor Extensions in Unity

by Elmar Talibzade on 04/18/16 11:32:00 am   Featured Blogs

1 comments Share on Twitter    RSS

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.

 

Crossposted on App Goodies

GUI code written will work for both Custom Inspector and Custom Window without any compile errors. So for example, if I’ve shown a code snippet for Custom Inspector, it doesn’t mean it won’t work for the Custom Window. Just note that in some cases it may lead to undesired results.

Making a Custom Inspector

Like an ordinary script, Custom Inspector it can be attached to any entity that is listed in the Hierarchy window. Data collected from that inspector can then be used during the play mode.

Unlike a custom window, custom inspector requires two scripts:

  • IMGUI – renders all custom GUI components (e.g, buttons, drop-down etc.) – it communicates with a MonoBehaviour script, allowing you to read and write variable values from it. IMGUI script cannot be attached as a component
  • MonoBehaviour – a script with all variables and functions, however, when it’s attached to the GameObject, custom IMGUI is being drawn instead of an ordinary inspector.

Now, with a brief explanation behind, let’s get to coding bit!

Setting Up

As mentioned earlier, we must create two scripts. I am going to create a script that allows you to generate your own NPC type, his base health etc. So I am going to name my scripts NpcClassScriptand NpcClassEditor.

Place NpcClassEditor into an “Editor” folder. This will prevent Unity from trying to compile the inspector script itself. Remember to keep NpcClassScript outside of “Editor” folder, otherwise, it won’t work!

Open NpcClassScript and declare few variable types:

public string npcName = "Bob";
public int npcHealth = 10;
public float npcSpeed = 4.5f;
public bool isEnemy = false;
 
public GameObject npcObject;
public AnimationClip defaultAnimation;
public Sprite npcIcon;
 
public class_type_list classType = class_type_list.villager;
 
public enum class_type_list 
{
    warrior,
    archer,
    tank,
    wizard,
    villager
}

As usual, we would declare our variables above Start() and Update(). It’s still a MonoBehaviour script, so if we attach it to any GameObject, we’ll see a default inspector with all the variables listed.

With all necessary variables listed, we’re good to go!

Open NpcClassEditor – this is where IMGUI and Monobehaviour scripts are different.

Before we start, we must include UnityEditor. This will grant us additional functions, allowing us to create custom GUI components such as text fields and buttons.

Remove both Start() and Update() functions, they’re not needed for a Custom Inspector because the script itself does not compile and run in the final build. In other words, NpcClassEditor serves as an alternative view of NpcClassScript.

Next, we must replace MonoBehaviour with Editor . This tells Unity that NpcClassEditor is an Editor script.

To this point, your script should look something like this:

using UnityEngine;
using System.Collections;
using UnityEditor;
 
public class NpcClassEditor : Editor 
{
    
}

Right above the public class, we must add the following line of code:

[CustomEditor(typeof(NpcClassScript))]

This will tell Unity that we’re going to reference our variables from NpcClassScript. Inside of our NpcClassEditor, create a public override void OnInspectorGUI(). This is where all the action will take place.

To make it easier for us to reference variables from NpcClassScript in the future, we must declare the variable of the following type:

using UnityEngine;
using System.Collections;
using UnityEditor;
 
[CustomEditor(typeof(NpcClassScript))]
public class NpcClassEditor : Editor 
{
    public override void OnInspectorGUI() 
    {
        NpcClassScript npcScript = (NpcClassScript)target;    
    }
}

Now if you want to access a variable from NpcClassScript, we must type npcScript beforehand.

Now if you check out the inspector, you’ll notice that it’s blank:

This means that OnInspectorGUI overrides the NpcClassScript’s inspector. Because there OnInspectorGUI has nothing to draw (yet), the inspector window is empty (core variables are overridden by this function)

Unlike ordinary MonoBehaviour scripts, you can’t directly open an editor script to edit the code. This gives your Custom Inspector a nicer and more professional look!

With the core part of NpcClassEditor written, we can start adding GUI components!

Unlike in a Custom Window, we must use EditorGUILayout()  function. While it’s possible to use GUI (where x, y, w, h must be entered manually), it won’t lead to desired results as it will use the x, y values from the whole inspector. As a result, your GUI components can appear at the very beginning of the inspector window, overlapping other components.

Let’s start with few basic components such as int, float, label etc.

Labels

Label is a simple text which is normally used to divide variables to categorize them. To create a label, add the following:

EditorGUILayout.Label("NPC Info");

The result:

Text Fields

Textfields require a bit more work to get to work. I will show you in a moment.

First off, in order to create a text field with a label attached to it on the right side, add this:

EditorGUILayout.TextField("NPC Name", npcScript.npcName);

If you let Unity compile changes, you’ll notice a text field with its own label has been added with “Bob” written inside. However, if you try to type something in, you’ll notice that there are no changes.

This is because npcScript.npcName returns a string and writes it into our text field. And, as a result, it does not accept changes.

To solve this, simply add npcScript.npcName before making a EditorGUILayout.TextField(). Try again, and you’ll now be able to type inside! Like so:

npcScript.npcName = EditorGUILayout.TextField("NPC Name", npcScript.npcName);

Buttons

To add a button simply use GUILayout.Button(). But unlike other GUI functions, we must wrap it inside of if () { }

if (GUILayout.Button("Debug the name")) 
{
    //do stuff
    Debug.Log(npcScript.npcName);
}

When the button is pressed, it’ll return true. In my case, it’ll write a debug NPC’s name.

But what if we want  to change the size of the button? It’s simple! Simply add EditorGUILayout.Width() and/or EditorGUILayout.Height() inside of the Button() after the name.

if (EditorGUILayout.Button("Big Fat Button", EditorGUILayout.Width(150), EditorGUILayout.Height(30))) 
{
    Debug.Log("Big fat button says: " + npcScript.npcName);
}

You can see changes after compilation.

Floats, Ints and Bools

Declaring floats, bools and ints involve the same principle as a text field we made before.

//float field
npcScript.npcSpeed = EditorGUILayout.FloatField("NPC Speed", npcScript.npcSpeed);
 
//int field
npcScript.npcHealth = EditorGUILayout.IntField("NPC Health", npcScript.npcHealth);
 
//boolean field
npcScript.isEnemy = EditorGUILayout.Toggle("Is Enemy", npcScript.isEnemy);

And we get to edit float, int and a boolean values respectively!

Dropdown Boxes

If you wish to have a drop-down field, it can be achieved by:

npcScript.classType = (NpcClassScript.class_type_list)EditorGUILayout.EnumPopup("Class Type", npcScript.classType);

Return back to Unity and you’ll see a difference:

Note that I have defined class_type_list in NpcClassScript. In order to reference it from NpcClassEditor, I simply did NpcClassScript.class_type_list . You can use this method if you want to reference your own class.

Using ObjectFields

FloatField, IntField, Toggle, TextField possibilities are endless! Or not…You might’ve noticed that EditorGUILayout is missing some other variable types such as GameObjectField, AnimationClipField etc. Where the heck did they go?

ObjectField()  to the rescue!

It allows you to draw more GUI types such as GameObject field, Sprite field and more! However, it takes more work to get the job done, but it’s worth it!

Say, we want to add a GameObject field to NpcClassEditor. This can be achieved by doing the following:

npcScript.npcObject = (GameObject)EditorGUILayout.ObjectField("NPC Object", npcScript.npcObject, typeof(GameObject), true);

We tell Unity that the ObjectField we need is a GameObject type by using a typeof()  function. The boolean at the end means that a user is able to drag GameObjects from the scene as well.

With that in mind, we can use the same approach for other variable types.

//animation clip
npcScript.defaultAnimation = (AnimationClip)EditorGUILayout.ObjectField("Default Animation", npcScript.defaultAnimation, typeof(AnimationClip), false);
 
//sprite
npcScript.npcIcon = (Sprite)EditorGUILayout.ObjectField("NPC Icon", npcScript.npcIcon, typeof(Sprite), false);

Compile and see the results for yourself:

There are tons of EditorGUILayout inspector fields that aren’t listed here. I’ve compiled an entire list of such GUI, preview images, description and code snippets which can be found here.

Making a Custom Window

Custom Window doesn’t work in the final build – it explicitly used only with an intention of maximizing user’s productivity. Some examples:

  • Mass place prefabs
  • Visual Scripting
  • Manage NPC AI and spawn them accordingly
  • Creating a “Support” window with articles and tutorials

Custom Window requires only one script: it includes both variables and functions and IMGUI, placed inside of anOnGUI() event (just like pre-4.6 GUI).

Setting Up

In this tutorial, I am going to create a Custom Window that allows a user to mass place prefabs with two options: specific location and at random points with min and max values.

So, first off, create a new C# script called MassPrefabPlacerWindow and put it into an Editor folder. It doesn’t matter whether the folder is inside of another folder or in the “Assets” root – as long as your script is inside of an Editor folder.

Open it, and, just like with a Custom Inspector, add include UnityEditor; at the top of the script. Remove Start()  andUpdate()  functions as Custom Window won’t work at runtime.

Replace MonoBehaviour  with EditorWindow to tell Unity that we’re going to draw some GUI components in it.

Instead of declaring a public override void OnInspectorGUI() , like in Custom Inspector, simply declare void OnGUI()  – this is where our GUI is going to be in.

Also, a note that OnGUI() works every time user interacts with a custom window (e.g clicks it). So if you add aDebug.Log("Hello!")  inside of it, it’ll print “Hello” in the console every time you interact with a custom window.

At this point, your script should look like this:

using UnityEngine;
using UnityEditor;
using System.Collections;
 
public class MassPrefabPlacerWindow  : EditorWindow {
 
    public void OnGUI()
    {
    
    }
}

Naming doesn’t matter, but should be relevant to actions it’s going to do.

Inside of DisplayWindow() put the following code:

MassPrefabPlacerWindow mpwind = EditorWindow.GetWindow(typeof(MassPrefabPlacerWindow), false, "Mass Prefab Placer");

It does what the function says – it “gets” our window and stores it into a variable mpwind. We’re going to use this variable to change few settings of the window. The boolean represents whether we want our editor window to be floating and the string at the end determines the name of our window. Floating window is an ordinary window. If you set it to false, you’ll be able to attach inside of Unity Editor.

Now place this code right before you declare a DisplayWindow() function:

[MenuItem("Window/Mass Prefab Placer")]

This will create a menu item named “Mass Prefab Placer” inside of a “Window” category. When a user clicks in inside of the menu, a function DisplayWindow() will be activated, thus showing our window!

Take a look at the script, if you’re lost or confused.

using UnityEngine;
using UnityEditor;
using System.Collections;
 
public class MassPrefabPlacerWindow  : EditorWindow {
    
    [MenuItem("Window/Mass Prefab Placer")]
    public static void DisplayWindow() 
    {
        MassPrefabPlacerWindow mpwind = EditorWindow.GetWindow(typeof(MassPrefabPlacerWindow), false, "Mass Prefab Placer");
    }
    
    public void OnGUI()
    {
        //gui stuff here
    }
}

Minimum Size

You can set the minimum size of your window by adding the following code right after declaring your mpwind variable:

mpwind.minSize = new Vector2(300, 300);

.minSize takes in a Vector2 where x  is the minimum width, and y  is the minimum height for your window.

Maximum Size

Using the same principle, we can set the maximum size our window can stretch to using this:

mpwind.maxSize= new Vector2(800, 800);

Now, with a window being displayed, we are ready to start creating GUI!

Using GUI

The principle of creating GUI for a custom window is same as for the custom inspector. Instead, we can use GUI functions to let us determine position and size of our GUI components. Like so:

if (GUI.Button(new Rect(10, 10, 150, 30), "Place Prefabs")) 
{
    //places prefabs
}

Keep Editor Data Persistent

You may have noticed that if you change a variable in your custom editor window/inspector, save and then quit, that inspector data hasn’t been saved and will show default variable. Why that?

Because we didn’t make our data Serializable, and we didn’t mark our scenes “Dirty”.

Serialization

Our Inspector doesn’t figure our the values through communicating with our C# API, instead it asks those values to serialize themselves so they can be accessed later again. Apart from saving inspector data, serialization is widely used by Unity to store Prefabs, Instantiate GameObjects, Saving .unity scenes etc.

You can read more on serialization at Unity’s official blog post.

Familiar with Serialization? Now let’s use it to store data!

Assuming I am writing a Custom Inspector, let’s open our NpcClassScript.

Right before we declare our public class NpcClassScript : Editor, add the following:

[System.Serializable]

If your script contains a public class or enum (like me) it needs to have [System.Serializable] tag above as well, otherwise variables declared inside of those classes won’t be saved.

Now we must mark our scenes “dirty” when a value is changed.

SetDirty()

Making scenes “Dirty”, means that you tell Unity that some data has been modified in your Custom Inspector, and therefore it marks the whole scene as dirty. You may have noticed if you didn’t use “Dirty” method, save option wouldn’t be available upon changes values in your window/inspector.

To make the scene dirty, we must use the following function:

EditorSceneManager.MarkSceneDirty(EditorApplication.currentScene);

This will mark a current scene as “dirty” (telling unity that changes were made in this scene). If you want to mark all scenes as dirty, then do this:

EditorSceneManager.MarkAllScenesDirty();

Placing this function into OnInspectorGUI or onGUI() events will cause performance loss, which is not what we want. Both events are activated when the Editor Window is being focused by the user, which turns out very inefficient to use such function when the user is not changing values.

To fix this, we can check if GUI is being modified (e.g when user enters values) and then mark a scene dirty:

if (GUI.changed) 
{
    EditorSceneManager.MarkSceneDirty(EditorApplication.currentScene);    
}

That should do it! With Serialization and SetDirty() in place, your window should be able to save values even after Unity restarts.

Using Property Fields

While ObjectFields cover a large deal of GUI components, what about UnityAction window or Color Gradient picker? You can’t add those!

Property Fields allows you to not only add such GUI components but automatically supports Undo, multi-object editing (if specified), and prefab overriding.

Adding property fields is a bit of a headache, but a pretty straightforward process. For this example, I am going to add property fields to our custom inspector.

I want to show you how to add a Gradient into our custom inspector. First off, inside of NpcClassScript, define a variable:

public Gradient myGradientField;

Note, that our variable must be public, otherwise, Unity will return errors later on.

Next, inside of our NpcClassEditor, we must create a Serialized Property variable

SerializedProperty property_myGradient;

Essentially, the name of the property doesn’t matter and it doesn’t have to be public.

Next, inside of OnEnable(), let’s assign our property to our variable from NpcClassScript:

void OnEnable() 
{
    property_myGradient = serializedObject.FindProperty("myGradientField");
}

With all this code work covered, it’s time to get our Gradient variable on display! To do that, inside of our OnInspectorGUI(), we must add the following:

public override void OnInspectorGUI() 
{
    EditorGUILayout.PropertyField(property_myGradient, new GUIContent("My Gradient Field"));
}

And, with that, we end up with our very own Gradient Field!

The Same thing can be achieved with other variable types such as booleans and floats.

Conclusion

Congratulations! You have successfully created your own editor extension! You can read more about editor extensions and what GUI elements you can draw at official Unity docs page. If you have problems, search the internet. If you can’t find a solution, post a thread on Unity Forums or ask a question at Answers Hub. The community is friendly and responsive!

If I missed a typo or a script error (or just want to say hi), drop me a comment down below!

If you enjoyed my lengthy tutorial, be sure to check out my blog for more of these!


Related Jobs

HB Studios
HB Studios — Lunenburg/Halifax, Nova Scotia, Canada
[09.13.19]

Experienced Software Engineer
AfterThought
AfterThought — Henderson, Nevada, United States
[09.11.19]

Unreal Engine 4 Programmer
Disbelief
Disbelief — Chicago, Illinois, United States
[09.11.19]

Junior Programmer, Chicago
Disbelief
Disbelief — Chicago, Illinois, United States
[09.11.19]

Senior Programmer, Chicago





Loading Comments

loader image