Разделяем и властвуем над интерфейсами в Unity
Рассказываю как создавать кастомные UI элементы через создание Mesh’а, используя Unity и UI Toolkit.
Что делаем?
В рамках статьи будем разбирать возможности генерации кастомных элементов интерфейса с использованию UI Toolkit.
Сперва будет скурпулезный разбор создание треугольника на основе генерации мэша, а вторым – градиентный ромб!
Глава 1: Mesh и треугольник!
В рамках данной главы, рассмотрим как сделать кастомный элемент треугольника, так же расскажу в целом, как работает эта система.
Поэтому, читаем код и комментарии к нему, чтобы потом смогли дома повторить: ).
Сперва будет приложен код (для самых умных и затейливых), а потом будет подробный разбор большинства моментов, поехали!
Так выглядит весь код, теперь будем разбираться, что к чему!
Начнем с базы, объявления нашего класса – TriangleElement
Для создания кастомного UI элемента используется наследование класса VisualElement. (Есть и другие способы, но сейчас я не буду углубляться в детали).
Мы будем использовать некоторые методы и данные из родительского класса, такие как contentRect и generateVisualElement;
Идем дальше – UxmlFactory и отображение элемента в иерархии:
Это относится к особенностям UI Toolkit, она нужна для того чтобы наш TriangleElement добавился в общий список UI элементов.
После этого, мы сможем его легко перетаскивать на окно верстки.
Более подробно про создание кастомых элементов, можно почитать в документации:
С этим разобрались, идем дальше:
Как вы многие знаете, одноименно классу называется метод который мы называем — конструктором и он вызывается когда создается наш объект.
Конкретно у нас, он подписывается на Action родительского класса (тот самый который упоминался выше)
(Как чеховское ружье, однажды подписавшись, оно обязательно вызовется)
Какой Action родительского класса?
Если кратко, он активируется когда нашему VisualElement'у будет необходимо перегенерировать себя, это как правило происходит, если были изменения в UI элементе (изменение свойств, размеров) или был вызван метод MarkDirtyRepaint()
Также оставлю ссылку на документацию:
Когда мы ловим этот Aciton, мы также получаем MeshGenerationContext и благодаря нему, можем дополнительно рендерить геометрию на элементе.
Mетод возвращает нам значение типа – MeshGenerationContext, его разберем чуть позднее.
Сделаем небольшое отступление и сделаем введение в игровые движки – поговорим про Mesh.
Мэш представляет собой набор вершин, рёбер и полигонов, которые определяют форму и структуру объекта.
Для создания кастомного элемента, мы как раз таки и создаем этот самый мэш, но только на уровне интерфейса и в 2D пространстве.
Дальше будет разбор минимальных данных которые нужны для создания такого объекта:
Это наши бравые слоны, господа «Vertices» и «Indices», далее будем называть их вершинами и индексами.
Vertex это внутрняя структура Unity UI Toolkit, который используется в отрисовки UI элементов, главное для нас это:
Под капотом у нее Vector3 — позиция вершины и Color32 (32 битное представление RGBA) — цветовой оттенок.
Вывод – вершины описывают свое местоположение и какого цвета они будут.
Дальше у нас идут индексы, они отвечают за соединение вершин мэш.
Большинство игровых движков и инструментов 3D моделирования используют подход треугольной сеткой (triangular mesh), когда все объекты состоят из треугольников.
Также и у нас, когда мы говорим, что массив indices заполнен – { 0, 1, 2 },
мы подразумеваем, что мы на выходе получим треугольник который состоит из этих вершины.
Поэтому, минимальное описание фигуры состоит из трех чисел для описания одного треугольника.
Про анатомию мэша и его строение, можно более подробно прочитать здесь:
Теперь спешу вас успокоить, дальше все будет намного проще, это были самые душные и тяжелые в понимание моменты :)
Дальше мы по коду добрались до тела нашего метода, который будет вызван при перегенерации нашего UI элемента:
Держим в голове, что у наших вершин есть поле tint (цвета вершины), мы как раз здесь их и определили, задав всем красный;)
А дальше определим их позиции (кто и где находится):
Начнем с того, что описание позиции вершины происходит через тип Vector3 (где 3, это количество измерений, т. е в нашем случае X, Y, Z).
Для указания позиций, нам нужна теоретческая поддержка:
Любой VisualElement имеет область отрисовки он называется – contentRect.
У него есть свойства высоты и ширины.
Я хочу рисовать свой треугольник всегда в середине этой области, чтобы независимо какой размер у области, у меня треугольник находились по середине.
Именно поэтому я делаю привязку к размеру области отрисовки. Если оно изменится, например произойдет уменьшение/увеличение размера экрана, тогда это вызовет перегенерацию всего UI элемента и соответственно этот метод вызовется еще раз, но уже с обновленными размерами contentRect'а.
Тут начало координат, как вы могли заметить, лежит в левом верхнем углу – позиция { x = 0, y = 0 };
А область нашего contentRect'a, кончается на значениях { x = width, y = height },
что в целом логично, самая краяняя точка по X – равна ширине элемента и Y – высоте.
Что в итоге получилось по вершинам?
- Вершина с индексом 0, находится в координатах { x = 0, y = height }; (левый нижний угол)
- Вершина с индексом 1, находится в координатах { x = width / 2, y = 0 }; (сверху по середине)
- Вершина с индексом 2, находится в координатах { x = width, y = height }; (правый нижний угол)
После всех подготовок значений позиций и цветов, теперь наконец-то нужно зарендерить все это дело!
Тут если кракто, мы говорим, что хотим создать мэш, с определенным количеством вершин и определенным количеством индексов.
Потом через метод SetAllVertices(), обозначаем наши вершины и через SetAllIndices() назначаем наши индексы.
И все, вуаля! Получили треугольник:
Глава 2: Ромб и градиент!
Теперь после простенького треугольника, можем перейти к более интересному ромбу.
Поступим следующим образом, ниже будет код и будет обзор только важных частей, который изменились.
Как видите, у нас изменились vertices и indices. Теперь у нас 4 вершины, как раз столько и нужно для создания прямоугольника, это мы помним из уроков геометрии.
А вот с индексами, давайте разбираться!
Давайте разобьем индексы на триплеты, получится – { 0, 1, 2 } и { 2, 3, 0 }.
Теперь изобразим наши вершины и индексы.
По позициям вершин – ничего нового или сложного там нет, все просто как пять копеек.
А вот что интересно, так это градиент и цвета вершин!
У треугольника все вершины принимали значение Color. red, мы можем это записать в RGBA представление и тогда это будет выглядеть так:
Да, мы можем прокидывать любые цветые значения и оттенки, которые можно описать через RGBA.
А как тогда нам сделать градиент? Для этого ответим на вопрос – кто этот ваш градиент?
Градиент (англ. Gradient) — вид заливки в компьютерной графике, которая по заданным параметрам цвета в ключевых точках рассчитывает промежуточные цвета остальных точек.
Также есть разные виды градиента: круговые, угловые, отражённые.
Мы будем делать линейный градиент, он выглядит вот так:
Теперь вернемся к нашим баранам, разберем наши вершины и цвета:
Вот так будет выглядеть UI элемент:
Каждая вершина окрасилась в свой цвет, а при переходах к другой вершине, происходит смешение цветов и получается тот самый градиент.
Но есть небольшой нюанс – различие цветов в UI Builder и в RunTime, сейчас покажу:
Эта проблема заключается в том, что в RunTimе и в UI Builder используются разные color space.
Это особенно заметно, когда происходит смешения цветов, как у нас в градиенте.
Подтверждения этому нашел на форумах Unity:
Что можно сделать или у нас будет градиент с нюансом?
По-дефолту, после создания проекта используется – Linear color space.
Если поставить Gamma, тогда все станет на свои места и заиграет новыми красками:
Послесловие
Если у вас остались вопросы или не поняли какую-то часть, напишите в комментариях, постараюсь объяснить, что да как:)
Надеюсь, эта статья оказалась интересной и полезной.
Вложил много сил и времени на написании этой статьи, буду благодарен обратной связи, лайкам и репостам.
Ссылка на репозиторий с кодом:
Спасиб за статью!
Оставлю мини-шутку все равно
Рассказываю как создавать кастомные UI элементы через создание Mesh’а, используся Unity и UI Toolkit.Шаг 1: Наследуемся от VisualElement
Шаг 2: Unity выпускает новое соглашение с разработчиками
Шаг 3: Устанавливаем Unreal Engine
...
Шаг 4: Мудохаемся с Unreal Engine
Шаг 5: Возвращаемся в Unity
Хорошая шутка)
UI Toolkit в целом выглядит очень вкусно, особенно если есть опыт работы с вебом. Но они совсем его не обновляют, печаль.
Насколько знаю, они над ним работают, даже выпускают фичи:
https://forum.unity.com/threads/whats-new-in-the-ui-toolkit-documentation.1256697/
Вот уж что не ожидал увидеть на ДТФ, так это гайд по Unity. Да не просто по Unity, а по UI Toolkit. Да не просто по UI Toolkit, а по UI Toolkit для рантайма, а не редактора.
Насколько помню, были уже гайды по Unity, а вот по UI Toolkit, вроде не было, тут да)