Пользовательские элементы управления
Пользовательские элементы управления
UI Toolkit предоставляет стандартный набор элементов для построения интерфейсов, но вы также можете создавать пользовательские элементы управления под задачи своего приложения. Например, индикатор здоровья может менять цвет в зависимости от текущего значения: по мере его снижения плавно переходить от зелёного к жёлтому и красному. Такой элемент можно без дополнительной настройки использовать для разных персонажей или приспособить для других показателей, например маны или силы. Этот самодостаточный элемент управления станет более наглядной заменой стандартному ползунку из библиотеки UI Toolkit. Пользовательские элементы управления позволяют заключить функциональность в самостоятельные компоненты и повторно использовать их в разных частях интерфейса. Хорошо спроектированный элемент не зависит от конкретного контекста, самодостаточен и способствует повторному использованию кода, упрощая сопровождение проекта. Не стоит создавать пользовательский элемент управления для части интерфейса, которая жёстко связана с конкретным компонентом и не имеет самостоятельной функции, например для игрового меню целиком.
Атрибут UxmlElement Чтобы создать пользовательский элемент управления, определите новый C#-скрипт, класс которого наследуется от VisualElement или от более подходящего производного класса. Нужен элемент, похожий на кнопку? Унаследуйте его от Button.
производительности |
Чтобы пользовательский элемент управления стал доступен в UXML и UI Builder, добавьте к его классу атрибут UxmlElement. Сам класс должен быть объявлен как public partial:
[UxmlElement] public partial class ExampleElement: VisualElement {
} После этого пользовательский элемент управления появится в разделе Library UI Builder, в категории Custom Controls (C#). Оттуда его можно перетащить в окно Hierarchy.
Пользовательские элементы управления отображаются в библиотеке UI Builder.
производительности |
Визуальные элементы не являются GameObject, поэтому у них нет привычных событий жизненного цикла Awake, OnEnable, OnDisable и OnDestroy. Пользовательский элемент управления инициализируется в конструкторе.
[UxmlElement] public partial class ExampleElement: VisualElement {
// Constructor
public ExampleElement() { // Initialization }
}Инициализацию также можно отложить до добавления пользовательского элемента в интерфейс. Для этого зарегистрируйте обратный вызов AttachToPanelEvent. Чтобы определить, когда элемент удалён из интерфейса, используйте обратный вызов DetachFromPanelEvent.
Атрибут UxmlAttribute Если добавить к свойству атрибут UxmlAttribute, оно появится в окне Inspector UI Builder и начальное значение можно будет задать интерактивно. Это особенно удобно при совместной работе с дизайнером: изменения в Inspector не требуют правки кода. Примените UxmlAttribute к каждому свойству, которое требуется открыть для настройки. Имя атрибута можно изменить аргументом name. При выборе элемента управления в Hierarchy его пользовательские атрибуты отображаются в Inspector, где их можно настроить напрямую. Атрибуты-декораторы изменяют представление полей пользовательских атрибутов примерно так же, как при работе с MonoBehaviour. Среди полезных декораторов TextArea, Tooltip, Range, Header, Min, Multiline, Space и Delayed. Например, Range добавляет ползунок для выбора значения в заданном диапазоне.
производительности |
Пользовательские атрибуты отображаются в Inspector UI Builder.
Ниже приведён простой пример пользовательского элемента управления с атрибутом UxmlElement и двумя открытыми свойствами, отмеченными UxmlAttribute.
[UxmlElement] public partial class ExampleElement: VisualElement { [UxmlAttribute(name:"my-text")]
public string myStringValue { get; set; }
[UxmlAttribute] public int myIntValue { get; set; }
}В этом примере благодаря параметру name свойство MyStringValue отображается в Inspector под именем "My Text". Свойства MyStringValue и MyIntValue можно редактировать в Inspector, когда в Hierarchy выбран экземпляр ExampleElement.
производительности |
До Unity 6 для создания пользовательских элементов управления требовалось реализовывать классы UxmlTraits и UxmlFactory, которые отвечали за регистрацию атрибутов и создание экземпляров пользовательских элементов. В Unity 6 появились атрибуты UxmlElement и UxmlAttribute, упрощающие этот процесс. Они напрямую делают пользовательские элементы управления и их свойства доступными в UXML и UI Builder. Новый рабочий процесс требует меньше шаблонного кода и ускоряет настройку элементов интерфейса.
Пример: пользовательский переключатель-ползунок Простой пример пользовательского элемента управления переключатель-ползунок, представляющий логическое значение. Он может быть привлекательнее стандартного переключателя. Дополнительная визуальная обратная связь - анимация ползунка, смена цвета и динамический текст - делает интерфейс понятнее.
Пользовательский переключатель-ползунок представляет логическое значение.
Определение пользовательского элемента управления Простая реализация такого элемента находится в сцене CustomControlsDemo проекта QuizU. Чтобы разобраться в её работе, откройте скрипт SlideToggle.cs; его фрагменты приведены ниже. Пользовательский элемент SlideToggle наследуется от наиболее подходящего базового класса - в данном случае BaseField<bool>. Атрибут UxmlElement делает элемент доступным в UXML и UI Builder, благодаря чему его можно использовать повторно.
[UxmlElement] public partial class SlideToggle : BaseField<bool> {
// …производительности |
public string EnabledText { get; set; }
= "Enabled";
[UxmlAttribute] public string DisabledText { get; set; }
= "Disabled";
[UxmlAttribute] public Color EnabledBackgroundColor { get; set; }
= new Color(0f, 0.5f, 0.85f,1f);
[UxmlAttribute] public Color DisabledBackgroundColor { get; set; }
= Color.gray;
Визуальная структура состоит из фона (m_Input) и ползунка (m_Knob), а внешний вид задаётся классами USS.public SlideToggle(string label) : base(label, new VisualElement()) {
AddToClassList(ussClassName);
m_Input = this.Q(className: BaseField<bool>.inputUssClassName);
m_Input.AddToClassList(inputUssClassName);
m_Input.name = "input";
m_Knob = new();
m_Knob.AddToClassList(inputKnobUssClassName);
m_Knob.name = "knob";
m_Input.Add(m_Knob);
labelElement.name = "label";
labelElement.text = (value) ? "enabled" : "disabled";
Обработчики реагируют на щелчки, нажатия клавиш и события навигации, поэтому состояние элемента можно менять несколькими способами.производительности |
// … RegisterCallback<ClickEvent>(evt => OnClick(evt));
RegisterCallback<KeyDownEvent>(evt => OnKeydownEvent(evt));
// …
}
static void OnClick(ClickEvent evt) {
var slideToggle = evt.currentTarget as SlideToggle;
slideToggle.ToggleValue();
evt.StopPropagation();
}
static void OnKeydownEvent(KeyDownEvent evt) {
var slideToggle = evt.currentTarget as SlideToggle;
if (slideToggle.panel?.contextType == ContextType.Player) return;
if (evt.keyCode == KeyCode.KeypadEnter || evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.Space) {
slideToggle.ToggleValue();
evt.StopPropagation();
}
}При переключении элемента надпись и цвет фона автоматически обновляются, обеспечивая визуальную обратную связь. Здесь SetValueWithoutNotify обновляет визуальное состояние переключателя, не вызывая ChangeEvent. Этот метод вызывается внутри элемента при изменении значения, поэтому интерфейс обновляется корректно и не попадает в бесконечный цикл обновлений.
public override void SetValueWithoutNotify(bool newValue) {
base.SetValueWithoutNotify(newValue);
m_Input.EnableInClassList(inputCheckedUssClassName, newValue);
m_Input.style.backgroundColor = newValue ? EnabledBackgroundColor : DisabledBackgroundColor;
labelElement.text = (value) ? EnabledText : DisabledText;
}производительности |
Изучите пример реализации в сцене CustomControlsDemo. Чтобы переключить активное состояние элемента, щёлкните по нему мышью или нажмите Enter либо пробел. В этом примере при переключении динамически меняются надпись и цвет фона, а короткая анимация обеспечивает визуальную обратную связь.
В Inspector задайте надписи и цвета фона для включённого и выключенного состояний.
Настройте текст и цвета SlideToggle.
Использование SlideToggle После компиляции SlideToggle можно встраивать в любую часть интерфейса. Используйте пользовательский класс SlideToggle так же, как любой другой визуальный элемент. Ниже приведён пример, в котором SlideToggle включает и отключает звук:
public class MuteAudioToggle : MonoBehaviour {
[SerializeField] AudioSettingsSO m_AudioSettingsSO;
[SerializeField] UIDocument m_Document;
void OnEnable() {
var root = m_Document.rootVisualElement;
SlideToggle slideToggle = root.Q<SlideToggle>("master-audio-toggle");производительности |
if (slideToggle != null) {
slideToggle.value = !m_AudioSettingsSO.IsMasterMuted;
slideToggle.RegisterValueChangedCallback(evt => m_AudioSettingsSO.IsMasterMuted = !evt.newValue);
}
}
}Здесь SlideToggle входит в существующий UXML-документ. MonoBehaviour находит его по имени в визуальном дереве, а затем методом RegisterValueChangedCallback связывает состояние переключателя с настройками звука.
Поскольку SlideToggle - самостоятельный пользовательский элемент, его можно применять для любых переключателей в интерфейсе. Например, в проекте UI Toolkit Sample - Dragon Crashers похожий SlideToggle включает и отключает счётчик кадров в секунду.
Стилизованный переключатель из проекта UI Toolkit Sample - Dragon Crashers
Адаптируйте SlideToggle к требованиям приложения: он отлично подходит для настроек графики, звука и игрового процесса. Создайте его один раз и используйте везде, где нестандартный переключатель улучшит взаимодействие с пользователем. Полную реализацию см. в скрипте SlideToggle.cs проекта QuizU.
производительности |
Создание других пользовательских элементов управления Если нужного элемента нет в стандартной библиотеке UI Toolkit, его можно создать самостоятельно. Вот несколько примеров применения пользовательских элементов управления в играх:
- Индикаторы здоровья и прогресса. Такие игровые показатели, как здоровье, мана и сила, сильно зависят от игрового процесса, поэтому хорошо подходят для пользовательских элементов управления. Откройте через UxmlAttribute максимальное и текущее значения, а также цвета состояний, чтобы можно было настраивать цветовые градиенты. - Рейтинговые звёзды. Этот элемент работает как сегментированный индикатор прогресса и представляет целое значение, например количество звёзд за прохождение уровня. Создайте визуальный элемент с несколькими дочерними элементами, которые переключаются между заполненным и пустым состояниями. Откройте в Inspector целочисленное значение и его максимум, а изображения спрайтов позвольте настраивать через UxmlAttribute. - Представление с вкладками. Вкладки часто используются для переключения между представлениями или разделами одного окна. Создайте пользовательский элемент со строкой вкладок и областью содержимого. Каждая вкладка может быть визуальным элементом, похожим на кнопку; предусмотрите возможность динамически добавлять и удалять вкладки. Не забывайте, что в большинстве случаев визуальный эффект можно усилить анимированными переходами USS. Пользовательские элементы управления позволяют игрокам использовать жесты, щёлкать, прокручивать и переключать элементы уникального интерфейса вашей игры. Нам не терпится увидеть, что вы создадите.
Custom controls
UI Toolkit offers a standard set of elements for building interfaces, but you can also create custom controls tailored to your application’s needs. For instance, a custom health bar could change color based on health value, animating from green to yellow and red as health decreases. It could be repurposed across characters without extra setup – or even used to represent other stats, like mana or power. This encapsulated control would offer a clear visual upgrade to the slider from the UI Toolkit standard library. Custom controls let you encapsulate functionality into standalone elements, making them reusable across different parts of your interface. Well-designed controls are abstract, selfcontained, and support code reuse, helping simplify project maintenance. When implementing custom controls, avoid using them with elements tied to specific components that lack standalone functionality (e.g., game menus).
The UxmlElement attribute To create a custom control, start by defining a new C# script that inherits from the VisualElement class – or a subclass that closely matches what you want to create. Want a button-like control? Just inherit from the Button class.
To make your custom control available in UXML and the UI Builder, add the UxmlElement attribute to your class. Ensure that the custom element is defined as a public partial class: [UxmlElement] public partial class ExampleElement: VisualElement {
} Your custom control will then appear in the Library section under the Custom Controls (C#) category in the UI Builder. You can then drag it into UI Builder’s Hierarchy window.
Custom controls appear in the UI Builder Library.
Because visual elements aren’t GameObjects, they don’t have the usual lifecycle events like Awake, OnEnable, OnDisable, and OnDestroy. Instead, you initialize a custom control using its constructor. [UxmlElement] public partial class ExampleElement: VisualElement {
// Constructor
public ExampleElement() { // Initialization }
}
You can also delay initialization until the custom control is added to the UI. To do this, register a callback for an AttachToPanelEvent. To detect that your custom control has been removed from the UI, use the DetachFromPanelEvent callback.The UxmlAttribute attribute Adding the UxmlAttribute attribute to a property makes it appear in the UI Builder’s Inspector window. This allows you to set initial values interactively. UxmlAttributes can be helpful when working with a designer, as changes in the Inspector don’t require modifying code. Apply the UxmlAttribute attribute to each property you want to expose. You can also customize attribute names with the name argument. Selecting the control in the Hierarchy will display your custom attributes in the Inspector window, allowing you to configure them directly. Decorator attributes can modify your custom attribute fields much like working with MonoBehaviours. Useful decorator attributes include TextArea, Tooltip, Range, Header, Min, Multiline, Space, and Delayed. For example, using the Range attribute adds a slider for selecting values within a range.
Custom attributes appear in the UI Builder’s Inspector.
Here’s a basic example of adding the UxmlElement attribute to a custom control, which includes two exposed properties using the UxmlAttribute attribute. [UxmlElement] public partial class ExampleElement: VisualElement { [UxmlAttribute(name:"my-text")]
public string myStringValue { get; set; }
[UxmlAttribute] public int myIntValue { get; set; }
}
In this example, MyStringValue appears as "My Text" in the Inspector using the name parameter. Both MyStringValue and MyIntValue are editable in the Inspector whenever an instance of ExampleElement is selected in the Hierarchy.Before Unity 6, creating custom controls required implementing UxmlTraits and UxmlFactory classes, which handled attribute registration and object instantiation for custom elements. Unity 6 simplifies custom element creation by introducing UxmlElement and UxmlAttribute attributes. These directly expose custom controls and properties in UXML and the UI Builder. This new workflow reduces the amount of boilerplate code and makes it faster to customize UI elements.
Example: A custom slide toggle control An example of a simple custom control could be a slide toggle, a switch-like element representing a boolean value. This might offer a more engaging experience than a standard toggle. Adding extra visual feedback, such as an animated switch, changing color, and dynamic text, can result in a more intuitive UI.
The custom slide toggle control represents a boolean value.
Defining the custom control In the QuizU project, you can find a simple implementation of this custom control in the CustomControlsDemo scene. Open the SlideToggle.cs script to see how it works (snippets shown below). The slide toggle custom control inherits from the most suitable base class – BaseField<bool> in this case. The UxmlElement attribute exposes the control in UXML and the UI Builder, making it reusable. [UxmlElement] public partial class SlideToggle : BaseField<bool> { // …
public string EnabledText { get; set; }
= "Enabled";
[UxmlAttribute] public string DisabledText { get; set; }
= "Disabled";
[UxmlAttribute] public Color EnabledBackgroundColor { get; set; }
= new Color(0f, 0.5f, 0.85f,1f);
[UxmlAttribute] public Color DisabledBackgroundColor { get; set; }
= Color.gray;
The visual structure consists of a background (m_Input) and a knob (m_Knob), with USS classes defining the appearance.public SlideToggle(string label) : base(label, new VisualElement()) {
AddToClassList(ussClassName);
m_Input = this.Q(className: BaseField<bool>.inputUssClassName);
m_Input.AddToClassList(inputUssClassName);
m_Input.name = "input";
m_Knob = new();
m_Knob.AddToClassList(inputKnobUssClassName);
m_Knob.name = "knob";
m_Input.Add(m_Knob);
labelElement.name = "label";
labelElement.text = (value) ? "enabled" : "disabled";
Event handling is implemented to respond to clicks, key presses, and navigation events. This allows multiple ways to change its state.// … RegisterCallback<ClickEvent>(evt => OnClick(evt));
RegisterCallback<KeyDownEvent>(evt => OnKeydownEvent(evt));
// …
}
static void OnClick(ClickEvent evt) {
var slideToggle = evt.currentTarget as SlideToggle;
slideToggle.ToggleValue();
evt.StopPropagation();
}
static void OnKeydownEvent(KeyDownEvent evt) {
var slideToggle = evt.currentTarget as SlideToggle;
if (slideToggle.panel?.contextType == ContextType.Player) return;
if (evt.keyCode == KeyCode.KeypadEnter || evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.Space) {
slideToggle.ToggleValue();
evt.StopPropagation();
}
}
The label and background color update automatically as the user toggles the switch, providing visual feedback. Here we use SetValueWithoutNotify to update the visual state of the toggle without triggering a ChangeEvent. Since this method is called internally when the value changes, the UI updates correctly without causing an infinite loop of updates. public override void SetValueWithoutNotify(bool newValue) {
base.SetValueWithoutNotify(newValue);
m_Input.EnableInClassList(inputCheckedUssClassName, newValue);
m_Input.style.backgroundColor = newValue ? EnabledBackgroundColor : DisabledBackgroundColor;
labelElement.text = (value) ? EnabledText : DisabledText;
}Explore the sample implementation in the CustomControlsDemo scene. Click the element with the mouse or press the Enter or Space key to toggle its active state. In this sample, the label and background color update dynamically as the user toggles the slide control, with a quick animation providing visual feedback. Use the Inspector to set string labels and background colors that correspond to the enabled and disabled state.
Customize the slide toggle text and colors.
Using the slide toggle Once compiled, the slide toggle is now ready to integrate into any part of your UI. Use the custom SlideToggle class just like any other visual element. Here’s an example implementation that uses the SlideToggle class to mute or unmute the sound:
public class MuteAudioToggle : MonoBehaviour {
[SerializeField] AudioSettingsSO m_AudioSettingsSO;
[SerializeField] UIDocument m_Document;
void OnEnable() {
var root = m_Document.rootVisualElement;
SlideToggle slideToggle = root.Q<SlideToggle>("master-audio-toggle");if (slideToggle != null) {
slideToggle.value = !m_AudioSettingsSO.IsMasterMuted;
slideToggle.RegisterValueChangedCallback(evt => m_AudioSettingsSO.IsMasterMuted = !evt.newValue);
}
}
}In this case, the SlideToggle is part of an existing UXML document. The MonoBehaviour locates it by name within the visual tree and then uses the RegisterValueChangedCallback method to link the toggle state to the audio settings. Since SlideToggle is a standalone custom element, you can use it for any kind of toggle switch in your UI. For example in the Dragon Crashers UI Toolkit sample, a similar SlideToggle enables and disables the fps counter.
The stylized toggle from UI Toolkit Sample – Dragon Crashers
Customize the SlideToggle to fit your application’s requirements – it’s ideal for settings like visuals, sound, or gameplay options. Build it once, then reuse it wherever a custom switch can enhance the user experience. For a full implementation, refer to the SlideToggle.cs script in the QuizU project.
Creating more custom controls If there’s a control that’s not included in the standard UI Toolkit library, you can create your own. Here are just a few examples to get you thinking about how you can deploy custom controls in your own games: —
Health bars/progress bars: Game attributes like health, mana, power, etc. can vary widely based on gameplay, making them great candidates for custom controls. Expose UxmlAttributes like max value, current value, and status colors to add options for color gradients.
Rating stars: This control functions like a segmented progress bar, representing an integer value (e.g. stars for completing a level). Start with a visual element with several child elements that can switch between filled and unfilled states. Expose an int with a max value in the Inspector and allow the user to customize the sprite images with UxmlAttributes.
Tab view control: A tabbed interface is a common UI for switching between different views or sections within the same window. Implement this by creating a custom element with a row of tabs and a content area. Each tab can be a button-like visual element, with options to add or remove tabs dynamically.
Remember that in most cases, you can also trigger USS transitions to add visual flair with animations. With custom controls, your users can pinch, click, scroll, and toggle through your unique game UIs. We can’t wait to see what you make with them.