UIElements Tutorial for Unity: Getting Started

In this Unity tutorial, you’ll learn how to use Unity’s UIElements to create complex, flexible editor windows and tools to add to your development pipeline. By Ajay Venkat.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 5 of this article. Click here to view the first page.

Exploring the Main Editor Window Controller

In Unity’s UIElements, the UXML and USS documents are dynamic, which makes it possible to reuse them across multiple editor scripts. This allows you to keep the logic separate from the layout of the editor windows. It also means that PresetWindow.cs brings the UXML, USS documents and the logic together.

The main functions of this controller file are:

  • Opening Window: Provides the control logic and method for opening the editor window.
  • Starting Logic: Includes things such as linking UXML documents and applying USS documents to your VisualElements.
  • Event Management: Dictates what happens when users click on buttons and interact with the editor window.
  • Creating Dynamic Elements: UXML can’t create some VisualElements because they are static. You might want to create a variable number of elements, and you need logic to do this.

Open PresetWindow.cs and you should see the following code. Don’t worry if it looks overwhelming, it’s simple when you break it down.

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

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

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

        // 5
        // Import UXML
        var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>
            ("Assets/RW/Editor/PresetWindow.uxml");
        VisualElement labelFromUXML = visualTree.CloneTree();
        root.Add(labelFromUXML);

        // 6
        // 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/RW/Editor/PresetWindow.uss");
        VisualElement labelWithStyle = new Label("Hello World! With Style");
        labelWithStyle.styleSheets.Add(styleSheet);
        root.Add(labelWithStyle);
    }
}

Copy the above code and replace the entire PresetWindow class definition in PresetWindow.cs. Then save the file.

It isn’t any different to the boilerplate code that Unity generated earlier, except for the numbered code comments that you’ll reference in the following section, where you’ll dig into what the code does.

Setting up the Editor Window

Previously, to open the editor window you selected Window ► UIElements ► PresetWindow. The reason the menu item exists in that location is because of the menu path string set using the MenuItem attribute in the // 1 code comment section.

Change the line:

[MenuItem("Window/UIElements/PresetWindow")]

to:

[MenuItem("RW/Preset Window")]

Save the file, reopen Unity and notice the new menu created in the toolbar called RW. Now, you can open the editor window more easily by selecting RW ► Preset Window.

Preset Window option under RW

Next look at this code:

PresetWindow wnd = GetWindow<PresetWindow>();
wnd.titleContent = new GUIContent("PresetWindow");

Section // 2 is responsible for creating an instance of the editor window and assigning it a title.

Note: Both section 1 and 2 are part of Unity Editor Scripting, which offers a different path to creating in-editor tools. If you want to know more, check out our Extend the Unity3d Editor tutorial.

Creating VisualElements Without UXML

When the editor window opens and becomes active, Unity calls OnEnable(). This is where you should set up all the initial layout code, bindings and event triggers.

Look at section // 3 next:

// 3
// Each editor window contains a root VisualElement object
VisualElement root = rootVisualElement;

This code gets a reference to the rootVisualElement, which is the VisualElement at the top of the hierarchy for this editor window. As discussed before, to add elements to the editor window, you have to add VisualElements as a child to the rootVisualElement.

You’ve already seen that you can use UXML Documents to create VisualElements with structured layouts, but you can also create VisualElements dynamically.

In the section commented // 4, a new label with some text is added:

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

A Label is a subclass of TextElement, which is a subclass of VisualElement. This is how the Label can be created as a VisualElement in this bit of code.

Once you create the label, you add it to root using root.Add(label);, which makes it visible in the editor window.

Newly-added label

Attaching UXML and USS to the Editor Window

The main advantage of UXML documents is that you can use them in multiple places. Unity uses a Visual Tree, which is a hierarchy of VisualElements with parent and child relationships, to achieve this.

Each editor window has its own Visual Tree, which has a rootVisualElement at the top. A UXML document with layout information has its own self-contained Visual Tree. When you add a UXML document to an editor window, you’re merging the two Visual Trees.

Section 5 (commented // 5) gets a reference to the UXML file and integrates its Visual Tree into the editor window’s Visual Tree:

// Import UXML
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>
    ("Assets/RW/Editor/PresetWindow.uxml"); // 1
VisualElement labelFromUXML = visualTree.CloneTree(); // 2
root.Add(labelFromUXML); //3

Breaking this down:

  1. You create a variable that stores the Visual Tree that the PresetWindow.uxml generates.
  2. You clone the Visual Tree using visualTree.CloneTree(). The tree stores its relationships in the labelFromUXML variable.
  3. You add the labelFromUXML to the root using root.Add(labelFromUXML). This merges the two Visual Trees.

The USS will affect the VisualElement it’s on, as well as all the children of that VisualElement.

Lastly, in code commented section // 6:

// 6
// 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/RW/Editor/PresetWindow.uss");
VisualElement labelWithStyle = new Label("Hello World! With Style");
labelWithStyle.styleSheets.Add(styleSheet);
root.Add(labelWithStyle);

This simply creates a new label named labelWithStyle with PresetWindow.uss attached and adds it to the root.

Modifying UXML and USS Attachments

At the moment, the attachments of PresetWindow.uxml and PresetWindow.uss aren’t that helpful. What you want is to attach PresetWindow.uxml to the root and for PresetWindow.uss to act as a universal style sheet.

Make this change by removing all the code in OnEnable() and replacing it with the following:

// 1
VisualElement root = rootVisualElement;

// 2
var visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>("Assets/RW/Editor/PresetWindow.uxml");
VisualElement uxmlRoot = visualTree.CloneTree();
root.Add(uxmlRoot);

// 3
var styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/RW/Editor/PresetWindow.uss");

var preMadeStyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/RW/Editor/PresetTemplate.uss");

root.styleSheets.Add(styleSheet);

root.styleSheets.Add(preMadeStyleSheet);

Here’s a step-by-step breakdown of what you just did:

  1. Set a reference to the rootVisualElement.
  2. Got a reference to the Visual Tree of the PresetWindow.uxml and attached it to the root.
  3. Set a reference to the style sheet from PresetWindow.uss and PresetTemplate.uss, then attached it to the root. These style sheets are now universal!

Save PresetWindow.cs, reload the editor window and notice the changes:

Preset Window with text changes

  • You removed the VisualElements created within PresetWindow.cs, leaving only the label created within PresetWindow.uxml.
  • The PresetWindow.uss style sheet affects the PresetWindow.uxml, as it’s now universal.