Unity UI Toolkit: Быстрый старт

Всем привет!

В игре может быть множество элементов интерфейса, всплывающих окон и т. д., и когда появится необходимость изменить общий стиль, например цвет кнопки или текста, то придется это менять во всех созданных элементах, если используется старая система UI Canvas — uGUI (IMGUI забудем как страшный сон). Не так давно Unity предоставили новую систему UI Toolkit, вдохновленную веб-технологиями (HTML-CSS vs UXML-USS) и позволяющую изменить цвет, шрифт и другие свойства всех элементов в игре одним движением. Преимуществ много, например можно подключить веб-дизайнера, и он тут быстро освоится.

Как и ожидается при появлении новой технологии, внятная документация отсутствует, статей в интернете мало и информации в них кусочками. Официальную документацию стараются делать, но явно не для людей. А так как мы все ценим свое драгоценное время, когда еще и горят дедлайны, мы хотим быстро и без лишних затрат использовать все удобства в разработке. Целью данной статьи как раз и является помощь в быстром освоении нового подхода Unity в создании интерфейсов, чтобы не отвлекаться на рысканье в потоках бессвязной документации и продолжать реализовывать свою фантазию или фантазию заказчика. Давно не доходили руки до написания статьи, но пора бы уже уронить свою каплю в море.

Для демонстрации основных часто используемых функций займемся созданием окошка с вводом текста, выбором из подгружаемого кодом списка и обработкой нажатия кнопки. Пусть это будет меню кафешки, когда нам захотелось перекусить. Это будет для вас отправной точкой для создания более сложных интерфейсов.

Установка UI Toolkit

Меню Window — Package Manager — Кнопка + — Add package from git URL… — com.unity.ui — Add

Изображение

f20eda68bb37ddcc39623f145a223ad7.jpg

После этого в меню Window появится новый пункт UI Toolkit.

Создание интерфейса

Меню Window — UI Toolkit — UI Builder

Изображение

f59c7069b350cdc8a5dae424aef28617.jpg

Кратко пройдемся по редактору:

  • (1) — Viewport — рабочее пространство, видим что создаем

  • (2) — StyleSheets — перечень классов USS

  • (3) — Hierarchy — иерархия всех элементов на странице

  • (4) — Library — набор стандартных элементов управления

  • (5) — Inspector — визуальное средство редактирования свойств

Начнем с корневого элемента, назовем его Panel:

Изображение

b96b78195cb5d993bc0725daaec3668c.gif

Добавим класс, тут же попросит сохранить:

Изображение

ff973c4cfa20b7a9a437e5c52c70f45b.gif

На видео получилась такая последовательность сохранений:

  1. Сохраняется файл стилей USS.

  2. Сохраняется страница с разметкой UXML.

  3. Опять потребовало сохранить UXML, с первого раза видимо ему непонятно, не знаю как у вас, но у меня так всегда.

  4. Здесь я нажал Ctrl-S для очередной попытки, т.к. в панели Viewport (1) продолжала висеть звездочка рядом с названием файла — это означает, что сохранение не произошло.

Он так себя ведет первый раз, потом в процессе работы просто нажимайте Ctrl-S, чтобы сохранить весь прогресс.

Теперь обязательно сделайте вот что:

  1. Переходим на сцену.

  2. Создаем пустой объект.

  3. Добавим в него компонент UI Document.

Изображение

825286c069ad52d05e05a071e0a25197.jpg
  1. В панели Project — Create — UI Toolkit — Panel Settings Asset.

Изображение

ebfe79d742cc57334bb62a652088b233.jpg
  1. Переносим его в свойство Panel Settings компонента UI Document.

  2. Файл cafe.uxml переносим в свойство Source Asset компонента UI Document.

Изображение

dc9159d574df4421a0695cdf920582e9.jpg
  1. Закрываем панель UI Builder.

  2. Открываем панель UI Builder (уже весело).

Для чего вот это вот все? В панели Viewport (1) появился важный пункт Unity Default Runtime Theme. Он позволяет во время редактирования видеть, как будет выглядеть интерфейс в игре. Зачем они сделали Dark и Light Editor Theme, которые выглядят совсем иначе и не связаны с игрой, только им известно.

Изображение

ce9bb812652a606038111c1786bc167c.jpg

Продолжаем веселье, заново создавая корневой элемент Panel, т.к. он потерялся из-за манипуляций с сохранениями. Не переживайте, потом ничто никуда не потеряется, только первый раз Unity решает покапризничать. Перетаскиваем класс .panel на элемент Panel, после чего окно инспектора должно выглядеть так:

Изображение

6dc40fc3cd320433c6d21249d23a1286.jpg

Видим, что класс добавлен в элемент.

Теперь щелкаем на название класса в окне StyleSheets (2), чтобы менять свойства. Не перепутайте, щелкая на элемент в иерархии (3), иначе вы будете менять свойства только одного элемента, а нам нужно изменять все окна в игре, редактируя класс.

Установим ширину окна, выравниваем его по центру экрана с помощью margin: auto (все как для веб), цвет настроения черный с легкой прозрачностью, и border-radius: 10px.

Изображение

3d036eb9198a14fcfb06f5b91f576eb7.jpg

Добавим заголовок окна. Переносим Label на нашу #Panel и убеждаемся, что она упала дочерним элементом, т.к. тут часто можно промахнуться, и элемент становится соседним, а не вложенным.

Изображение

790e4336f758d8872265dcec676f70f9.jpg

В классе .panel установим цвет текста, чтобы цвет наследовался всем вложенным элементам, тут тоже все как в веб.

Изображение

b93608f63987093deaa061617cb63212.jpg

Добавим класс для заголовка, просто установив шрифт чуть больше, и перетянем его на метку.

Изображение

8e7e02ec654dda9f3cdb8c417969877b.jpg

В инспекторе можете сами пройтись по атрибутам, там все интуитивно понятно. Скажу только сразу, что разметка изначально настроена на технологию flex. Если вы не знакомы с flex, то гугл вам все расскажет. Все правила из веб разработки касаемо flex применимы и здесь.

Также здесь работает и масштабирование как в Canvas. Вначале мы создавали файл настроек Panel Settings Asset, и если на нем щелкнуть в панели Project, то в стандартном инспекторе мы увидим свойства Scale Mode как в Canvas. По умолчанию установлено Constant Physical Size, что для меня лично удобно, я всегда в Canvas переключал на это значение.

Обработчик событий: оживляем кнопку

Зачем нам это все, если мы не сможем это запрограммировать? Добавим обработчик на кнопку.

Добавим кнопку в наше окошко, сохраним и перейдем на сцену. Создаем скрипт с любым названием, например CafeUI. Ранее мы добавили пустой объект с компонентом UI Document. На него и вешаем скрипт. Переходим в редактор кода, например Visual Studio.

Unity рекомендует обрабатывать элементы страницы в методе OnEnable (), так и сделаем. В листинге ниже комментарии все объясняют.

CafeUI.cs

using UnityEngine;
using UnityEngine.UIElements; //ссылка на библиотеку для работы с UI Toolkit

public class CafeUI : MonoBehaviour
{
    Button okButton;

    void OnEnable()
    {
        //Получаем ссылку на компонент UIDocument
        var uiDocument = GetComponent();
        //Находим кнопку таким запросом, в параметр передаем имя кнопки
        okButton = uiDocument.rootVisualElement.Q

Единственного удобства uGUI в перетаскивании методов на кнопку тут нет, но и такой способ особо не напрягает.

Список товаров, связывание данных (ListView и Binding)

В кафе должен быть список товаров, и мы, конечно же, хотим формировать его динамически во время выполнения. Источник данных может быть любой, но в данном случае для простоты мы будем генерировать список в коде, чтобы отобразить его в окне.

Кидаем на страницу в UI Builder элемент ListView, установим ему атрибуты fixed-item-height="50" style="height: 120px; margin: 10px;". В панели Project создаем новый UXML-документ (Create — UI Toolkit — UI Document), задаем ему имя item.uxml и открываем его двойным щелчком или клавишей Enter. Это будет шаблон элементов списка. Он будет состоять из картинки, названия товара и цены. Должно выглядеть это так:

Изображение

9177cd1a44fa114f221a13c9c14febfb.jpg

Внизу в коде uxml видно, какие атрибуты выставлены элементам. Здесь можно обойтись без uss-стилей, т.к. других окон у нас не будет, но если у вас должно быть несколько окон со списками, то удобнее будет установить единый стиль для списков в одном uss-файле. Цвет текста будет наследоваться от класса .panel. Корневому элементу установлено расположение вложенных элементов в строку и выравнивание посередине. Картинка 50 px на 50 px, название товара занимает всю оставшуюся область (flex-grow: 1), и для цены установлена фиксированная ширина, иначе весь список будет кривой, т.к. цена может быть разной.

Далее нужно создать класс, который будет представлять данные о товаре:

Item.cs

public class Item
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Теперь самое сложное. В официальной документации предоставили довольно сложноватый пример, состоящий из нескольких классов и запутанного кода. Я же не люблю плодить много файлов и классов, и максимально упростил пример, чтобы сосредоточиться только лишь на связывании данных. Сделаем все по порядку. Нумерация пунктов соответствует нумерации в комментариях кода.

  1. Инициализируем список товаров. Здесь для простоты создаем список из двух товаров, в реальной игре же источник данных может быть любой — база данных, api-запросы, файлы и т.д.

  2. Находим отображаемый список на странице. Это ListView с именем list на главной странице cafe.uxml.

  3. Связываем шаблон элементов списка с самим списком.

  4. Связываем данные с отображаемым списком.

  5. Регистрируем событие выбора из списка.

  6. Переносим шаблон item.uxml на переменную itemsListTemplate в инспекторе

Изображение

86ee711a597196734fed572f2578bb69.jpg
  1. Стилизуем список. Тут пара хитростей. В UI Toolkit есть встроенные классы для каждого контрола, их очень много и можно посмотреть по ссылке https://docs.unity3d.com/2021.3/Documentation/Manual/UIE-ElementRef.html

  • изменим цвет наведения мыши на элемент списка: .unity-list-view__item:hover { background-color: rgba(0, 0, 0, 0.2); }

  • изменим цвет выбранного элемента; второй селектор тут добавлен, чтобы при наведении мыши не менялся цвет выбранного элемента: .unity-collection-view__item--selected, .unity-collection-view__item--selected:hover { background-color: rgba(255, 255, 255, 0.2); }

Эти селекторы можно добавить прямо в файл index.uss.

CafeUI.cs

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UIElements; //ссылка на библиотеку для работы с UI Toolkit

public class CafeUI : MonoBehaviour
{
    Button okButton;

    public VisualTreeAsset itemsListTemplate;   //uxml-шаблон элементов списка
    List items = new List();        //список товаров
    ListView itemsListView;                     //список на странице

    void OnEnable()
    {
        //Получаем ссылку на компонент UIDocument
        var uiDocument = GetComponent();
        //Находим кнопку таким запросом, в параметр передаем имя кнопки
        okButton = uiDocument.rootVisualElement.Q