Exploring UIElements Part 2: Gridify

Gridify

Exploring UIElements Part 2: Gridify

In this tutorial, we’ll be continue learning the basics of Unity’s new UIElements framework by creating a custom editor window that makes it easy to spread out several GameObjects into a grid pattern – Gridify.

Update: Check out Part 3 of the series once you’re finished with this one.


Prerequisites

This is a beginner-level tutorial that assumes you have gone through Part 1 of the Extending the Unity Editor with UIElements series.

The Unity version used throughout this tutorial is 2019.3.12f1, but any version 2019.1 or newer should work.


What We’re Building

In this tutorial, we’ll be building a new custom editor window for moving GameObjects around in the Scene, specifically to spread them out into a grid pattern. We’ll learn how to:

  • Use Unity’s built-in custom editor window template.
  • Get numeric input from the user with the <FloatField> element.
  • Give your window a custom icon.
  • Integrate with the Unity editor’s Undo system. 1

… and more! Our goal is to end up with something that looks like this:

A preview of the tool’s final form.


Getting Started

We’ll start with a new 3D project. Create a new folder named Editor in your Assets folder. As in Part 1, all of our files will reside in this folder.

Creating an Editor Window ASAP

Go into the Editor folder you just created, right click, and select Create 🠚 UIElements 🠚 Editor Window. I chose the name Gridify, but you can pick anything you’d like.

The create UI editor window template, about to click Confirm.

Immediately after clicking Confirm, three files should be created and a new editor window should open. Unity just created a UXML template, a USS file for styling, and a C# file to glue everything together.

The three new files created after clicking confirm, and the new editor window that popped up.

Like the window we made in Part 1, it’s a first-class editor window that you can move, resize, or dock just like the Hierarchy, Scene, or Inspector windows. It’s a bit faster to get a UIElements-ready custom editor window up and running this way compared to our approach in the previous tutorial, but there is a bit of junk that we need to clean up before we start working on Gridify.


Cleaning Up the Defaults

Before we start working on our feature, let’s walk through the files that Unity just created and clean them up while we’re at it. We’ll start with the C# file:

Gridify.cs

First off are some imports, the class declaration where we subclass EditorWindow, and the function that opens the window and sets the title. Let’s change the menu path and rename the function for clarity:

using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using UnityEditor.UIElements;

public class Gridify : EditorWindow
{
    [MenuItem("Window/UIElements/Gridify")]
    [MenuItem("Custom Tools/Gridify")]
    public static void ShowExample()
    public static void OpenWindow()
    {
        Gridify wnd = GetWindow<Gridify>();
        wnd.titleContent = new GUIContent("Gridify");
    }

We should also add a hotkey to open our editor window quickly since we’ll need to refresh the window everytime we change our UXML files. You can take a look at the Unity docs for a reminder of how the hotkey syntax works.

public class Gridify : EditorWindow
{
    [MenuItem("Custom Tools/Gridify)]
    [MenuItem("Custom Tools/Gridify %#g")]
    public static void OpenWindow()

The only other function in the file is OnEnable, which sets up the UI when the window is opened. Let’s walk through it and modify it to suit the UI we will be building.

First off, a local variable is created to store a reference to the rootVisualElement property. This saves a few keystrokes later on in the function, but isn’t strictly necessary, so let’s get rid of it:


    public void OnEnable()
    {
        // Each editor window contains a root VisualElement object
        VisualElement root = rootVisualElement;

The next section creates a <Label> and attaches it to the root. We’ll be describing our UI in a UXML template, so we can delete this as well.

        // VisualElements objects can contain other VisualElement following a tree hierarchy.
        VisualElement label = new Label("Hello World! From C#");
        root.Add(label);

The following section loads the UXML template file created earlier, creates an element representing our UI from it, and attaches the new element to the root. This is what we did in Part 1, and is also what we want to do here, so we’ll leave it in, just making a few cosmetic changes for brevity and clarity.

        // Import UXML
        var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/Gridify.uxml");
        var uxmlTemplate = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/Editor/Gridify.uxml");
        VisualElement labelFromUXML = visualTree.CloneTree();
        var ui = uxmlTemplate.CloneTree();
        root.Add(labelFromUXML);
        rootVisualElement.Add(ui);

Finally, the sample code shows an example of loading a USS file and applying the resulting stylesheet to a VisualElement before adding it to the UI. We’ll be using a different method to apply our USS file to our UI, so let’s get rid of this entire section:

        // A stylesheet can be added to a VisualElement.
        // The style will be applied to the VisualElement and all of its children.
        var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Editor/Gridify.uss");
        VisualElement labelWithStyle = new Label("Hello World! With Style");
        labelWithStyle.styleSheets.Add(styleSheet);
        root.Add(labelWithStyle);
    }
}

OnEnable is now a slim 3 lines, and we’re done with the C# file for now. At this point, we should be able to refresh our editor window and see the following:

The editor window now only has a single, unstyled label.

We’re done with the C# file for now. Let’s move on to the USS file next.


Gridify.uss

The Unity style sheet file is short and sweet. It uses two standard supported CSS properties, font-size and color, as well as one Unity vendor extension property, -unity-font-style, which is similar to the unsupported font-weight CSS property.

Label {
    font-size: 20px;
    -unity-font-style: bold;
    color: rgb(68, 138, 255);
}

You can read more about USS syntax and supported properties in the official Unity USS documentation. We’ll leave this file alone for the moment and take a look at the UXML file that Unity generated for us.


Gridify.uxml

Like the USS file, the default UXML file is pretty trim already. It only has the required boilerplate for a UXML template and a single <Label>.

<?xml version="1.0" encoding="utf-8"?>
<engine:UXML
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:engine="UnityEngine.UIElements"
    xmlns:editor="UnityEditor.UIElements"
    xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
>
    <engine:Label text="Hello World! From UXML" />
</engine:UXML>

In Part 1, we loaded our USS file and attached it to our UI in C#, similar to the code we just removed from our C# file. USS files can also be applied to any VisualElement in a UXML file, except for the root <engine:UXML> element 4, by including a <Style> element as a child element.

Practically, this means we need to wrap our UI in a <VisualElement> and add a <Style> element as a child of that instead of the root. Using a <VisualElement> like this for grouping is similar to wrapping a portion of an HTML file in a <div> for styling purposes. For the src attribute, we’ll use the relative path to the USS file.

    xsi:noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd"
>
    <engine:VisualElement>
        <engine:Style src="Gridify.uss" />
        <engine:Label text="Hello World! From UXML" />
    </engine:VisualElement>
</engine:UXML>

If you refresh the editor window now, you should see that the stylesheet has been applied to the sibling <Label> element our UI.

The label in the editor window is now large and blue.

Now that we are confident that our stylesheet is being applied to our UI, we can finish cleaning up the default files. Let’s remove the <Label>:

        <engine:Style src="Gridify.uss" />
        <engine:Label text="Hello World! From UXML" />
    </engine:VisualElement>

At this point, if you refresh the editor window, you should be left with an empty window:

The editor window is now empty.

The last thing we need to do before we can begin working on our feature is to clean up the USS file that we looked at earlier.


Back to Renamerator2.uss

Obliterate the contents of the file, we’ll style things later:

Label {
    font-size: 20px;
    -unity-font-style: bold;
    color: rgb(68, 138, 255);
}

And with that, we’re ready to start working on our gridify feature. We’ll begin by building the basic structure of our UI.


Building the UI

Let’s take a look at what we want our window to look like again:

A sketch of the UI we are about to build, with 1 input and 1 button.

It looks like all we need is:

  • 1 numeric field
  • 1 button

We’ve already used the <VisualElement> subclass <Button> when we were building the Renamerator in Part 1. For the numeric field, we will use another built-in subclass, the <FloatField>, which is like the <TextField>, but only allows numeric input.

Unlike the <TextField> and <Button>, which are in the UnityEngine.UIElements namespace (aliased to engine), the <FloatField> is in the UIEditor.UIElements namespace (aliased to editor). With that in mind, let’s build our UI:

    <engine:Style src="Gridify.uss" />
    <editor:FloatField name="spread" label="Spread Distance" />
    <engine:Button name="gridifyBtn" text="Gridify Selected" />
  </engine:VisualElement>

Refresh the window to verify.

The window now has a numbers-only textbox with a large blue label and a button.

And with that, our UI’s structure is complete. It does look a little odd though.


Detour: The UIElements Debugger

Diagnosing Style Issues

You may be wondering why the <FloatField>'s label text is large and blue when our stylesheet doesn’t mention the FloatField element. UIElements comes with a powerful debugger that we can use to diagnose this issue, similar to the developer tools offered by most major web browsers. You can access it by right clicking your editor window tab and choosing UIElements Debugger from the context menu.

The editor window context menu is up, with the UIElements Debugger item highlighted.

The debugging window displays the structure of your UI on the left side, and the selected element’s properties on the right side. Hovering an item on the left side highlights the corresponding element on your window’s UI.

The Pick Element button performs this search in reverse – after clicking it, you hover an element on your UI, and it highlights the element in the debugging window. Let’s use it find out what’s going on with our UI by clicking Pick Element, then moving our cursor to hover the “Spread Distance” text:

The UIElements debugger is up and the FloatField’s Label is highlighted.

Now we can see that the <FloatField> is actually a compound element, made up of a <Label> and a <FloatInput>, and our USS file has a rule for Label elements. Looking at the right side of the debugger window, the last entry of the Matching Selectors section verifies that we’re matching the Label selector on line 1 of our USS file.

Live Editing the UI

Not only can we inspect our UI with the debugger, but we can also use it to modify our UI on the fly. If you scroll down the right side of the debugging window, you’ll see that most things are editable, such as the element’s Text, color, and font-size. Try editing some of the values, and you should see the changes reflected on your UI immediately.

The text content, size, and color have been changed via the debugger.

These changes will persist until you refresh the window.


Styling the UI

Before we start moving GameObjects around, let’s open up our USS file and make some small changes to our UI’s style.

Maintaining a Consistent Style

Now that we know the Label selector applies to our FloatField, let’s modify it to get rid of the color and bold, and reduce the font size a bit as well:

Label {
    font-size: 20px;
    font-size: 16px;
    -unity-font-style: bold;
    color: rgb(68, 138, 255);
}

The label is no longer blue or bold, and is smaller.

The <Label> now has larger text than the other parts of our UI, the <FloatInput> and the <Button>. We’d like to bump up the font size of the other elements to match. Like CSS, several USS selectors can share the same rule by separating the selectors with a comma. Let’s try to reuse our style for the other elements:

Button,
FloatInput,
Label {
    font-size: 16px;
}

The text content, size, and color have been changed via the debugger.

If we want our style to apply to all of our UI, we can use the universal selector, the asterisk. Let’s refactor our USS to use that instead:

Button,
FloatInput,
Label {
* {
    font-size: 16px;
}

I don’t like the way the button looks – it’s short and stretched all the way across the window. Let’s give it a bit of margin and padding to let it stand out more:

* {
    font-size: 16px;
}
Button {
    margin: 10px 30px;
    padding: 5px;
}

The button has padding and margin now.

The last thing our whiteboard sketch has that our UI doesn’t is an awesome hand-drawn icon. We’ll have to go back to our C# file for that.


A Custom Icon

The GUIContent constructor that we call in the OpenWindow function has an overload that accepts a Texture for the icon.

Drag a PNG format image that you want to be your icon into your Editor folder – I made my own 32x32 icon using GIMP and named it GridifyIcon:

The Gridify icon, a capital letter G in in a grid.

Your image may not preview correctly, instead display a black box, like this:

The Editor folder now has a PNG image file.

The icon will work in this state, but it is a bit annoying to not have a preview of the image in the Project view. You can fix it by selecting the image and changing the texture type of the image to “Sprite (2D and UI)” in the Inspector window.

The menu is open to change the image type from Default to Sprite 2D and UI.

After applying this change, the thumbnail should display correctly.

The thumbnail of the icon image is now correct.

To use the icon, we must load it and pass it to the GUIContent constructor:

        Renamerator2 wnd = GetWindow<Gridify>();
        var icon = (Texture2D)EditorGUIUtility.Load("Assets/Editor/GridifyIcon.png");
        wnd.titleContent = new GUIContent("Renamerator2");
        wnd.titleContent = new GUIContent("Renamerator\u00B2", icon);

If you save the changes above and refresh the editor window, you should have a fancy looking window tab like this:

The window title tab now has an icon and a unicode superscript number two.


Adding Functionality

Now that we’re done with the structure and style of our UI, it’s time to focus on functionality. Everything from here on out will be in the C# file.

Setting Up a Click Handler

First, let’s create a skeleton for our function that will be called when we click the gridify button:

        rootVisualElement.Add(ui);
    }

    void GridifySelected()
    {
        Debug.Log("[Gridify]");
    }
}

Next, let’s find the button in OnEnable using the Q query function and register our function to be called when the button is clicked:

        rootVisualElement.Add(ui);
        var btn = ui.Q<Button>("gridifyBtn");
        btn.clicked += GridifySelected;
    }

Clicking the button should now log to the debug console:

There is a log message in the debug console after the button was clicked.


Getting User Input - <FloatField>

Before we start moving GameObjects around, we should see how far the user wants to move them. Like the TextField in the previous tutorial, the FloatField gives access to the input text via the value property.

Let’s add a field to store a reference to the FloatField


public class Gridify : EditorWindow
{
    FloatField spread;

    [MenuItem("Window/UIElements/Gridify %#g")]

    }

… wire it up in OnEnable


        rootVisualElement.Add(ui);
        spread = ui.Q<FloatField>("spread");
        var btn = ui.Q<Button>("gridifyBtn");

… and check its value in the gridify function.


    void GridifySelected()
    {
        Debug.Log("[Gridify]");
        Debug.Log($"[Gridify] Spreading {spread.value} units.");
    }

Type anything in the field and verify that it’s echoed to the console when you click the button:

The debug console log message matches the float field.


Moving GameObjects

Alright, time to actually do some real work! In Part 1, we learned how to access the selected GameObjects via Selection.gameObjects. To move these objects, we can access the Transform component of each GameObject and set its position.

One Dimension

Let’s first line up objects along the X axis, then move onto a grid afterwards. We’ll use a humble for loop to iterate over the selected objects and move them, starting from (0,0,0):

First, some convenience variables:

    void GridifySelected()
    {
        var selected = Selection.gameObjects;
        var numObjs = selected.Length;
        var start = new Vector3(0, 0, 0);
        Debug.Log($"[Gridify] Spreading {spread.value} units.");

Now the loop – for each item, we need to calculate how far to move the object, and then move it.


        var start = new Vector3(0, 0, 0);

        Debug.Log($"[Gridify] Spreading {spread.value} units.");
        Debug.Log($"[Gridify] Spreading {numObjs} objects {spread.value} units.");
        for (var i = 0; i < numObjs; i++)
        {
            var xOffset = i * spread.value;
            var pos = start;
            pos.x += xOffset;
            selected[i].transform.position = pos;
        }
    }

Let’s test it out! Create several cubes (or anything) in your scene, select and few, and gridify them! You should see something like this:

Several cubes are now in a line after gridification.


Two Dimensions

In this section, we’ll modify our code to arrange the selected GameObjects in a square-ish grid pattern.

We want the smallest square that’s larger than the number of GameObjects we have selected. For example, if we had 8 objects, we’d want to arrange them in a 3x3 (9-element) square. We can calculate the side lengths by rounding up the square root of the number of elements, guaranteeing a square large enough to hold everything. For example, again assuming 8 objects: the square root of 8 is about 2.82, which rounds up to 3, so we would choose a side length of 3 and try to fill out a 3x3 square.

