Building Useful Editor Scripts

Generally people are drawn to Unity with the flashy features such as the multiplatform support, and the quick route to prototyping. Once you start using Unity, there are other powerful features that keep you around. My favorite one of these Unity features is Editor scripts. If you place a C# file in a folder named “Editor”, then you can run that code in-editor, and access the UnityEditor namespace. This is also where OnGUI becomes extremely useful. For gameplay purposes, OnGUI is just too slow to use, but for editor scripts it allows you to build useful menus quickly and easily. In this post, I am going to run through some reasons you might end up building an editor window or other editor extension, then I am going to cover some techniques for building these windows, and finally I am going to get into specifics on making the best use of the OnGUI functions. This is the Unity script reference page for editor windows: http://docs.unity3d.com/Documentation/ScriptReference/EditorWindow.html. This is where you are going to want to begin when building useful popup windows to provide yourself and those you work with the tools you will end up building.

The first need I usually encounter on a project for an editor script is mass asset editing. There are going to be times on almost any project you want to perform the same operation on a large number of assets. You might decide that you want to change a naming convention and rename a bunch of files, or you might decide that managing asset import settings on a per file basis is a hassle, and you have an obvious pattern you follow that you can automate.

Another great use that comes up is scripts for debugging and problem solving. I have found that on many projects, shader and material management can get cumbersome quickly. It’s easy to accidently have half of the models in your environments using the base Diffuse shader, and the other half using Mobile/Diffuse. Building an editor window that visits every renderer in the scene to collect information on the assigned material, including shader and main texture, and then presenting this information to the user in a way that they can sort by texture size, shader, shared materials, and materials that look like duplicates of each other will let you keep a much cleaner game environment.

The needs for editor scripts will probably come up naturally, and what you probably want to know is what tools you have available for building them, and how to maximize usability. The first set of tools you are going to want to learn to build effective editor scripts are related to OnGUI. OnGUI is a function like Start and Update that you can put into an editor window, mono behavior, and other Unity scripts.

There are a few things to be aware of about OnGUI to save yourself some problems later. When a repaint is necessary, OnGUI can be called multiple times within a single frame. Chances are you are also going to be looping through lists and other data containers, and presenting the user a set of controls for operating on those. A common mistake many people make writing their first few editor scripts is putting logic for responding to the interface right there within the code on OnGUI. This might not seem like a mistake at first, it saves you some time and lets you get an editor window up and running quickly. The first problem you will encounter with this is you will be trying to manipulate the data you are looping through to display, resulting in adding a lot of workaround code and temporary variables to store information for once you leave a loop. The second problem you will encounter is you or someone else will probably end up wanting to perform some of these operations on your data elsewhere in your code, not just within this editor window. If the code that operates upon your data is all wrapped up in the editor window code, this becomes a giant pain in the ass.

This is where it becomes valuable to familiarize yourself with the “model-view-controller” architectural pattern. This is the pattern I believe works best when building tools that operate upon data. The model is your data model, where all of the information you are operating upon exists. The view is your editor window, the interface you provide to the user for seeing your data, and the input for manipulating data. The controller is the class that will operate upon the data, generally taking commands in from the view. There are countless benefits to building your editor windows in this style: you can build each piece as generic as possible, allowing you to quickly add new data models, new views, and new controllers when you need them, and mixing and matching pieces where appropriate. The first time you realize you need to perform an operation your editor window does to a set of data elsewhere in code, you will either thank yourself for following the model view controller pattern, or curse yourself that you have to re-write large blocks of your editor window code so you can pull out the function you need to call again elsewhere, because you did not follow this pattern.

The model view controller pattern can also help with that first problem I mentioned previously, when the view is operating upon the data it is attempting to display. If you build your controller in a way that you can send events to it, and it performs those events later at a safe point, such as when you are not iterating through the list you intend to delete a member from, the code within your editor window will be much cleaner and less buggy.

