С недавних пор в Unity появилась (и еще разрабатывается) новая система для UI. Необходимо её как следует разобрать и решить, чем же она может мне помочь, и как её я могу использовать.
Идея:
Хочу как следует разобраться с UI Elements в Unity и с возможностью их использования в моем проекте. Ну и написать небольшой экскурс с их использованием.
План:
- Научиться работать с UI Elements.
- Оценить их использование в написании инструментария для редактора.
- Оценить их использование на RunTime в приложении.
- Написать несколько примеров возможного использования.
Что это такое?
UI Elements представляют собой новую систему для создания интерфейсов.
Она пришла на замену печально известной IMGUI, с которой более менее работать можно было только при разработке инструментария и custom inspectors. Она откровенно тормозила и имела ряд ограничений. В давние времена это был единственный способ сделать интерфейс, либо нужно было создавать не существующие тогда в движке спрайты и работать с ними.
Благо в последующем появилась встроенная система спрайтов и UI на их основе. И это очень облегчило создание интерфейсов для приложений в RunTime. Но в самом редакторе можно было делать инструментарий все еще только при помощи IMGUI.
Но вот появился UI Elements и он должен облегчить всем жизнь.
Из чего же состоит UI Elements?
Первое и немаловажное - у него появились нормальные стили. Это аналог .css для вэба. В Unity он называется .uss (Unity style sheets) и по сути работает по тем же правилам что и .css. Открывается дверь для Вэб Разработчиков в новый мир игровой индустрии и разработки мультиплатформенных приложений с использованием Unity (во всяком случае пока приоткрывается).
.root {
/* Colors */
--black: rgb(0, 0, 0);
--gray1: rgb(13, 13, 13);
--gray2: rgb(36, 36, 36);
--gray3: rgb(61, 61, 61);
--gray4: rgb(97, 97, 97);
--gray5: rgb(120, 120, 120);
--gray6: rgb(158, 158, 158);
--gray7: rgb(194, 194, 194);
--white: rgb(255, 255, 255);
--env_n: rgb(153, 232, 49);
--env_h: rgb(185, 255, 87);
--env_c: rgb(122, 229, 0);
--env_d: rgb(50, 61, 38);
/* Params */
width: 100%;
height: 100%;
background-color: var(--gray1);
color: var(--gray6);
padding: 2px;
}
.body {
background-color: var(--gray3);
margin: 2px;
border-radius: 3px;
}
.header {
font-size: 18px;
color: var(--env_n);
}
/* текст */
* {
-unity-font: url('/Assets/ArtContent/UI/JetBrainsMono-Regular.ttf');
font-size: 14px;
}
h0 {
font-size: 48px;
}
h1 {
font-size: 36px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 18px;
}
/* кнопки */
.chrono-button {
width: 160px;
height: 40px;
background-color: var(--gray5);
align-items: center;
justify-content: center;
-unity-text-align: middle-center;
border-radius: 3px;
margin: 1px;
color: var(--gray7);
font-size: 18px;
}
.chrono-button:hover {
background-color: var(--gray6);
color: var(--white);
}
.chrono-button:active {
background-color: var(--gray4);
color: var(--white);
}
.chrono-button:disabled {
background-color: var(--gray2);
color: var(--gray4);
}
.environment-button {
background-color: var(--env_n);
color: var(--white);
}
.environment-button:hover {
background-color: var(--env_h);
}
.environment-button:active {
background-color: var(--env_c);
}
.environment-button:disabled {
background-color: var(--env_d);
}
Просто бы расцеловал их всех, как Брежнев, за такие возможности. Давно этого не хватало.
Второе - это верстка в формате XML, точнее .uxml.
Немного абстрактного кода:
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements">
<ui:VisualElement name="root" class="root">
<uie:PropertyField name="Interactable" binding-path="_interactable"/>
<uie:PropertyField name="TargetGraphic" binding-path="_targetGraphic"/>
<ui:VisualElement name="BaseColorBlock"/>
<ui:VisualElement class="body" style="margin-left:15px">
<uie:PropertyField name="AdditionalGraphics" binding-path="_additionalGraphics"/>
<ui:Button name="AddGraphics" class="chrono-button environment-button" text="New"/>
</ui:VisualElement>
<uie:PropertyField name="OnClick" binding-path="_onClick"/>
</ui:VisualElement>
</UXML>
Все довольно-таки гибко и удобно. Разбиваем на необходимые блоки и расставляем элементы как нам хочется. Присваиваем элементам необходимые стили или управляем параметрами стиля прям отсюда. Верстка адаптивная, и если нам надо управлять её поведением при различных соотношений сторон, все это мы делаем либо в стилях либо в uxml.
Элементов очень много, их список можно посмотреть в последних актуальных доках Unity. Для меня это https://docs.unity3d.com/2020.1/Documentation/Manual/UIE-ElementRef.html. Есть некоторые элементы, которые можно добавлять только из кода, такие, как например PopupField, так как он работает уже с необходимыми разработчику типами и списками, которые не сформируешь в uxml.
При разработке CustomEditor и PropertyDrawer есть очень полезный атрибут binding-path. Если в него записать имя сериализуемого поля редактируемого объекта, то они свяжутся вместе и не надо проделывать никаких манипуляций с SerializedProperty и SerializedObject для связки их и сохранения изменений.
И третья часть - это использование этой верстки в коде. Инициализация её, связка с нужными объектами, присвоение колбэков, поиск элементов и добавление к ним новых.
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
[CustomPropertyDrawer(typeof(MyClass))]
public class MyClassDrawerUIE : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty p)
{
//Создаем основной блок
var root = new VisualElement();
//Находим верстку
var uxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>
("Assets/Editor/MyClassUI.uxml");
//Находим стиль
var uss = AssetDatabase.LoadAssetAtPath<StyleSheet>
("Assets/Editor/MyStyle.uss");
//Клонируем дерево элементов из нашей верстки в созданный блок
uxml.CloneTree(root);
//Присваиваем нашему блоку нужный стиль
root.styleSheets.Add(uss);
//Находим в верстке кнопку AddGraphics с надписью New
var button = root.Q<Button>("AddGraphics");
//Вешаем колбэк на нажатие этой кнопки
button.clicked += OnClick;
//Находим в верстке элемент с названием BaseColorBlock
var baseColorBlock = root.Q<VisualElement>("BaseColorBlock");
//Создаем новый элемент Box
var colorBlock = new Box();
//Раскрашиваем этот элемент в зеленый цвет
colorBlock.style.backgroundColor = new StyleColor(Color.green);
//Делаем этот элемент дочерним для ранее найденного BaseColorBlock
baseColorBlock.Add(colorBlock);
//Используем полученный блок для отображения объекта нашего класса
return root;
}
/// <summary>
/// Метод вызываемый по нажатию на кнопку New
/// </summary>
private void OnClick()
{
//Делаем что-то
}
}
Работать с ними довольно-таки просто. Я расписал в коде комментарии, чтобы было максимально понятно как это работает. Добавлю только несколько моментов.
Передавать верстку из uxml в корневой элемент можно несколькими способами. Первый - это как в примере сверху - клонировать дерево:
var root = new VisualElement();
var uxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>
("Assets/Editor/MyClassUI.uxml");
uxml.CloneTree(root);
Второй - это инстанцировать Визуальный элемент из uxml:
var uxml = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>
("Assets/Editor/UI/StyleSystem/ChronoImageStyle.uxml");
var root = uxml.Instantiate();
Поиск элементов в дереве для дальнейшей работы с ними - это вообще красота, столько всего можно с этим сделать.
С колбэками там есть ряд своих правил, но это уже тема для отдельной статьи на будущее.
UI Elements и RunTime:
Тут много написать не получится, так как на момент написания он еще в разработке. Но посмотреть его можно, включив в "Package Manager" "UIElements Runtime". Если его там нет (а его периодически включают и отключают), то просто в Packages/manifest.json необходимо вписать строку
"com.unity.ui.runtime": "0.0.4-preview",
В целом он работает, но не весь. На WebGL так совсем не пашет.
В сцене создаете объект, на него вешаете компонент PanelRenderer, к которому линкуете свои .uxml и .uss, либо формируете свои VisualTreeAsset и StyleSheet, и он их отрисовывает. На RunTime подгрузить верстку и стиль с файла не получится, разве что вместе с AssetBundle заранее сформированные VisualTreeAsset и StyleSheet.
Итого:
UI Elements выглядит как довольно-таки перспективное нововведение. Буду их использовать как минимум для формирования инструментария. А на RunTime придется пока использовать уже старый и проверенный Unity UI.
Вопросы, предложения, что более подробно разобрать - обязательно оставляйте в комментариях.
P.S. Результат использования: