Version: 2021.3+
This example demonstrates how to bind custom controls to custom data types.
This example creates a custom data type and a custom control based on three built-in controls. It binds the custom control to the custom data type. The drawer converts between Celsius and Fahrenheit.
You can find the completed files that this example creates in this GitHub repository.
This guide is for developers familiar with the Unity Editor, UI Toolkit, and C# scripting. Before you start, get familiar with the following:
Create a custom data type Temperature
, and use it as a serialized property.
Create a Unity project with any template.
Create a folder named bind-custom-data-type
to store all the files.
Create a C# script named Temperature.cs
and replace its contents with the following:
using System;
namespace UIToolkitExamples
{
public enum TemperatureUnit
{
Celsius,
Farenheit
}
[Serializable]
public struct Temperature
{
public double value;
public TemperatureUnit unit;
}
}
Create a C# script named PlanetScript.cs
and replace its contents with the following:
using UnityEngine;
namespace UIToolkitExamples
{
public class PlanetScript : MonoBehaviour
{
public Temperature coreTemperature;
}
}
Create a custom Editor for Planet
and a custom Property Drawer for Temperature
.
In the custom Property Drawer, implement a button that converts temperature between Fahrenheit and Celsius by writing to properties of SerializedProperty
(use doubleValue
and enumValueIndex
) and then calling SerializedObject.ApplyModifiedProperties()
.
The custom Property Draweris is considered as a custom control. It’s a built-in control that behaves in a custom way.
Create a folder named Editor
.
In the Editor folder, create a C# script named PlanetEditor.cs
and replace its contents with the following:
using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
namespace UIToolkitExamples
{
[CustomEditor(typeof(PlanetScript))]
public class PlanetEditor : Editor
{
public override VisualElement CreateInspectorGUI()
{
return new PropertyField(serializedObject.FindProperty("coreTemperature"));
}
}
}
In the Editor folder, create a C# script named TemperatureDrawer.cs
and replace its contents with the following:
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace UIToolkitExamples
{
[CustomPropertyDrawer(typeof(Temperature))]
public class TemperatureDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var asset = Resources.Load<VisualTreeAsset>("temperature_drawer");
var drawer = asset.Instantiate(property.propertyPath);
drawer.Q<Label>().text = property.displayName;
// Don't allow conversion when you've selected multiple objects in the Inspector
if (!property.serializedObject.isEditingMultipleObjects)
{
drawer.Q<Button>().RegisterCallback<ClickEvent, SerializedProperty>(Convert, property);
}
return drawer;
}
static void Convert(ClickEvent evt, SerializedProperty property)
{
var valueProperty = property.FindPropertyRelative("value");
var unitProperty = property.FindPropertyRelative("unit");
// F -> C
if (unitProperty.enumValueIndex == (int)TemperatureUnit.Farenheit)
{
valueProperty.doubleValue -= 32;
valueProperty.doubleValue *= 5.0d / 9.0d;
unitProperty.enumValueIndex = (int)TemperatureUnit.Celsius;
}
else // C -> F
{
valueProperty.doubleValue *= 9.0d / 5.0d;
valueProperty.doubleValue += 32;
unitProperty.enumValueIndex = (int)TemperatureUnit.Farenheit;
}
// Important: Because you are bypassing the binding system, you must save the modified SerializedObject
property.serializedObject.ApplyModifiedProperties();
}
}
}
Create a UXML file with the following:
Set the binding-path
of the two fields to value
and unit
of the Temperature
property.
In the Editor folder, create a folder named Resources
.
In the Resources folder, create a UI Document named temperature_drawer.uxml
and replace its contents with the following:
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<ui:VisualElement class="unity-base-field">
<ui:Label class="unity-base-field__label" />
<ui:VisualElement class="unity-base-field__input" style="flex-direction: row;">
<uie:DoubleField binding-path="value" />
<uie:EnumField binding-path="unit" />
<ui:Button text="Convert" />
</ui:VisualElement>
</ui:VisualElement>
</ui:UXML>
Temperature
property of the custom contol changes.