Following a design pattern such as this allows you to quickly build new views for the same controller and data model. This is especially relevant in Unity, where you can keep your data model and controller code outside of the editor folder, and then build two separate views for managing that data, one to run in the editor and perform advanced operations, and another to be run within the game, to manage that data at runtime.

The final large benefit I find from following the model view controller pattern is you can much more easily focus on each task individually. Instead of needing to figure out the logic for performing an action upon your data at the same time as you try and figure out how best display the interface for performing this action, you can focus on building the editor window first, and build a clean interface that calls into stubbed out empty functions in the controller.

When constructing these editor windows, you will often find yourself operating upon data you need to save when the user makes changes. I highly recommend you make it extremely obvious when this data is dirty and has changed, and make it immediately obvious how to save this data. A large frustration for tools users is going to be lack of feedback from a tool. The most frustrating feeling in game development is losing work. You don’t want a user to lose work because they misunderstood your interface, and assumed they had saved changes when they had not.

The next thing I recommend when building editor windows is to open up an image editing program or other tool, and build yourself some visual concepts for your menu. You might not think you are making a quick and simple menu, and the layout is not that important, but you often will be surprised how much time people spend in some menus you build, especially if you have provided a poor interface. Building these visual prototypes will also help you begin to identify the importance of different data elements you wish to display, and where it makes sense to place controls. You will also grasp the importance of spacing and placement of elements. Chances are, if you start building an editor window through code without having a prototype to work off of, you will take a lot more shortcuts than you otherwise would have, and will end up with an interface that you are not as happy with. You will also hit points where you decide you need to add a new element to your window, and you realize you have to do some major code rearranging to get it to where it would best fit in visually. It is much easier to work towards a prototype, you will be aware of where things should go before you begin writing the code.

Once you have a prototype and are ready to start building your window, you will want to refresh yourself on the commands available in OnGUI for creating controls and GUI elements. The GUI classes here http://docs.unity3d.com/Documentation/ScriptReference/GUI.html and http://docs.unity3d.com/Documentation/ScriptReference/EditorGUI.html have functionality for creating labels, buttons, boxes, and other GUI elements. GUI expects you to pass in the position of each element, so I prefer to work with GUILayout and EditorGUILayout http://docs.unity3d.com/Documentation/ScriptReference/GUILayout.html and http://docs.unity3d.com/Documentation/ScriptReference/EditorGUILayout.html. The layout classes will automatically position GUI elements. I prefer this because I do not need a giant list of constants for positions of UI elements, I do not need to keep X and Y offsets that I change between placing UI elements, I can just let Unity figure out where to place things. This is not to say you have no control over the positioning of these elements. GUILayout functions such as BeginVertical and EndVertical allow you to fine tune grouping and placement of GUI elements. You can also fine tune these GUI elements through the GUILayoutOptions that is often available as the final input to most of these elements. If you want to keep your buttons at a set width, you can put GUILayout.Width(widthValue) at the end of your call to GUILayout.Button, and not worry about the button auto-resizing when the user changes the size of the window, or the text on the button changes.

Once you begin working with these GUI classes, there are going to be some gotchas that crop up. I want to save you time by covering these before you begin building a menu, so you do not need to go back and fix things later. The first thing I recommend you do is build yourself a GUI and GUILayout class local to your project, to function as a library for extending the tools you have available. I am going to use the EditorGUILayout.TextField as an example here. The EditorGUILayout version of TextField is different than the GUILayout version, in that it takes an extra string that is used as a label for the text field. The first time you you use TextField, you will notice that you can only specify the total width of the object, and you have no control over the width of the label and the field input individually. When I first encountered this, it was a moment of mild frustration, as I had series of text labels on top of each other, and I wanted to make sure that the width of all of the labels were the same so the input fields would all start at the same X position, but I wanted to have different widths for the input fields, as I expected different fields would need to display different amounts of information. I found the best way to resolve this was to write my own “JoeGUILayout.TextField” function, that took in a width for the label and the text field separately, internally using GUILayout.Label and GUILayout.TextField to accomplish this.

