some wise words
Want to be notified of new blog posts?


Elwin Verploegen

Tutorial: Extending The Unity3D EditorDecember 22, 2014

By Elwin

Extending the Unity3D editor is one of the best features of the engine. If you want to see how this is useful in an actual project, you can read our blog post on our Dialogue Editor or Interaction Tool. In this tutorial I will teach you the basics on how to create your own editor scripts, which will hopefully get you going in creating amazing tools for your project. I’ll be doing this in Unity 5.0.0b17, but anything here should work just fine in Unity 4.6 (and probably below that as well). Whenever I add or change code to a script I will highlight it in a dark green.

I’ll be going through all the steps with you to get your first editor script going, this will include creating the window and modifying variables of a script on an object in the scene. If anything is unclear please send me a tweet and I’ll try to help you out.

Getting started

Start by creating a new project and adding a folder named Editor in the Assets directory, this is where we’ll be working from most of the time. Let’s create a file named ObjectSetter in the Editor folder, open that and drop in the following code:

using UnityEditor;
using System.Collections;

public class ObjectSetter : EditorWindow {

}

The above code is the start of any editor window script. A difference with a regular script is that instead of using UnityEngine; we are using UnityEditor;. This allows us to use the UnityEditor namespace. Another point to remember is that instead of extending MonoBehaviour (like a regular script would do), we’re extending EditorWindow. Before we continue make sure that your Project panel looks like this:

Unity3D Project Panel

Current project panel.

 Adding a menu button

Next up is adding a button in the toolbar at the top of the window. We’ll be adding a button under the Tools button, so that you’ll have to click on Tools first, followed by ObjectSetter. This is how that will look:

Toolbar

Toolbar

using UnityEditor;
using System.Collections;

public class ObjectSetter : EditorWindow {

    [MenuItem ("Tools/ObjectSetter")] //Add a menu item to the toolbar
    static void OpenWindow(){
    }
}

The first line of code that was added creates a new menu item in the tool bar, which is fairly dynamic. You can add pretty much type any path there and Unity will generate the button for you. You can even add a shortcut for the button. You can read how to do that in the Unity documentation over here.

Create a custom window

Now that we have a button in the toolbar, we can create a window and add a button in it. Let’s start with creating a window.

using UnityEditor;
using System.Collections;

public class ObjectSetter : EditorWindow {

    public static ObjectSetter window;

    [MenuItem ("Tools/ObjectSetter")]
    public static void OpenWindow(){
        window = (ObjectSetter)EditorWindow.GetWindow(typeof(ObjectSetter)); //create a window
        window.title = "Object Setter"; //set a window title
    }
}

First we create a new variable called window, this will (as the name suggests) hold the window. In the OpenWindow function we added 2 lines of the code. The first line calls EditorWindow.GetWindow(), this function returns the first EditorWindow of type ObjectSetter. If it doesn’t exist it will create one and return that instance. We then save that in our window variable. Try changing the title of the window to something else and see what happens.

If you just changed the code and nothing happened, you are absolutely correct. The OpenWindow function is only called when you press the button in the toolbar. This means that whenever you make a change you will have to press the button to make sure you can see the change. This is obviously very annoying when trying to debug, so let’s start with adding some code to fix that.

using UnityEditor;
using System.Collections;

public class ObjectSetter : EditorWindow {

    public static ObjectSetter window;

    [MenuItem ("Tools/ObjectSetter")]
    public static void OpenWindow(){
        window = (ObjectSetter)EditorWindow.GetWindow(typeof(ObjectSetter)); //create a window
        window.title = "Object Setter"; //set a window title
    }

    void OnGUI(){
        if(window == null)
            OpenWindow();
    }
}

Here we added the OnGUI function (you’re probably familiar with this function, as it can also be used in regular scripts). We’ll add a little trick where we check if the window variable is set to null, if it’s null we call OpenWindow again, setting the default variables again. Every time Unity compiles script, it will reset static variables. Whenever you change some code all you have to do is click on the editor window to focus it to reload the code.

