All videos. All books. One low price.

Get unlimited access to all video courses and books on this site with the new raywenderlich.com Ultimate Subscription. Plans start at just $19.99/month.

Home Unity Tutorials

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.

4.8/5 12 Ratings

Version

  • C# 6, Unity 2019.3, Unity

Unity’s editor provides tools that make game development enjoyable, efficient and productive. Sometimes, however, you may need a tool that the editor doesn’t include by default. Luckily, Unity’s UIElements framework lets you create custom editor windows to add missing tools.

In this tutorial, you’ll learn to make a Preset Manager from scratch that modifies and stores different properties of a GameObject using UIElements.

Throughout this process, you’ll get hands-on experience with the following topics:

  • Writing Unity XML to create layouts.
  • Styling editor windows using Unity Style Sheets.
  • Manipulating and interacting with UIElements.
  • Registering events and callbacks.
  • Binding object values to UIElements.
  • Debugging editor windows.
Note: This tutorial assumes that you know the basics of Unity development, HTML and CSS. If you’re new to Unity development, check out this great Getting Started in Unity tutorial. To learn more about HTML and CSS, head on over to w3schools (HTML) and w3schools (CSS) to learn more.

You’ll need a copy of Unity 2019.3 (or newer) installed on your machine to follow along with this tutorial.

Getting Started

Now that you have Unity, you need to download the sample project by clicking on the Download Materials button at the top or bottom of this tutorial.

Once downloaded, extract the files and open the Introduction to UI Elements Begin project in Unity. With the project loaded, open the RW folder using the Project window and take a look at the folder structure:

RW Folder Structure

Here’s a quick breakdown of what each folder contains:

  • Animations: All the animations and animation controllers for the Preset GameObject.
  • Editor: At the moment, just a USS document with some pre-made styles. By the end of the tutorial, it will contain all the editor styles.
  • Materials: The default material for the Preset GameObject.
  • Scenes: The main game scene.
  • Scripts: All the scripts required for the animations and data handling.
  • Prefabs: The Preset prefab.

Open the Main Scene in the Scenes folder and look at the Game view. You’ll see a monkey head, which you’ll modify using UI Elements.

The main scene view

Creating an Empty Editor Window

An empty editor window is the blank canvas you’ll start with. You’ll then add UIElements to create layouts and build your custom tools.

In Unity, you need to store all editor-related scripts in a folder named Editor.

Navigate to RW/Editor and select Assets ► Create ► UIElements Editor Window. This will open the UIElements Editor Window Creator. Name the editor window PresetWindow and Confirm.

The UIElements Editor Window Creator screen

Within RW/Editor project folder, there will now be four files.

the newly added files with PresetTemplate stylesheet

The PresetTemplate.uss file is a pre-made style sheet to simplify the process of styling the editor window.

You may not realize it, but you just created your first editor window! So where is it? Right in front of you! The new editor window should have popped up in the Unity editor. If it didn’t, you can bring it up from Window ► UIElements ► PresetWindow.

Your first editor window

Note: The red text indicating the VisualElements is for reference only. It will not appear on-screen.

Now that you’ve built your first editor window, it’s time to get into some theory.

Understanding Visual Elements

The basic building block of all UIElements is the VisualElement. All elements are either a VisualElement or a sub-class of the VisualElement, making the UI hierarchy a collection of VisualElements parented to each other.

Each editor window has a rootVisualElement property, which represents the top element of the hierarchy. The root has to add a VisualElement as a child for Unity to know about and draw it.

Exploring UXML Documents

The UXML format is unique to Unity, but it takes its cues from other markup languages like HTML, XAML and XML. If you’re familiar with any of those, you’ll probably notice a lot of similarities while working with UXML. If these are foreign to you, take a moment to familiarize yourself with the starter UXML documentation.

Open the PresetWindow.uxml file in the RW/Editor project folder and take a look at the contents. This is the default UXML document attached to the editor window’s rootVisualElement.

The only section of this file you will be modifying is the green highlighted section. Everything else is code that won’t directly affect the layout of the editor window.

Now, take a look at the following line:

<engine:Label text="Hello World! From UXML" />

A simple label VisualElement with some text created that line. The line iteslf consists of two main parts: the type of VisualElement and its attributes.

Analyzing a VisualElement

PresetWindow.uxml with the code you'll change highlighted

The type of VisualElement here is a Label, which derives from engine, making it a built in control. This means it has a pre-defined look and functionality with its own custom attributes.

All VisualElements come with a common set of attributes such as the name and class.

The text attribute is unique to labels, but all VisualElements share the following attributes:

  • Name: An identifier for the element, which should be unique. The name helps you find the element in USS and C#.
  • Class: A space-separated list of identifiers that characterize the element. The class is primarily for assigning styles.

You can create your own VisualElements, but that’s beyond the scope of this tutorial.

Change the text attribute of the label to: I changed the text!. The line should now look like this:

<engine:Label text="I changed the text!" />

Save the UXML file and go to Unity… where you’ll see that nothing’s changed.

To reload UXML changes, you need to close the editor window and reopen it by selecting Window ► UIElements ► PresetWindow.

Note: From here on, reloading the editor window refers to closing and reopening the window.

PresetWindow.cs with the label changed

So far, you’ve only changed one of the text strings, but it’s only going to get more interesting from here!

Note: The reason only one of the labels’ text strings changed is because PresetWindow.cs creates the other two labels in code.

Exploring the USS Document

CSS is the inspiration behind USS, and many of the selectors and properties are similar. The layout system uses the Yoga Layout Engine.

Open PresetWindow.uss within RW/Editor and look at the contents:

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

At the moment, this style sheet only affects one label within the editor window, but you’ll change this later in PresetWindow.cs.

The style sheet only affects one label in PresetWindow.cs

Style sheets can be set to any VisualElement, and they’ll also affect all the children of that VisualElement. If you want to create a universal style sheet, you must assign the style sheet to the rootVisualElement.

Next, you’ll look at a break down of the current contents of the PresetWindow.uss file:

  1. Label: In this case, this is a type selector. It will apply the style to all VisualElements of type label that are children of the VisualElement the style sheet is on.
  2. font-size: Affects the label‘s font size.
  3. -unity-font-style: A custom Unity property that sets the font style of the text. Options include bold, italic and so on.
  4. color: A text property that modifies the color of the text.

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.

Creating Preset Window Layouts

Now that you’ve covered some of the theory, you can move on to creating the base layout for your Preset Window.

Preset Window's base layout

This is a plan for the different sections within the editor window. You can represent sections, or panels, within UXML as empty VisualElements.

The different sections within the editor window each have their own functions:

  • Button Bar: Contains action buttons such as creating and deleting presets.
  • Preset List: A List view that you’ll populate with the presets you created.
  • Description: A simple description of the purpose of the editor window.
  • Preset Settings: All the settings of the preset that you can change and save.

You can break down the expected layout into a Visual Tree that you can easily replicate in UXML.

UXML Visual Tree

Creating Layouts in UXML

Open PresetWindow.uxml and delete the following label VisualElement:

<engine:Label text="I changed the text!" />

Now, you have an empty section where you can insert new VisualElements:

Where to insert the new VisualElements

Save the PresetWindow.uxml and reload the editor window in Unity. You’ll see a clean, empty window:

Empty Preset Window

Insert the following code into the empty section in the PresetWindow.uxml:

<engine:VisualElement name="ButtonHolder">
	<!-- 1 -->
</engine:VisualElement>

<engine:VisualElement name="Container">

	<engine:VisualElement name="LeftPanel">
		<!-- 2 -->
	</engine:VisualElement>

	<engine:VisualElement name="RightPanel">

		<engine:VisualElement name="RightTopPanel">
			<!-- 3 -->
		</engine:VisualElement>
	
		<engine:VisualElement name="RightBottomPanel">
			<!-- 4 -->
		</engine:VisualElement>

	</engine:VisualElement>

</engine:VisualElement>

All you’re doing here is replicating the Visual Tree diagram above in UXML. You can break down each section of the editor window into empty VisualElements that you can fill with content. Note that you’ve made some name changes:

  1. LeftPanel: Refers to the Preset List.
  2. RightTopPanel: Was previously the Description.
  3. RightBottomPanel: Formerly, the Preset Settings.

Now that you have a clean layout, it’s time to start making it look the way you want.

Making the Button Holder Layout

Your first step is to change the layout of the area that will eventually hold your tool’s buttons.

To start, open PresetWindow.uss and remove everything in it, then insert the following code:

#ButtonHolder 
{
	height: 50px;
	width: auto;
	background-color: rgb(21, 132, 67);

	display: flex;
	flex-direction: row;
}

The # symbol lets you use the name selector and style it as ButtonHolder. You’ve changed the background-color to a dark green, set the height to a fixed size and set the width to auto.

Notice how the # symbol selector is used to select VisualElements based on their name attribute.

When display is set to flex, all child elements will use the Flexbox layout system commonly used in HTML/CSS.

You’ve also set flex-direction to row. This means VisualElements that are children of #ButtonHolder will display left-to-right.

Save PresetWindow.uss and check the editor window. You’ll see the following:

Preset Window's new Button Holder

Next, you’ll make the layout of the Main Container look just right.

Setting up the Main Container’s Layout

To start changing the Main Container’s layout, insert the following code under the #ButtonHolder section:

#Container 
{
	background-color: rgba(242, 246, 250,1);
	display: flex;
	flex-direction: row;
	width: auto;
	text-color: rgb(51, 51, 51);
}

This will create a container with a flex-direction of row so that Unity can arrange the panels from right to left. But when you save and reload the editor window, you’ll notice there are no changes.

That’s because you want the height property of the #Container to change with the height of the editor window. To fix this, open PresetWindow.cs and add the following code below the OnEnable() function:

private void OnGUI()
{
    // Set the container height to the window
    rootVisualElement.Q<VisualElement>("Container").style.height = new 
        StyleLength(position.height);
        
}

Unity runs OnGUI() every frame the editor window is open, so you can use this area to perform dynamic changes to UIElements.

Every frame, the PresetWindow.cs queries the rootVisualElement to find an object of type VisualElement with a name of Container.

Once the query has found the VisualElement, it changes the element’s height to the position.height, which is the height of the editor window. You use the StyleLength class when a VisualElement’s style requires a length value.

Sometimes, visuals are easier to digest when trying to understand new layout topics, so here’s a further breakdown:

Visual breakdown of the code to set the container height

Querying is very important with UIElements, as it’s the primary way to get the references of VisualElements you create in UXML documents.

Save PresetWindow.cs and open the editor window:

Empty editor window with a new layout

Notice the white tint, indicating that the Container fills the editor window as you intended.

Filling the Main Container

Now that you have an empty Main Container with the layout you want, it’s time to give it some subcontainers.

Navigate to the PresetWindow.uss and insert the following code after the #Container section:

#RightPanel 
{
	position: relative;
	flex: 1;
	width: auto;
	border-width: 2px;
	border-color: rgba(21, 132, 67,1);
}

#LeftPanel 
{
	display: flex;
	-unity-text-align: middle-center;
	flex: 1;
	width: auto;
	border-width: 2px;
	border-color: rgba(21, 132, 67,1);
}

#RightTopPanel 
{
	border-width: 1px;
	border-color: rgba(21, 132, 67,1);
	-unity-text-align: middle-center;
	display: flex;
	flex: 1;
}

#RightBottomPanel 
{
	border-width: 1px;
	border-color: rgba(21, 132, 67,1);
	-unity-text-align: middle-center;
	display: flex;
	flex: 1;
}

This creates the layouts for the other subcontainers using flex: 1 to scale the elements evenly between two elements that share the same parent. To further understand flex layouts, check the Yoga Documentation.

Save the file and open the editor window to see a beautiful Ray Wenderlich-themed window:

The PresetTemplate.uss that you attached earlier will set the USS for the other VisualElements.

Adding the Core UIElements

Now that you have a base layout, it’s just a matter of adding the VisualElements that you need in the correct places.

Open the PresetWindow.uxml and look at the different sections where you can place VisualElements, marked by comment placeholders that were copied in earlier.

Code showing where to place VisualElements

At 1, insert the following code:

<engine:Button name="NewButton" class="ButtonList" text="New Preset"/>
<engine:Button name="ClearButton" class="ButtonList" text="Clear Preset"/>
<engine:Button name="DeleteButton" class="ButtonList" text="Delete Preset"/>
<editor:ObjectField name="ObjectField"/>

This creates three named buttons and an ObjectField. You’ll add actions and triggers to these VisualElements in PresetWindow.cs.

At 2, insert the following code:

<engine:Label text="Saved Presets" style="-unity-font-style: bold; margin-top: 20px;"/>

<engine:ListView name="ListView"/>

This creates a label for the title and a List view, which you will populate in PresetWindow.cs.

At 3, insert:

<engine:Label text="Description" style="-unity-font-style: bold; margin-top: 20px;"/>

<engine:Label text="This is a preset manager that will be able to create and save variations of this GameObject so that various styles can be tested throughout the development of the game." style="margin-top: 20px; white-space: normal; line-height: 5px; font-size: 15;"/>

These two labels describe the editor window, which is a good example of inline styling with UXML.

At 4, insert:

<engine:Label text="Preset Bound Values" style="-unity-font-style: bold; margin-top: 20px; margin-bottom: 15px;"/>

<engine:TextField name="ObjectName" label="Object Name"/>
<editor:ColorField name="ColorField" label="Object Color"/>
<editor:Vector3Field name="SizeField" label="Object Size"/>
<editor:Vector3Field name="RotationField" label="Object Rotation"/>
<editor:FloatField name="AnimationSpeedField" label="Animation Speed"/>
<engine:Toggle name="IsAnimatingField" label="Is Animating"/>

This is a set of VisualElements created to modify the preset. You can use the label attribute to create a pre-made label next to the VisualElement to provide a description.

Open the editor window and be proud of the masterpiece you’ve created!

Editor window, with content

You’ll notice that there are hovering animations and rounded corners on the buttons at the top of the window. If you want to see what’s happening, open PresetTemplate.uss and explore its contents.

The ObjectField also mentions it has no type, meaning you have to configure it in PresetWindow.cs.

Adding Functionality to the Editor Window

It’s great that you have a good-looking editor window, but there’s no point if you can’t interact with it. Open the PresetWindow.cs and create the following empty methods:

private void SetupControls()
{

}

private void PopulatePresetList()
{

}

private void LoadPreset(int elementID)
{

}

private void BindControls()
{

}
  1. SetupControls(): Adds actions to the buttons so something happens when you click them.
  2. PopulatePresetList(): Populates the List view with VisualElements that represent the presets.
  3. LoadPreset(int elementID): Given an ID, the script will load a preset into the editor window.
  4. BindControls(): Binds the selected preset’s values to the editor window controls.

Setting up the ObjectField

Before you populate these methods, you need to set up the object field.

At the top of the PresetWindow.cs class, add the following private variables:

private PresetObject presetManager;
private SerializedObject presetManagerSerialized;
private Preset selectedPreset;
private ObjectField presetObjectField;

These are references that you’ll use to modify the presets. The SerializedObject binds the values.

At the end of the OnEnable() method, add the following code:

// 1
presetObjectField = root.Q<ObjectField>("ObjectField");

// 2
presetObjectField.objectType = typeof(PresetObject);

// 3
presetObjectField.RegisterCallback<ChangeEvent<UnityEngine.Object>>(e =>
{
    if (presetObjectField.value != null)
    {
        // 4
        presetManager = (PresetObject)presetObjectField.value;
        presetManagerSerialized = new SerializedObject(presetManager);
    }

    PopulatePresetList();
            
});

PopulatePresetList();
SetupControls();

There’s a lot going on here, so take a look at the code step-by-step:

  1. Makes a query to find the ObjectField, searching through the hierarchy starting from the root element.
  2. Sets the ObjectField‘s objectType to PresetObject, which is a pre-made script containing the presets.
  3. Registers a ChangeEvent callback to the presetObjectField, so when anything changes, the code within it will run.
  4. Creates a SerializedObject from the value of the presetObjectField and calls PopulatePresetList.

Visualization of the callbacks

Since UIElements run using Retained Mode GUI, you can’t check for a change in the ObjectField every frame. That’s why you use callbacks. This is a more efficient style of rendering a GUI in application programming.

You can register callbacks to any VisualElement and run a certain piece of code when one occurs. Find a complete list of all callbacks in the Unity3d docs.

Now that you’ve finished your ObjectField, your next step is to get your buttons ready to go.

Setting up Buttons

Get started by inserting the following into SetupControls():

// 1
Button newButton = rootVisualElement.Q<Button>("NewButton");
Button clearButton = rootVisualElement.Q<Button>("ClearButton");
Button deleteButton = rootVisualElement.Q<Button>("DeleteButton");

// 2
newButton.clickable.clicked += () => 
{
    if (presetManager != null)
    {
        Preset newPreset = new Preset();
        presetManager.presets.Add(newPreset);

        EditorUtility.SetDirty(presetManager);
                
        PopulatePresetList();
        BindControls();
    }
};

// 3
clearButton.clickable.clicked += () =>
{
    if (presetManager != null && selectedPreset != null)
    {
        selectedPreset.color = Color.black;
        selectedPreset.animationSpeed = 1;
        selectedPreset.objectName = "Unnamed Preset";
        selectedPreset.isAnimating = true;
        selectedPreset.rotation = Vector3.zero;
        selectedPreset.size = Vector3.one;
    }
};

// 4
deleteButton.clickable.clicked += () =>
{
    if (presetManager != null && selectedPreset != null)
    {
        presetManager.presets.Remove(selectedPreset);
        PopulatePresetList();
        BindControls();
    }
};

Here’s what this code does:

  1. Creates three button references by querying the rootVisualElement for the buttons you created in the PresetWindow.uxml.
  2. Attaches the code to let the newButton add a new empty preset to the presetManager.
  3. Attaches the code to clear the settings of the selectedPreset to the clearButton.
  4. Sets the code to allow the deleteButton to delete the selectedPreset.

Logic behind the new buttons

Unlike other VisualElements, the button doesn’t use RegisterCallback to detect clicks. Instead, it has a custom property called clicked that takes an action.

Congratulations, you now have three working buttons!

three clickable buttons wired up

Right now you can click them, and they execute the relevant methods, but the methods don’t contain any functional code yet. You’ll fix that next! :]

Populating the List View

Now that the buttons actually call methods, it’s time to show a list of all the presets in the List view.

Add this to the top of PresetWindow.cs so you can use the Dictionary type:

using System.Collections.Generic;

Next, insert the following into PopulatePresetList():

// 1
ListView list = (ListView)rootVisualElement.Q<ListView>("ListView");
list.Clear();
        
// 2
Dictionary<Button, int> buttonDictionary = new Dictionary<Button, int>();

// 3
if (presetManager == null)
{
    return;
}
  1. Find the ListView using queries, then clear it to prevent duplicates from any potential previously executed PopulatePresetList() calls.
  2. Create a dictionary to monitor the index of the button, tracking what preset it belongs to.
  3. Code safety. Return early if the presetManager is null for any reason.

With the first bit of code added to the PopulatePresetList() method, add the rest to complete it:

for (int i = 0; i < presetManager.presets.Count; i++) {
    // 1
    VisualElement listContainer = new VisualElement();
    listContainer.name = "ListContainer";

    Label listLabel = new Label(presetManager.presets[i].objectName);
       
    Button listButton = new Button();
    listButton.text = "L";

    // 2
    listLabel.AddToClassList("ListLabel");
    listButton.AddToClassList("ListButton");
    
    listContainer.Add(listLabel);
    listContainer.Add(listButton);
            
    // 3
    list.Insert(list.childCount, listContainer);

    if (!buttonDictionary.ContainsKey(listButton))
    {
        buttonDictionary.Add(listButton, i);
    }


    // 4
    listButton.clickable.clicked += () => 
    {
        if (presetObjectField.value != null)
        {
            LoadPreset(buttonDictionary[listButton]);
        }
    };

    // 5
    if (selectedPreset == presetManager.presets[buttonDictionary[listButton]])
    {
        listButton.style.backgroundColor = new StyleColor(new 
        Color(0.2f,0.2f,0.2f,1f));

    }
}

What you’re essentially doing here is looping through all the presets stored in presetManager and creating a new VisualElement. At each iteration, you add to the list, styling the VisualElements and attaching actions to the buttons of each element along the way.

Don’t worry if it looks overwhelming, here’s a breakdown:

  1. Create a container using an empty VisualElement and create a label with the preset’s name. Add another button to load the preset.
  2. Add the label and button to their respective classes to style them. The styles for these elements are in PresetTemplate.uss. You then add the elements as children of listContainer.
  3. Insert the listContainer at the end of the list.
  4. Register a click event to the button to load the preset. You use the buttonDictionary to load the correct preset, which corresponds to the button you press.
  5. If the loop is currently on the selectedPreset, then highlight the button with a color change to symbolize its selection.

The LoadPreset(int elementID) method doesn’t do anything right now. To change that, add the following:

if (presetManager != null)
{
    selectedPreset = presetManager.presets[elementID];
    presetManager.currentlyEditing = selectedPreset;

    PopulatePresetList();

    if (selectedPreset != null)
    {
        BindControls();
    }
}
else
{
    PopulatePresetList();
}

This code simply sets the selectedPreset to the preset of the button you clicked and refreshes the editor.

Before you go any further, save the PresetWindow.cs and return to the editor. Test the editor by dragging the Preset Object GameObject from the Hierarchy, into the editor window’s object field slot.

The editor window is almost complete

Take a moment to appreciate how far you’ve come! There’s only one more thing left to do: Attach the fields to the preset.

Binding Values in UIElements

In the context of UIElements, binding is when you directly link VisualElement fields to variables within scripts. When a VisualElement Field changes, the variables will change and vice versa. In this case, you want to link to the preset’s settings.

To bind the properties of the selectedPreset to the fields, insert the following into BindControls():

// 1 - Finding the properties in the selected preset manager
SerializedProperty objectName = presetManagerSerialized.FindProperty("currentlyEditing.objectName");

SerializedProperty objectColor = presetManagerSerialized.FindProperty("currentlyEditing.color");

SerializedProperty objectSize = presetManagerSerialized.FindProperty("currentlyEditing.size");

SerializedProperty objectRotation = presetManagerSerialized.FindProperty("currentlyEditing.rotation");

SerializedProperty objectAnimationSpeed = presetManagerSerialized.FindProperty("currentlyEditing.animationSpeed");

SerializedProperty objectIsAnimating = presetManagerSerialized.FindProperty("currentlyEditing.isAnimating");

// 2 - Binding those properties to the corresponding element
rootVisualElement.Q<TextField>("ObjectName").BindProperty(objectName);

rootVisualElement.Q<ColorField>("ColorField").BindProperty(objectColor);

rootVisualElement.Q<Vector3Field>("SizeField").BindProperty(objectSize);

rootVisualElement.Q<Vector3Field>("RotationField").BindProperty(objectRotation);

rootVisualElement.Q<FloatField>("AnimationSpeedField").BindProperty(objectAnimationSpeed);

rootVisualElement.Q<Toggle>("IsAnimatingField").BindProperty(objectIsAnimating);

currentlyEditing is a Preset found within PresetObject.cs, which stores the selected preset.

Here’s what this code does:

  1. Finds each property, such as color, size etc., within the presetManagerSerialized and stores it in a reference variable.
  2. Uses queries to find the corresponding fields. It then binds the fields to the property using BindProperty(SerializedProperty).

Save the PresetWindow.cs file and return to the Unity editor.

Testing and Debugging the Editor Window

Test the editor window by making new presets and checking if the values change in real-time in the inspector.

Drag the Preset Object GameObject from the Hierarchy, into the Preset Field at the top-right corner of your PresetWindow editor. Make sure the Preset Object GameObject is selected in the Hierarchy to show it’s properties in the Inspector. Then, click the L button next to the newly created preset in your custom editor before making changes.

The final editor window

Select a preset by using the Applied Preset field in the inspector, press Play and watch the GameObject take the form of your preset.

Editing the monkey head animation

Unity also provides a debugger for UIElements, which you can access by opening Window ► Analysis ► UIElements Debugger.

Pick Select a panel ► PresetWindow and then Pick Element.

Opening the debugger

You can inspect elements, check their styling and scaling, and even see the associated Visual Tree! Best of all, you can change things on the fly to see how they look.

Great job! You can now create versatile and advanced editor windows to take your projects to a whole new level.

Where to Go From Here?

You can download the complete project using the Download Materials button at the top or bottom of this tutorial.

In this Unity UIElements tutorial, you learned how to:

  • Create Editor Windows using UXML and USS.
  • Add functionality to UIElements.
  • Create new elements and binding properties.

This project is only the beginning; there’s so much more you can add. I’m interested to see what you come up with!

Did you enjoy this tutorial? Want to learn more? Check out our book Unity Games by Tutorials, which has more info on making games with Unity.

If you want to learn more about Unity’s UIElements, check out this UIElements tutorial by Unity.

You’ll also find some useful information in the official UIElements Developer Guide.

If you have any suggestions, questions or you want to show off what you did to improve this project, join the discussion below.

Average Rating

4.8/5

Add a rating for this content

12 ratings

More like this

Contributors

Comments