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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
UIElements Tutorial for Unity: Getting Started
35 mins
- Getting Started
- Creating an Empty Editor Window
- Understanding Visual Elements
- Exploring UXML Documents
- Analyzing a VisualElement
- Exploring the USS Document
- Exploring the Main Editor Window Controller
- Setting up the Editor Window
- Creating VisualElements Without UXML
- Attaching UXML and USS to the Editor Window
- Modifying UXML and USS Attachments
- Creating Preset Window Layouts
- Creating Layouts in UXML
- Making the Button Holder Layout
- Setting up the Main Container’s Layout
- Filling the Main Container
- Adding the Core UIElements
- Adding Functionality to the Editor Window
- Setting up the ObjectField
- Setting up Buttons
- Populating the List View
- Binding Values in UIElements
- Testing and Debugging the Editor Window
- Where to Go From Here?
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()
{
}
-
SetupControls()
: Adds actions to the buttons so something happens when you click them. -
PopulatePresetList()
: Populates the List view with VisualElements that represent the presets. -
LoadPreset(int elementID)
: Given an ID, the script will load a preset into the editor window. -
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:
- Makes a query to find the ObjectField, searching through the hierarchy starting from the
root
element. - Sets the ObjectField‘s
objectType
toPresetObject
, which is a pre-made script containing the presets. - Registers a
ChangeEvent
callback to thepresetObjectField
, so when anything changes, the code within it will run. - Creates a
SerializedObject
from the value of thepresetObjectField
and callsPopulatePresetList
.
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:
- Creates three button references by querying the rootVisualElement for the buttons you created in the PresetWindow.uxml.
- Attaches the code to let the
newButton
add a new empty preset to thepresetManager
. - Attaches the code to clear the settings of the
selectedPreset
to theclearButton
. - Sets the code to allow the
deleteButton
to delete theselectedPreset
.
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!
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;
}
- Find the ListView using queries, then clear it to prevent duplicates from any potential previously executed
PopulatePresetList()
calls. - Create a dictionary to monitor the index of the button, tracking what preset it belongs to.
- 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:
- Create a container using an empty VisualElement and create a label with the preset’s name. Add another button to load the preset.
- 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
. - Insert the
listContainer
at the end of thelist
. - 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. - 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.
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.