        var start = new Vector3(0, 0, 0);
        var sideLength = Mathf.CeilToInt(Mathf.Sqrt(numObjs));
        Debug.Log($"[Gridify] Spreading {numObjs} objects {spread.value} units.");

Now that we know how long the sides are, we will arrange the object in rows along the X axis, increasing the Z component everytime we get to a new row. Thus, the X component will have a cyclic pattern, like 0,1,2,0,1,2,0,1,2..., and the Z component will increase by 1 per sideLength objects, like 0,0,0,1,1,1,2,2,2,.... These patterns correspond to the the modulus (%) and integer division (/) operators, respectively.

        for (var i = 0; i < numObjs; i++)
        {
            var xOffset = i * spread.value;
            var xOffset = (i % sideLength) * spread.value;
            var zOffset = (i / sideLength) * spread.value;
            var pos = start;
            pos.x += xOffset;
            pos.z += zOffset;
            selected[i].transform.position = pos;
        }

That’s it! Now when we press the button, the GameObjects should snap into a grid formation instead of a line:

Several cubes are now in a grid format after gridification.


Finishing Touches

Our editor window is pretty awesome, but it has one significant problem. The Unity editor is unaware of what our custom editor window is doing, and that means that it can’t help us undo our changes. The Renamerator from Part 1 had this same issue. However, there is a solution.

Adding Undo/Redo Support

To get first-class Undo/Redo support, all we need to do is tell Unity what objects we are changing. Unity’s built-in Undo class has a RecordObjects function that takes an Object[] of objects to watch and a string name for the operation that you are performing.

The only catch regarding this function is that the objects are only shallowly monitored. This means that first-level properties are recorded, but properties of properties are ignored. Given some GameObject g, a change to g.name would be recorded, but a change to g.transform.position would not be – in short, more than one . means it won’t work.

What this means for us is that instead of passing in the array of GameObjects that we have (Selection.gameObjects), we need to generate an array of their Transforms to monitor instead. We’ll use LINQ’s Select and ToArray functions, so first we need to import LINQ:

using System.Linq;
using UnityEditor;

And then we can get a Transform[] to pass to Undo.RecordObjects:

        var sideLength = Mathf.CeilToInt(Mathf.Sqrt(numObjs));
        var transforms = selected.Select(x => x.transform).ToArray();
        Undo.RecordObjects(transforms, $"[Gridify] Moved {numObjs} objects");
        Debug.Log($"[Gridify] Spreading {numObjs} objects {spread.value} units.");

And with that done, we can gridify without reservation, because our changes will show up on the main Unity Edit menu like so:

The gridify action shows up in the undo/redo menu now.


Wrap Up

Final Code: https://github.com/exploringunity/gridify

This time we started with just a whiteboard drawing and a dream, and now we have a first-class custom editor window powered by UIElements that can do a snap GameObjects into a grid with just the click of a button.

Here are some of the things we covered:

  • Using the UIElements debugger
  • Integrating with Unity’s Undo system
  • Programmatically moving GameObjects around the scene
  • Giving your editor window an icon

What’s Next?

In Part 3 of this series, we’ll learn more about UIElements by continuing to work on The Renamerator from Part 1, adding support for regular expressions, confirmation before the rename, a preview of the changes, and more. See you next time!


  1. If you tried out The Renamerator from Part 1, you may have noticed that you can’t undo the rename using Edit 🠚 Undo. We’ll learn how to provide undo support in this part. ↩︎

  2. Kenney.nl is awesome, check them out! In their own words: “Free game assets, no strings attached. We’ve created thousands of sprites, 3D models and sound effects which you can use in your projects. The generous public domain license allows any kind of use, even commercial!” ↩︎

  3. Funny enough, programmatically planting trees with just a click is already a built-in Unity feature. ↩︎

  4. When I wrote this, the Unity documentation on UXML templates said that “Unity does not support <Style> elements under the root <UXML> element.” That is true for 2019.3.12f1, but seems to have been fixed in Unity 2020.1.0b3 (beta version). ↩︎