Now that we have the OnGUI function, we can add a button to it. To create a button we have to use the normal GUI.Button function. To do so we have to include the UnityEngine namespace. You can use all of the regular GUI functions, but you also have access to the EditorGUI functions, which we’ll use a bit later. This is how it will look for now:

Editor Window button

Editor Window button

using UnityEditor;
using UnityEngine;
using System.Collections;

public class ObjectSetter : EditorWindow {

    public static ObjectSetter window;

    [MenuItem ("Tools/ObjectSetter")]
    public static void OpenWindow(){
        window = (ObjectSetter)EditorWindow.GetWindow(typeof(ObjectSetter)); //create a window
        window.title = "Object Setter"; //set a window title
    }

    void OnGUI(){
        if(window == null)
            OpenWindow();

        if(GUI.Button(new Rect(0, 0, position.width, position.height), "Press me")){
            Debug.Log("hodor");
        }
    }
}

With those lines added you should now see a button that fills up the window, and when you press it you should see hodor printed in your console. To get the size of the window, we use position.width & position.height, these are both properties of EditorWindow.position.

Create placeholder scripts & objects

Before we continue with actually modifying variables of an object through our script, let’s start by adding a cube to the scene and applying a script to it named DataHolder, this is just a script where we can modify some things to show that it all works. DataHolder looks like this:

using UnityEngine;
using System.Collections;

public class DataHolder : MonoBehaviour {

    public GameObject cam;
    public int health;
    public string username;

}

This is now how my scene hierarchy & project panel looks. Don’t forget to add the DataHolder script to the Cube!

Hierarchy & Project Panels

Hierarchy & Project Panels

Modifying the DataHolder variables

Here is where it gets a bit more complex, as we need to write a bit more code to get things working. Let’s start by grabbing the currently selected object so that we can manipulate it.

using UnityEditor;
using UnityEngine;
using System.Collections;

public class ObjectSetter : EditorWindow {

    public static ObjectSetter window;
    private static GameObject obj;

    [MenuItem ("Tools/ObjectSetter")]
    public static void OpenWindow(){
        window = (ObjectSetter)EditorWindow.GetWindow(typeof(ObjectSetter)); //create a window
        window.title = "Object Setter"; //set a window title
}

    void OnGUI(){
        if(window == null)
            OpenWindow();

        if(Selection.activeGameObject != null){
            //gets the object you currently have selected in the scene view
            obj = Selection.activeGameObject;
            GUI.Label(new Rect(0, 0, position.width, 25), "Current selected object: " + obj.name);
        }
    }

    void Update(){
        Repaint();
    }
}

I took out the button, because we won’t be needing that anymore. At the top I’ve added a variable called obj, I like to have this for easy access throughout this script (I usually have to refer to this quite often, so doing less typing is favourable to me). In the OnGUI function we then make use of the very handy Selection.activeGameObject. This variable holds the GameObject that you have currently selected in the scene.

Next, we check if you actually have something selected and then set obj to that object. After that we simply print the name of that object in the window using the regular GUI.Label.

We also added an Update function, this functions the same as in a regular script and will be called every frame. The Repaint function in there makes sure that our custom window is redrawn every frame. Click on different objects in the scene and watch the text in the window change. If you try commenting out the repaint function you will notice that the text only changes when you focus back on the window.

Now that we have the object we can move on and start modifying the variables in DataHolder. This is how it should look after applying the modifications below:

Editor Window with variable fields

Editor Window with variable fields

using UnityEditor;
using UnityEngine;
using System.Collections;

public class ObjectSetter : EditorWindow {

    public static ObjectSetter window;
    private static GameObject obj;

    [MenuItem ("Tools/ObjectSetter")]
    public static void OpenWindow(){
        window = (ObjectSetter)EditorWindow.GetWindow(typeof(ObjectSetter)); //create a window
        window.title = "Object Setter"; //set a window title
    }