As I was building these editor windows, I discovered that there was often a lot of data that I wanted available to view, but did not want to be always viewable. Either advanced setup options, or useful debugging information. This is when I started using EditorGUILayout.Foldout constantly. It provides a little triangle you can click on to show an area as visible or not, and provides a label. Unfortunately, Foldout is unlike most other GUI controls in that it does not take in a “params GUILayoutOptions[].” I was a stickler for precise positioning, and widths and heights of things, so I needed a solution for this. What I did here was first call GUILayout.Label with GUIContent.none as the data to display, and the GUILayoutOptions that I wished to use, including a width and height. The next thing I did after that was use EditorGUI.Foldout instead of EditorGUILayout, so I could pass in a position to force the foldout to sit within. This is where the GUILayoutUtility set of Unity utility functions here http://docs.unity3d.com/Documentation/ScriptReference/GUILayoutUtility.html came into play. The rectangle I passed to EditorGUI.Foldout was GUILayoutUtility.GetLastRect, cleanly placing the foldout within my window, and not disrupting the rest of the GUILayout functions. Wrap this code up together, and you can throw that into your “MyGUILayout” file to expand your tools.

Eventually I hit a point when building these menus that I was not making good use of color. Every GUI element was the same color against the same dark gray background. The first thing I did was place GUILayout.Box objects between different parts of my editor window, setting the height of the box to only a few pixels, and the width to the width of the editor window. Eventually I discovered that I can pass in the “style” of the Box to the grouping tools I had been using. An example of this would be GUILayout.BeginVertical(GUI.skin.box). This will render that semi-transparent box behind all elements within that vertical group. If you have another grouping nested in this one, then the transparency is additive, and the boxes blend together, making that nested box a bit brighter.

At some point, you are going to want to use a scroll view to display some content. GUILayout.BeginScrollView is perfect for this, it takes in and returns a scroll position as a Vector2, and otherwise functions very similar to the the other layout functions, such as BeginVertical. A very important thing to note is that you are not going to get any performance gains by only using scroll views to hide information. The logic for trying to render UI that is not in view still occurs, meaning if you have a very large scroll view containing a lot of information, by default all of that is going to render, and you will probably have an editor window that takes a long time to refresh, making it feel laggy and unresponsive. Luckily you can work around this. If you have elements in a scroll view, before attempting to render them, test to see if they are actually visible. This is much easier when using GUI and not GUILayout, as you will have to manually store the position of elements when using GUI, and with GUILayout you will have to figure out where the next thing you want to draw will be. For complex scroll views like this, I do recommend switching from GUILayout to GUI, it becomes easier to work that way. Writing yourself some helper functions, this will result in code that looks like if(IsInScrollView(scrollPosition,guiPosition)) { GUI.Label(guiPosition,”You Can See Me”); }. Once you introduce these tests before rendering UI elements, you will notice an immediate increase in performance on your OnGUI code, a complete refresh will be much faster.

Hopefully that is enough information to get you moving in the right direction on writing editor scripts. The ability to improve your workflow directly in Unity through these editor scripts is currently my favorite part of Unity. There are so many points in the game development process where you will want a quick and simple little tool to perform some repetitive action, or something more complex, and being able to so quickly write and build these makes the entire process of making games much more fun.

Advertisements
About

Joseph Stankowicz is a software engineer who has worked in the video games industry for over eight years. The last two years have had a heavy focus on Unity development, where he helped ship over eleven titles to iOS and Android platforms. He also is really excited about 3D printing, and keeps his Solidoodle 3 printing out stuff as often as possible. You can view his LinkedIn profile here http://www.linkedin.com/pub/joseph-stankowicz/60/294/420

Tagged with:
Posted in Unity3D, Unity3D Performance

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: