Creating Editor Windows in Unity
For the last decade, Unity has been a great platform for developing games that provides a large number of tools to developers: rendering engine, physics engine, animation systems, audio mixers etc. However, when creating levels or generating in-game data, Unity falls short, because each game is unique and requires different kinds of tools. Thankfully, Unity has an API available to us developers to create our own editor windows, custom drawers and inspector panels. In this series of blog posts, I am going to show you how I develop custom editors in Unity for designers and artists. We will start by creating an editor window, put two panels in it, and then make it resizable. In the next post, we will turn that window into a clone of Unity’s own Console window.
Creating an Editor Window
Well then, let’s start! First, we are going to create an empty Unity project and add a folder named Editor under Assets. If you give folders in Unity certain names, it will treat their contents in a special way. Editor is one of those names and the scripts in this folder will become a part of Unity’s own editor. So, then, let’s build an editor window script: go into the Editor folder you just created, right-click, and select Create -> C# Script. You can also select Editor Test C# Script, but we will change all the content anyway, so it doesn’t actually matter. Name your script ResizablePanels
and then open it in your favourite text editor (I prefer Visual Studio on Windows and Rider on Mac).
Since this is going to be an editor window, the class should derive from EditorWindow
instead of MonoBehaviour
. The EditorWindow
class resides in UnityEditor
namespace, so we will need to add it as well.
using UnityEngine;
using UnityEditor;
public class ResizablePanels : EditorWindow
{
}
Editor windows require a static method to be initialized. In this method, we are going build the window and (optional) give it a title. GetWindow()
is an EditorWindow
method which creates the window, if it doesn’t exist, or finds and focuses on it if it does.
However, a static method on its own will not be enough, we also need to add a button, or something like it, to let our users open the window within Unity. Thankfully there is already an attribute called MenuItem
which adds a menu item to Unity’s menu bar and runs the method it is applied to. So, the following code will create the most basic editor window you can create in Unity.
using UnityEngine;
using UnityEditor;
public class ResizablePanels : EditorWindow
{
[MenuItem("Window/Resizable Panels")]
private static void OpenWindow()
{
ResizablePanels window = GetWindow<ResizablePanels>();
window.titleContent = new GUIContent("Resizable Panels");
}
}
Here is the menu item:
And here is our window that is opened when you click it:
In order to draw in this window, we will use the OnGUI()
method (yes, Unity’s own editor system still uses the old GUI system and it probably won’t change for a long time). But first, we need two rectangles to define our panels. I will also draw these panels in their own methods, so while we are at it, we should add those methods, too.
using UnityEngine;
using UnityEditor;
public class ResizablePanels : EditorWindow
{
private Rect upperPanel;
private Rect lowerPanel;
[MenuItem("Window/Resizable Panels")]
private static void OpenWindow()
{
ResizablePanels window = GetWindow<ResizablePanels>();
window.titleContent = new GUIContent("Resizable Panels");
}
private void OnGUI()
{
DrawUpperPanel();
DrawLowerPanel();
}
private void DrawUpperPanel()
{
}
private void DrawLowerPanel()
{
}
}
Our editor window is starting to shape up. All we need to do now is to draw the panels and check if they work correctly. GUILayout.BeginArea(Rect rect)
would create a rectangle area to draw in and GUILayout.EndArea()
marks the end. These areas will define our panels. I am also going to add a label in both areas so that we can see how they look. Let’s fill in DrawUpperPanel()
and DrawLowerPanel()
:
using UnityEngine;
using UnityEditor;
public class ResizablePanels : EditorWindow
{
private Rect upperPanel;
private Rect lowerPanel;
[MenuItem("Window/Resizable Panels")]
private static void OpenWindow()
{
ResizablePanels window = GetWindow<ResizablePanels>();
window.titleContent = new GUIContent("Resizable Panels");
}
private void OnGUI()
{
DrawUpperPanel();
DrawLowerPanel();
}
private void DrawUpperPanel()
{
upperPanel = new Rect(0, 0, position.width, position.height * 0.5f);
GUILayout.BeginArea(upperPanel);
GUILayout.Label("Upper Panel");
GUILayout.EndArea();
}
private void DrawLowerPanel()
{
lowerPanel = new Rect(0, position.height * 0.5f, position.width, position.height * 0.5f);
GUILayout.BeginArea(lowerPanel);
GUILayout.Label("Lower Panel");
GUILayout.EndArea();
}
}
Well, they seem to be working:
However, those two panels are set to cover half of the window each, but if we want them to be resizable, they need to have variable heights. So, I am going to add a size ratio variable; this way, when one panel covers a certain amount of the window, the other can cover the remaining part.
using UnityEngine;
using UnityEditor;
public class ResizablePanels : EditorWindow
{
private Rect upperPanel;
private Rect lowerPanel;
private float sizeRatio = 0.5f;
[MenuItem("Window/Resizable Panels")]
private static void OpenWindow()
{
ResizablePanels window = GetWindow<ResizablePanels>();
window.titleContent = new GUIContent("Resizable Panels");
}
private void OnGUI()
{
DrawUpperPanel();
DrawLowerPanel();
}
private void DrawUpperPanel()
{
upperPanel = new Rect(0, 0, position.width, position.height * sizeRatio);
GUILayout.BeginArea(upperPanel);
GUILayout.Label("Upper Panel");
GUILayout.EndArea();
}
private void DrawLowerPanel()
{
lowerPanel = new Rect(0, (position.height * sizeRatio), position.width, position.height * (1 - sizeRatio));
GUILayout.BeginArea(lowerPanel);
GUILayout.Label("Lower Panel");
GUILayout.EndArea();
}
}
Resizing Panels
OK, both panels have variable heights… but we still can’t resize them! But don’t worry, we are not too far from the final product. Now we just need another rectangle area, so that when the user clicks down there we can start resizing. I am going to add that area just between the two panels. It is going to be 10 pixels tall, because, well, why not? I like 10.
We also need to show the user that the mouse pointer is in the resizing area. EditorGUIUtility.AddCursorRect(Rect rect, MouseCursor cursor)
does that, so we are going to use it in our drawing method.
There is one more thing we need to show the user: where the resizing area begins. This is going to look like magic, but I assure you, it is not: we will use one of Unity’s own icons and stretch it and place it so that it looks like a line between upper and lower panels.
using UnityEngine;
using UnityEditor;
public class ResizablePanels : EditorWindow
{
private Rect upperPanel;
private Rect lowerPanel;
private Rect resizer;
private float sizeRatio = 0.5f;
private bool isResizing;
private GUIStyle resizerStyle;
[MenuItem("Window/Resizable Panels")]
private static void OpenWindow()
{
ResizablePanels window = GetWindow<ResizablePanels>();
window.titleContent = new GUIContent("Resizable Panels");
}
private void OnEnable()
{
resizerStyle = new GUIStyle();
resizerStyle.normal.background = EditorGUIUtility.Load("icons/d_AvatarBlendBackground.png") as Texture2D;
}
private void OnGUI()
{
DrawUpperPanel();
DrawLowerPanel();
DrawResizer();
}
private void DrawUpperPanel()
{
upperPanel = new Rect(0, 0, position.width, position.height * sizeRatio);
GUILayout.BeginArea(upperPanel);
GUILayout.Label("Upper Panel");
GUILayout.EndArea();
}
private void DrawLowerPanel()
{
lowerPanel = new Rect(0, (position.height * sizeRatio) + 5, position.width, position.height * (1 - sizeRatio) - 5);
GUILayout.BeginArea(lowerPanel);
GUILayout.Label("Lower Panel");
GUILayout.EndArea();
}
private void DrawResizer()
{
resizer = new Rect(0, (position.height * sizeRatio) - 5f, position.width, 10f);
GUILayout.BeginArea(new Rect(resizer.position + (Vector2.up * 5f), new Vector2(position.width, 2)), resizerStyle);
GUILayout.EndArea();
EditorGUIUtility.AddCursorRect(resizer, MouseCursor.ResizeVertical);
}
}
Almost finished:
And now, we will finalize it by adding the actual interaction. We will process the incoming events and if the event is a mouse down
event and it is in the resizing area, we will start resizing.
using UnityEngine;
using UnityEditor;
public class ResizablePanels : EditorWindow
{
private Rect upperPanel;
private Rect lowerPanel;
private Rect resizer;
private float sizeRatio = 0.5f;
private bool isResizing;
private GUIStyle resizerStyle;
[MenuItem("Window/Resizable Panels")]
private static void OpenWindow()
{
ResizablePanels window = GetWindow<ResizablePanels>();
window.titleContent = new GUIContent("Resizable Panels");
}
private void OnEnable()
{
resizerStyle = new GUIStyle();
resizerStyle.normal.background = EditorGUIUtility.Load("icons/d_AvatarBlendBackground.png") as Texture2D;
}
private void OnGUI()
{
DrawUpperPanel();
DrawLowerPanel();
DrawResizer();
ProcessEvents(Event.current);
if (GUI.changed) Repaint();
}
private void DrawUpperPanel()
{
upperPanel = new Rect(0, 0, position.width, position.height * sizeRatio);
GUILayout.BeginArea(upperPanel);
GUILayout.Label("Upper Panel");
GUILayout.EndArea();
}
private void DrawLowerPanel()
{
lowerPanel = new Rect(0, (position.height * sizeRatio) + 5, position.width, position.height * (1 - sizeRatio) - 5);
GUILayout.BeginArea(lowerPanel);
GUILayout.Label("Lower Panel");
GUILayout.EndArea();
}
private void DrawResizer()
{
resizer = new Rect(0, (position.height * sizeRatio) - 5f, position.width, 10f);
GUILayout.BeginArea(new Rect(resizer.position + (Vector2.up * 5f), new Vector2(position.width, 2)), resizerStyle);
GUILayout.EndArea();
EditorGUIUtility.AddCursorRect(resizer, MouseCursor.ResizeVertical);
}
private void ProcessEvents(Event e)
{
switch (e.type)
{
case EventType.MouseDown:
if (e.button == 0 && resizer.Contains(e.mousePosition))
{
isResizing = true;
}
break;
case EventType.MouseUp:
isResizing = false;
break;
}
Resize(e);
}
private void Resize(Event e)
{
if (isResizing)
{
sizeRatio = e.mousePosition.y / position.height;
Repaint();
}
}
}
And here is the finished version:
As you can see, creating an editor window in Unity with resizable panels is fairly easy. In the next blog post we will build on this editor window to create a clone of Unity’s console window.
You can find the full source code here.