Всем привет!
В игре может быть множество элементов интерфейса, всплывающих окон и т. д., и когда появится необходимость изменить общий стиль, например цвет кнопки или текста, то придется это менять во всех созданных элементах, если используется старая система 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
Изображение
После этого в меню Window появится новый пункт UI Toolkit.
Создание интерфейса
Меню Window - UI Toolkit - UI Builder
Изображение
Кратко пройдемся по редактору:
(1) - Viewport - рабочее пространство, видим что создаем
(2) - StyleSheets - перечень классов USS
(3) - Hierarchy - иерархия всех элементов на странице
(4) - Library - набор стандартных элементов управления
(5) - Inspector - визуальное средство редактирования свойств
Начнем с корневого элемента, назовем его Panel:
Изображение
Добавим класс, тут же попросит сохранить:
Изображение
На видео получилась такая последовательность сохранений:
Сохраняется файл стилей USS.
Сохраняется страница с разметкой UXML.
Опять потребовало сохранить UXML, с первого раза видимо ему непонятно, не знаю как у вас, но у меня так всегда.
Здесь я нажал Ctrl-S для очередной попытки, т.к. в панели Viewport (1) продолжала висеть звездочка рядом с названием файла - это означает, что сохранение не произошло.
Он так себя ведет первый раз, потом в процессе работы просто нажимайте Ctrl-S, чтобы сохранить весь прогресс.
Теперь обязательно сделайте вот что:
Переходим на сцену.
Создаем пустой объект.
Добавим в него компонент UI Document.
Изображение
В панели Project - Create - UI Toolkit - Panel Settings Asset.
Изображение
Переносим его в свойство Panel Settings компонента UI Document.
Файл cafe.uxml переносим в свойство Source Asset компонента UI Document.
Изображение
Закрываем панель UI Builder.
Открываем панель UI Builder (уже весело).
Для чего вот это вот все? В панели Viewport (1) появился важный пункт Unity Default Runtime Theme. Он позволяет во время редактирования видеть, как будет выглядеть интерфейс в игре. Зачем они сделали Dark и Light Editor Theme, которые выглядят совсем иначе и не связаны с игрой, только им известно.
Изображение
Продолжаем веселье, заново создавая корневой элемент Panel, т.к. он потерялся из-за манипуляций с сохранениями. Не переживайте, потом ничто никуда не потеряется, только первый раз Unity решает покапризничать. Перетаскиваем класс .panel на элемент Panel, после чего окно инспектора должно выглядеть так:
Изображение
Видим, что класс добавлен в элемент.
Теперь щелкаем на название класса в окне StyleSheets (2), чтобы менять свойства. Не перепутайте, щелкая на элемент в иерархии (3), иначе вы будете менять свойства только одного элемента, а нам нужно изменять все окна в игре, редактируя класс.
Установим ширину окна, выравниваем его по центру экрана с помощью margin: auto
(все как для веб), цвет настроения черный с легкой прозрачностью, и border-radius: 10px
.
Изображение
Добавим заголовок окна. Переносим Label на нашу #Panel и убеждаемся, что она упала дочерним элементом, т.к. тут часто можно промахнуться, и элемент становится соседним, а не вложенным.
Изображение
В классе .panel установим цвет текста, чтобы цвет наследовался всем вложенным элементам, тут тоже все как в веб.
Изображение
Добавим класс для заголовка, просто установив шрифт чуть больше, и перетянем его на метку.
Изображение
В инспекторе можете сами пройтись по атрибутам, там все интуитивно понятно. Скажу только сразу, что разметка изначально настроена на технологию 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<UIDocument>();
//Находим кнопку таким запросом, в параметр передаем имя кнопки
okButton = uiDocument.rootVisualElement.Q<Button>("okButton");
//Регистрируем событие нажатия кнопки
okButton.RegisterCallback<ClickEvent>(ClickMessage);
}
void ClickMessage(ClickEvent e)
{
//Реализуем тут любые действия при нажатии кнопки
Debug.Log("ok");
}
}
Единственного удобства 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. Это будет шаблон элементов списка. Он будет состоять из картинки, названия товара и цены. Должно выглядеть это так:
Изображение
Внизу в коде uxml видно, какие атрибуты выставлены элементам. Здесь можно обойтись без uss-стилей, т.к. других окон у нас не будет, но если у вас должно быть несколько окон со списками, то удобнее будет установить единый стиль для списков в одном uss-файле. Цвет текста будет наследоваться от класса .panel. Корневому элементу установлено расположение вложенных элементов в строку и выравнивание посередине. Картинка 50px на 50px, название товара занимает всю оставшуюся область (flex-grow: 1
), и для цены установлена фиксированная ширина, иначе весь список будет кривой, т.к. цена может быть разной.
Далее нужно создать класс, который будет представлять данные о товаре:
Item.cs
public class Item
{
public string Name { get; set; }
public decimal Price { get; set; }
}
Теперь самое сложное. В официальной документации предоставили довольно сложноватый пример, состоящий из нескольких классов и запутанного кода. Я же не люблю плодить много файлов и классов, и максимально упростил пример, чтобы сосредоточиться только лишь на связывании данных. Сделаем все по порядку. Нумерация пунктов соответствует нумерации в комментариях кода.
Инициализируем список товаров. Здесь для простоты создаем список из двух товаров, в реальной игре же источник данных может быть любой - база данных, api-запросы, файлы и т.д.
Находим отображаемый список на странице. Это ListView с именем
list
на главной странице cafe.uxml.Связываем шаблон элементов списка с самим списком.
Связываем данные с отображаемым списком.
Регистрируем событие выбора из списка.
Переносим шаблон item.uxml на переменную
itemsListTemplate
в инспекторе
Изображение
Стилизуем список. Тут пара хитростей. В 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<Item> items = new List<Item>(); //список товаров
ListView itemsListView; //список на странице
void OnEnable()
{
//Получаем ссылку на компонент UIDocument
var uiDocument = GetComponent<UIDocument>();
//Находим кнопку таким запросом, в параметр передаем имя кнопки
okButton = uiDocument.rootVisualElement.Q<Button>("okButton");
//Регистрируем событие нажатия кнопки
okButton.RegisterCallback<ClickEvent>(ClickMessage);
//Работаем со списком
//1. Инициализируем список товаров
items.Add(new Item { Name = "Пирожок", Price = 40 });
items.Add(new Item { Name = "Мороженое", Price = 60 });
//2. Находим отображаемый список на странице
itemsListView = uiDocument.rootVisualElement.Q<ListView>("list");
//3. Связываем шаблон элементов списка с самим списком
itemsListView.makeItem = () =>
{
return itemsListTemplate.Instantiate();
};
//4. Связываем данные с отображаемым списком
itemsListView.bindItem = (_item, _index) =>
{
//Связываем список товаров по индексу
var item = items[_index];
//Получаем доступ к визуальным элементам шаблона по именам, которые мы указали в шаблоне
_item.Q<Label>("name").text = item.Name;
_item.Q<Label>("price").text = $"{item.Price} руб";
//В данном случае картинка товара должна лежать в папке Resources и иметь название такое же, как название товара
_item.Q<VisualElement>("image").style.backgroundImage = Resources.Load<Texture2D>(item.Name);
};
//Здесь все стандартно
itemsListView.itemsSource = items;
//5. Регистрируем событие выбора из списка
itemsListView.onSelectionChange += ItemsListView_onSelectionChange;
}
private void ItemsListView_onSelectionChange(IEnumerable<object> obj)
{
if (obj.Count() > 0)
{
var item = obj.First() as Item;
Debug.Log($"{item.Name}: {item.Price} руб");
}
}
void ClickMessage(ClickEvent e)
{
//Реализуем тут любые действия при нажатии кнопки
Debug.Log("ok");
}
}
Результат работы
USS как CSS
USS максимально схож с CSS, благодаря чему знакомый с веб-разработкой будет чувствовать себя здесь в своей стихии. В вашем распоряжении все возможности из мира CSS:
Использование переменных. Например:
Hidden text
:root {
--general-bg: #4D4D4D;
}
Button {
background-color: var(--general-bg);
}
Вложенность и комбинации селекторов (
selector1 selector2
;selector1 > selector2
;selector1, selector2
...)transform, translate, transition...
И т.д. и т.п. Все возможности перечислены в документации
Полезные ссылки
Комментарии (8)
Ka33yC
26.12.2022 17:47+1Как жаль, что юнитеки занимаются изобретением велосипедов никому не нужных. А нам с этим работать
ScratchUA
27.12.2022 06:51В игре может быть множество элементов интерфейса, всплывающих окон и т. д., и когда появится необходимость изменить общий стиль, например цвет кнопки или текста, то придется это менять во всех созданных элементах, если используется старая система UI Canvas - uGUI
Ну да, ну да, префабы и варианты префабов используют же только слабаки. UI Toolkit - очередная бесполезная блажь от Unity Technologies, которая мало кому интересна. Бесплатный UI Toolkit Sample – Dragon Crashers - полноценный мобильный UI на UI Toolkit - собрал аж 8 оценок и отзывов за 3,5 месяца (на данный момент) с момента публикации в Asset Store. Настолько высок интерес к продукту...
Ну и, как обычно, оно часто ломается от версии к версии. В 2022.2 сломали ширину PropertyFields и разрабы, вместо того, чтобы пофиксить баг, присоветовали знакомому разработчику весьма популярного ассета добавить класс .unity-base-field__aligned во все контролы. В упомянутом ассете, все инспекторы свойств созданы при помощи UI Toolkit и контролов там далеко за пару сотен, а то и больше.
Переходить на UI Toolkit для создания игрового UI, как по мне, это не самая лучшая идея, пока идеально работает связка UGUI + Modern UI Pack, а вот для создания инспекторов и сервисных UI для Unity Editor подходит неплохо.alexxxproof Автор
28.12.2022 12:52В окнах обычно разные наборы элементов, и тогда будет сотня префабов вместо сотни окон. После перехода на UI Toolkit стало намного проще и интереснее делать интерфейсы.
Интерес невысок, потому что много скептиков, да и я долго обходил его стороной, пока не появился вопрос от заказчика - а что если потом я захочу поменять цвет интерфейса?
В USS много разных селекторов и комбинаций, а не только название встроенного класса, чтобы что-то поменять.
ScratchUA
28.12.2022 21:04Вы неправильно меня поняли. Я говорю о префабах не окон, а конкретных uGUI контролов: кнопок, текстовых полей, переключателей, слайдеров и т.д. В Unity есть вариации префабов, а совсем недавно появились еще и вариации материалов и вам не нужно создавать множество материалов с идентичными параметрами, которые отличаются, например, только цветом. Достаточно создать базовый прототип материала с определенными настройками Diffusion / Normal / Metallic / Height и т.д. и потом создавать вариации материалов, в которых меняется только цвет Diffusion.
Извиняюсь за прямоту, но, по всей видимости, у вас было совсем немного практики в создании UI на uGUI, если задача по смене цвета интерфейса вызвала у вас необходимость переходить на UI Toolkit. USS и UXML я изучал с самых первых версий, так что про комбинации селекторов я знаю. Но в итоге побаловался, ощутил непередаваемую мощь привнесённых Web-технологий, и вернулся к uGUI. Хотя безработным верстальщикам-формошлёпам, стремящимся войти в геймдев, вполне себе зайдет. Осталось еще только вернуть Unity Script (специфический Java Script для Unity), благополучно выпиленный в Unity 2018 и будет полное счастье.alexxxproof Автор
28.12.2022 21:12Получается, что тут уже речь идет о личных предпочтениях, а не о работоспособности технологии, а о вкусах не спорят. От себя скажу, что работает UI Toolkit прекрасно, я продолжаю сейчас с ним работать, все задачи выполняются в короткий срок и никаких нареканий нет.
bustedbunny
28.12.2022 12:38Зачем они сделали Dark и Light Editor Theme, которые выглядят совсем иначе и не связаны с игрой, только им известно.
Потому что UI Builder также используется для создания интерфейса для Editor.
ElenaSotnikova
Очень интересная статья, хоть я игры и не разрабатываю ))) очень подробная и наглядная. Даже захотелось попробовать окунуться в этот мир разработки
alexxxproof Автор
Попробуйте, не пожалеете, это очень интересно и увлекательно. Я вот жалею, что раньше не занялся разработкой игр