    void OnGUI(){
        if(window == null)
            OpenWindow();

        if(Selection.activeGameObject != null){
            //gets the object you currently have selected in the scene view
            obj = Selection.activeGameObject;
            GUI.Label(new Rect(5, 0, position.width-10, 25), "Current selected object: " + obj.name);

            //make sure to only show the interface
            DataHolder comp = obj.GetComponent<DataHolder>();
            if(comp != null){
                comp.health = EditorGUI.IntField(
                    new Rect(5, 30, position.width-10, 16), 
                    "Health", 
                    comp.health
                );
                comp.username = EditorGUI.TextField(
                    new Rect(5, 50, position.width-10, 16), 
                    "User Name", 
                    comp.username
                );
                comp.cam = (GameObject) EditorGUI.ObjectField(
                    new Rect(5, 70, position.width-10, 16), 
                    "Camera GameObject", 
                    comp.cam, 
                    typeof(GameObject)
                );
            }
        }
    }

    void Update(){
        Repaint();
    }
}

We first try to get the DataHolder component from the selected object. If it has one, we show 3 EditorGUI fields. These generally have the same layout and will become familiar pretty fast, a list of all possibilities can be found over at the EditorGUI documentation. The 3rd variable field requires most work, but is very useful. You can essentially put any object type in there, this allows you to grab components from GameObjects (e.g. scripts, colliders, renderers, etc.).

To see this working, just select any object with the DataHolder script attached to it. Now change some of the variables or drag in a GameObject into the “Camera GameObject” field. Keep an eye on the inspector and you should see those variables change in real-time. Congratulations, you just created your first Editor script and are now ready to conquer the world! Let’s add some more small features though.

Create DataHolder

When the user selects a cube that doesn’t have a script we don’t want to manually drag in the script on the GameObject. Instead, we’ll add a button to add the component to the GameObject.

using UnityEditor;
using UnityEngine;
using System.Collections;

public class ObjectSetter : EditorWindow {

    public static ObjectSetter window;
    private static GameObject obj;

    [MenuItem ("Tools/ObjectSetter")]
    public static void OpenWindow(){
        window = (ObjectSetter)EditorWindow.GetWindow(typeof(ObjectSetter)); //create a window
        window.title = "Object Setter"; //set a window title
    }

    void OnGUI(){
        if(window == null)
            OpenWindow();

        if(Selection.activeGameObject != null){
            //gets the object you currently have selected in the scene view
            obj = Selection.activeGameObject;
            GUI.Label(new Rect(5, 5, position.width-10, 25), "Current selected object: " + obj.name);

            //make sure to only show the interface
            DataHolder comp = obj.GetComponent<DataHolder>();
            if(comp != null){
                comp.health = EditorGUI.IntField(
                    new Rect(5, 30, position.width-10, 16), 
                    "Health", 
                    comp.health
                );
                comp.username = EditorGUI.TextField(
                    new Rect(5, 50, position.width-10, 16), 
                    "User Name", 
                    comp.username
                );
                comp.cam = (GameObject) EditorGUI.ObjectField(
                    new Rect(5, 70, position.width-10, 16), 
                    "Camera GameObject", 
                    comp.cam, 
                    typeof(GameObject)
                );
            }
            else{
                if(GUI.Button(new Rect(5, 30, position.width-10, position.height-40), 
                "Add DataHolder")){
                    obj.AddComponent<DataHolder>();
                }
            }
        }
    }

    void Update(){
        Repaint();
    }
}

Now try creating a new cube and select that. The window should now show 1 big button that says “Add DataHolder”. Pressing that will add a component to the selected object, namely DataHolder. Once you press the button, it will disappear and the regular interface will show up again, this is because every frame it will check if the current object has the DataHolder component selected.

Conclusion

If you did everything as instructed, you should now have 1 window that looks like the left one when you have a normal GameObject selected. When you select an object with a DataHolder you will see the screen on the right.

The final result

The final result

That’s it for this blog. This knowledge should be enough to create very powerful editor scripts (you can probably recreate 90% of my Interaction Editor with just the above knowledge). Next time I’ll show you some useful functions to make everything a bit prettier and easier to work with from a user perspective.

If you have any questions or remarks, don’t hesitate to get in touch with me via Twitter.

Let’s add some more features in the next part in this tutorial.

Want to be notified of new blog posts?