Создание интерфейса (UI) под разные разрешения экрана
Современные игры и приложения часто нуждаются в поддержке широкого спектра различных разрешений экрана, и особенно в данной возможности нуждаются интерфейсы этих игр. Система создания интерфейсов в Unity снабжена рядом различных инструментов для реализации этих возможностей, которые также можно комбинировать между собой массой различных способов.
В данном руководстве о том “как это делается”, мы собираемся использовать простой пример, чтобы взглянуть и сравнить между собой различные инструменты в контексте данного вопроса. В нашем случае как показано ниже, у нас по углам имеется три кнопки и нашей целью является адаптировать данную компоновку под разные разрешения.

Для данного руководства о том “как это делается” мы решили использовать четыре разрешения экрана: Phone HD в портретной ориентации (640 x 960) и альбомной (960 x 640) и Phone SD также в портретной (320 x 480) и альбомной (480 x 320). Изначально компоновка была настроена под Phone HD портретную ориентацию и разрешение.
Using anchors to adapt to different aspect ratios
Элементы интерфейса по-умолчанию привязаны к центру родительского прямоугольника. Данное означает что они сохраняют постоянное смещение относительно центра.
Если с данной настройкой разрешение было изменено под альбомное соотношение сторон, кнопки могут выпасть из своих прямоугольных областей, где они изначально должны были быть расположены.

Одним из способов сохранить расположение кнопок в области экрана является изменение компоновки таким образом, чтобы места их расположения были связаны с их соответствующими углами на экране. Привязка левой верхней кнопки может быть также установлена в левом верхнем углу при использовании в инспекторе выпадающего списка Anchors Preset (наборы привязок), или путём перетаскивания треугольных ручек привязок в видовом окне сцены (Scene View). Лучше сделать это пока текущее разрешение экрана, установленное в игровом режиме (Game View) является тем разрешением, для которого изначально всё и было задумано, где места расположения кнопок были бы подобраны более разумно и как говориться к месту.(Ознакомьтесь со страницей UI Basic Layout для получения более подробной информации по привязкам.). Так же например привязки для левой нижней и правой нижней кнопок могут быть выставлены в левый нижний и правый нижний угол соответственно.
Как только кнопки были привязаны к своим углам, то при дальнейших изменениях разрешений экрана и соотношений сторон они будут сохранять свою позиции относительно этих углов.

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

В данном руководстве о том “как это делается” мы уже знаем о том, что небольшая разрешающая способность портретной и альбомной ориентации Phone HD устройств не соответствует экранам, которые физически обладают меньшим по сравнению с ними размером, в то время как это самое соответствие больше выражается в плотности пикселей на 1 дюйм самого экрана. На этих экранах меньшей плотности кнопки не должны отображаться крупнее чем на экранах устройств с большей плотностью пикселей — они должны быть точно такого же размера, ни больше, не меньше.
Данное означает что кнопки должны становиться меньше настолько же, насколько в процентном соотношении становиться меньше сам экран. Другими словами, масштаб кнопок должен быть привязан к масштабу размеров экрана. Вот где может помочь компонент Reference Resolution
Масштабирование с компонентом Screen Size
Компонент Canvas Scaler может быть добавлен в корень Canvas — игровой объект (Game Object) со встроенным в него компонентом Canvas, все интерфейсные элементы которого являются его потомками. Он также создаётся по-умолчанию во время создания нового компонента Canvas через меню GameObject.
В компоненте Canvas Scaler вы можете установить его UI Scale Mode в Scale With Screen Size. В данном режиме масштабирования вы можете определить какое разрешение использовать в качестве базового. Если текущее разрешение больше или меньше базового, фактор масштабирования компонента Canvas устанавливается соответственно так, чтобы все элементы интерфейса масштабировались в большую или меньшую сторону вместе с разрешением экрана.
In our case, we set the Canvas Scaler to be the Phone HD portrait resolution of 640 x 960. Now, when setting the screen resolution to the Phone SD portrait resolution of 320 x 480, the entire layout is scaled down so it appears proportionally the same as in full resolution. Everything is scaled down: The button sizes, their distances to the edges of the screen, the button graphics, and the text elements. This means that the layout will appear the same in the Phone SD portrait resolution as in Phone HD portrait; only with a lower pixel density.

Чего стоит опасаться: так это того, что после добавления компонента Reference Resolution, важно также проверять как будет выглядеть компоновка с другими соотношениями сторон. Установив разрешение обратно в Phone HD альбомное, можно заметить как кнопки стали больше, чем должны быть (и для чего должны были быть использованы).

Причина, по которой кнопки при альбомном соотношении сторон становятся больше кроется в том, как работают настройки базового разрешения (Reference Resolution). По-умолчанию они сравнивают ширину текущего разрешения с шириной базового и как результат всё на экране масштабируется основываясь на коэффициенте масштабирования, получаемом из этой разницы. Если текущее альбомное разрешение равное 960 x 640 превосходит в 1.5 раза ширину портретного базового разрешения равного 640 x 960, то вся компоновка в целом будет увеличена в 1.5 раза.
Компонент имеет свойство под названием Match, которое может принять значение равное 0 (ширина), 1 (высота) или любое значение лежащее в пределах между 0 и 1. По-умолчанию оно установлено в 0, что означает то, что текущая ширина экрана соответствует базовой ширине базового разрешения, о котором говорилось ранее.
Если свойство Match имеет значение не равное 0.5, оно будет сравнивать текущую ширину с базовой шириной, текущую высоту с базовой высотой, и выберет коэффициент масштаба близкий и к тому и к другому разрешению.
At this point the layout supports all the four screen resolutions using a combination of appropriate anchoring and the Canvas Scaler component on the Canvas.

Для более подробной информации о том, какими ещё способами можно добиться масштабирования элементов интерфейса относительно разных разрешений экрана, посетите страницу документации Canvas Scaler.
Unity UI Toolkit
Расширьте возможности редактора, добавив в него пользовательские интерфейсы для интуитивно понятной разработки. Облегчайте совместную работу разных команд, создавая потрясающие интерфейсы для игр и приложений.
Просмотреть UI Toolkit версии LTS 2021 можно в Unity Hub или на странице загрузки LTS. Попробовать
Что нового в UI Toolkit
UI Toolkit содержит функции, ресурсы и инструменты для разработки пользовательских интерфейсов и расширений редактора. В версии LTS 2021 этот инструмент поддерживает создание и отладку интерфейса для среды выполнения в играх и приложениях с интуитивно понятным рабочим процессом. Это помогает художникам и дизайнерам быстрее приступать к работе.

Основные преимущества
Узнайте о функциях и возможностях UI Toolkit.
- Специальные инструменты для разработки
- Рендеринг динамического текста
- Анимация интерфейса
- Рендеринг без использования текстур
- Настраивайте интерфейс и редактор

Начните работу с нашими специальными инструментами для разработки
UI Toolkit включает специальные инструменты для разработки, такие как UI Builder и UI Debugger. Они создавались по примеру аналогичных веб-технологий. Инструмент UI Builder подходит для визуального проектирования и редактирования пользовательских интерфейсов игр и приложений прямо в Unity, а UI Debugger помогает быстро устранять проблемы.

Рендеринг динамического текста
TextMesh Pro — технология рендеринга шрифтов. Она была интегрирована в UI Toolkit, чтобы разработчики могли выполнять это действие без дополнительных плагинов и пакетов. Вам доступны расширенные возможности стилизации. Вы можете добавлять аккуратный и красивый текст с любым размером и разрешением.

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

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

Разрабатывайте пользовательские интерфейсы и расширения для редактора Unity
UI Toolkit включает в себя структуру интерфейса с сохраненным режимом, которая облегчает разработку пользовательского интерфейса для расширений редактора Unity. Поэтому вы можете создавать уникальные окна и инспекторы для упрощения рабочих процессов художников, дизайнеров и программистов.
Другие преимущества
Быстрые итерации
Работайте и выполняйте итерацию быстрее благодаря глобальному управлению стилями и возможности творить в реальном времени.
Знакомые процессы
Инструменты разработки интерфейса и рабочие процессы основываются на стандартных веб-технологиях, благодаря чему к ним проще привыкнуть.
Улучшенная командная работа
Логика в коде C#, структура интерфейса в документах UXML и возможность редактировать стили в таблицах упрощают работу с различными частями интерфейса и позволяют избежать конфликтов.
Эффективность рендеринга
Получите больший контроль над производительностью игры с помощью подсказок по рендерингу и динамического атласа текстур.
Плодотворное сотрудничество
UI Toolkit можно использовать вместе с IMGUI для редактора и UGUI для времени выполнения. Инструмент также совместим с новой системой ввода Unity.
Повторное использование
Делитесь стилями и виджетами внутри одного или нескольких проектов, а также в редакторе и среде выполнения.
Сравните системы Unity для работы с интерфейсом
Хотя Unity рекомендует UI Toolkit для новых проектов по разработке интерфейса, все же есть случаи, когда имеет смысл использовать более старые системы — Unity UI (uGUI) и IMGUI. Сравните разные системы для работы с интерфейсом, чтобы выбрать наиболее подходящую.
Начните использовать UI Toolkit уже сегодня
Получите доступ к UI Toolkit LTS 2021 из Unity Hub или со страницы загрузки LTS, чтобы начать использовать интуитивно понятные инструменты разработки и совместные рабочие процессы в своих проектах.
Ресурсы

Блог
Узнайте, как начать работу с UI Toolkit, из этого пошагового руководства.

QuizU
A UI Toolkit sample
QuizU is an official Unity sample demonstrating various design patterns and project architecture including MVP, state pattern, managing menu screens, and much more using UI Toolkit.

Документация
Ознакомьтесь с руководством от Unity, чтобы узнать больше об UI Toolkit.

Сообщество
Примите участие в обсуждениях на форуме сообщества и следите за новостями UI Toolkit в Unity.
Часто задаваемые вопросы
В каких случаях стоит использовать UI Toolkit и Unity UI?
Инструмент UI Toolkit рекомендуется в качестве альтернативы Unity UI. Он позволяет создавать интерфейсы наложения, совместимые с самыми разными разрешениями экрана. Он подойдет командам, которые работают со значительным количеством интерфейсов, хотят внедрить привычные рабочие процессы для художников и дизайнеров и/или выполнять рендеринг интерфейса без использования текстур.
Unity UI по умолчанию рекомендуется для пользователей, которые хотят создать интерфейс, расположенный и освещенный в трехмерном мире, а также VFX с пользовательскими шейдерами, материалами и простыми ссылками из классов MonoBehaviour.
Можно ли использовать UI Toolkit с Unity UI?
Инструменты UI Toolkit и Unity UI совместимы между собой, поэтому вы можете переносить существующие проекты без необходимости переписывать весь интерфейс. Это означает, что вы можете воссоздать некоторые части существующего проекта с помощью UI Toolkit, оставив другие части в Unity UI, — и они будут работать вместе.
Можно ли использовать UI Toolkit для создания интерфейсов в системе World Space?
UI Toolkit в настоящее время не поддерживает элементы интерфейса, которые размещаются или освещаются в трехмерном мире. Однако можно сделать так, чтобы элементы интерфейса на экране следовали за объектами в системе World Space.
Можно ли использовать UI Toolkit с DOTS?
Вы можете взаимодействовать с UI Toolkit из систем DOTS в основном потоке, как и с другими встроенными функциями движка.
UI Toolkit предоставляет возможность связывания данных?
UI Toolkit пока не предоставляет решения для связывания данных. Значения элементов интерфейса нужно прописывать в коде C#. Мы добавим эту возможность в следующем крупном выпуске UI Toolkit.
Совместим ли инструмент UI Toolkit с экспериментальной версией пакета векторной графики?
Да. Несмотря на то, что экспериментальные версии пакетов официально не поддерживаются Unity и потому не рекомендуются для использования в производстве, UI Toolkit можно использовать с ассетами SVG (если у вас установлен пакет векторной графики).
Как начать использовать UI Toolkit?
Инструмент UI Toolkit доступен в рамках выпуска LTS 2021. Получить доступ к UI Toolkit можно в Unity Hub или на странице загрузки.
Как будет меняться UI Toolkit в будущем?
Мы создали UI Toolkit, чтобы предоставить единое решение для создания интерфейсов, которое будет соответствовать всем потребностям разработчиков и дизайнеров, работающих с инструментами редактора или интерактивными функциями.
Вы можете ознакомиться с планами развития платформы Unity и при желании предоставить обратную связь нашей команде.
Создание архитектуры UI на Unity
Разработчик Янко Оливейра (Yanko Oliveira) поделился в блоге на Gamasutra своим опытом и советами по выстраиванию архитектуры пользовательского интерфейса для мобильных игр на Unity.
Предисловие
При разработке мобильного проекта, особенно F2P, не обойтись без метаигры, которая чаще всего гораздо сложнее основной геймплейной петли. А это значит много работы над пользовательским интерфейсом.
UI мало кто любит заниматься, поэтому разработчики часто выбирают уже существующие архитектуры и упрямо защищают их в жарких спорах. На деле нет какой-то идеальной архитектуры, подходящей для всех.
В этом материале я изложу один способ разработки интерфейса, который хорошо работает при создании систем средней и высокой сложности, и был проверен на нескольких выпущенных играх.
Если вкратце, эта архитектура — нечто вроде менеджера окон с историей и управлением потоком данных. Её легко адаптировать под конкретный проект.
Немного о UI на Unity
Он неплох, но иногда может быть ужасным
Многие необходимые вещи уже встроены в Unity, поэтому разработка интерфейса в нём — довольно простая задача, если понимаешь, как что работает. Однако некоторые сомнительные решения и небольшие детали могут вызывать фрустрацию.
Иногда придётся создавать свои компоненты интерфейса. При этом нужно помнить о целесообразности этих действий — возможно, то, что вам нужно, уже есть на движке.
Когда пишете код, создавайте новое
Расширить базовые классы UI и добавить дополнительные функции существующим компонентам — лёгкий путь, но опасный. Есть большая вероятность, что в результате придётся делать больше работы, чем если бы вы создали новые компоненты и классы.
От холстов зависит, какие части интерфейса нужно обновить
Когда вы что-то меняете в иерархии, весь меш обновляется. То есть, если весь интерфейс — это один холст (canvas), и в одном маленьком текстовом поле каждый кадр меняется число, то весь холст будет заново отстраиваться каждый кадр.
В идеале нужно иметь все статичные элементы на одном холсте, а динамические — на другом, но чаще всего это невозможно. Нужно искать баланс между оптимизацией и временем, затраченным на работу.
Используйте якоря вместо абсолютного позиционирования
Отзывчивый интерфейс легче адаптировать для разных разрешений. Также не забудьте выставить референсное разрешение в CanvasScaler, иначе потом придётся всё перестраивать. Я использую 1920*1080.
Используйте transform.SetParent(parent, false) вместо transform.parent
Это распространённая ошибка. Её симптом — элементы выглядят нормально, когда вы передвигаете их в редакторе, но меняют положение при рендеринге.
Глоссарий
Экран (screen) — отдельная замкнутая часть интерфейса. Они бывают двух видов: панели (panels) и диалоги (dialogs).
Одновременно могут быть открыты несколько панелей и диалогов, но только один диалог может быть интерактивным в единицу времени. Панели могут быть только открыты или закрыты, а диалоги обладают историей.
Виджет (widget) — часть экрана, которую можно использовать несколько раз.
Слой (layer) — то, в чём находится и чем контролируется экран.
- Диалог выбора уровня.
- Панель, отображающая количество ресурсов пользователя.
- Навигационная панель.
- Виджет. Значения в нём могут динамически меняться, и могут быть использованы в других элементах (к примеру, в диалоге с достижениями).
Иерархия
Вот приблизительное устройство иерархии элементов интерфейса (в скобках перечислено, что в них обычно содержится).
UI [код UI Manager, основной холст]
— Камера
— Слой диалога (код слоя диалога)
— — Диалог А (контроллер диалога А)
— — Диалог B (контроллер диалога B)
— Слой панелей (код слоя панелей)
— — Панель A (контроллер панели A)
— — Панель B (контроллер панели B)
Unity отрисовывает элементы строго по иерархии, то есть, нижние элементы рендерятся в последнюю очередь. В этом примере панели всегда будут расположены поверх диалогов.
Во время настройки основного холста используйте Screenspace — Camera, а не Screenspace — Overlay. Она работает точно так же, и можно добавлять вещи вроде трёхмерных моделей, систем частиц и даже эффектов постобработки.
Организация экранов и виджетов
Префабы (prefab) очень помогают. Я обычно делаю по одному префабу для экранов и по несколько для виджетов. Что бы вы ни делали, старайтесь разделять элементы на как можно большее количество префабов.
В идеале ваш художник интерфейса должен работать в Unity. Так вы сможете сосредоточиться на программировании, вместо того, чтобы пытаться перенести творения художника в игру. Если он не знает движка, постарайтесь его научить.
Также можно сделать редактор для художника, но это нужно делать вместе с ним, потому что это ему работать в программе.
Спрайты лучше всегда переводить в атласы (atlas). Я пользуюсь встроенным Sprite Packer и складываю все спрайты для одного атласа в отдельную папку, чтобы все они попадали в Asset Bundle.
Также рекомендую работать над заготовками в сторонних программах как можно дольше, прежде чем запускать Unity, используя Balsamiq, Axure, InVision или Origami Studio, в зависимости от предпочтений художника.
Когда заготовки сделаны и одобрены, художник делает префаб экрана, который программисты добавляют в игру. Если времени на весь рабочий цикл нет, программисты могут делать собственные версии экранов, чтобы добавить нужные функции, а потом менять их на одобренные префабы.
Архитектура кода
Код системы интерфейса разделён на три части: главный фасад, контроллеры слоёв и контроллеры экранов. В моём примере иерархии было два слоя, в реальности их почти всегда больше. Можно каждый раз создавать новый слой, но я пользуюсь пара-слоями (para-layers): дополнительные объекты в иерархии, к которым код слоя привяжет все изменения экрана (Screen Transforms).
Все контроллеры слоёв — это вариации AUILayerController. У этого класса есть код для показа и скрытия экранов, и он общается с самими экранами. PanelLayer просто указывает, что он управляет панелями, и отправляет их к пара-слоям. DialogueLayer контролирует историю и очерёдность диалогов. Вот код базового класса.
Каждому экрану можно добавить параметр Properties. У этих параметров класс [System.Serializable], так что их можно менять прямо в префабе. Экран появляется при регистрации, когда префаб привязывается к слою. Экран привязан к ScreenID.
Вот отрывок из AUIScreenController.
UI Manager получает запросы методов (method calls) и направляет их к нужным слоям. Я обычно добавляю ещё ярлыки для отдельных вещей, которые точно пригодятся, вроде UICamera или MainCanvas.
Никакой код не должен прямо взаимодействовать с кодом экранов или слоёв, всё должно происходить через UI Manager.
Контроль анимации и плавности
Animator — мощный инструмент, если нужно анимировать гуманоидного персонажа, но для интерфейса он не очень подходит. Разве что анимации должны быть очень простыми: тогда можно дописать код к стейт-машинам (state machines) Animator.
Чаще всего в интерфейсе очень много анимаций для экранов, которые постоянно появляются и исчезают. При их настройке легко случайно создать условия для зависания при нажатии кнопки.
В коде экранов есть встроенные возможности для появления и исчезания, и во время анимации взаимодействие с ними заблокировано. Чтобы избежать проблем, можно тщательно контролировать EventSystem или, как я, создать прозрачный прямоугольник поверх всего.
Оптимизация интерфейса в Unity без кода

В этой статье ведущий UI/UX художник Никита Кандыбин и технический UI художник Ольга Кинчак поделятся эффективными базовыми практиками по оптимизации Unity UI, которые используются в компании Banzai Games при создании игровых интерфейсов, а также укажут на подводные камни тех или иных решений в дизайне и верстке. Практически во всех перечисленных ниже пунктах можно обойтись без кода, настраивая компоненты непосредственно в редакторе, а что-то даже предусматривая заранее на стадии проектирования макетов интерфейса.
Нативный Unity UI или, как его еще называют в народе, uGUI представляет собой довольно гибкий и удобный инструмент для создания и редактирования игровых пользовательских интерфейсов. Однако недостаток знаний, осведомленности о специфике работы и неправильное использование возможностей игрового движка рано или поздно приводит к возникновению проблем. Будь то низкая частота кадров, высокая загрузка центрального процессора, перегрев устройства и прочие страшные слова для проекта, который хочет стать (или продолжать оставаться) успешным.
Для мобильных платформ с их ограничениями по железу вопросы производительности и оптимизации становятся камнем преткновения на пути к реализации всего желаемого. Тем не менее это вовсе не повод отказываться от Unity UI — система хороша, просто нужно научиться в ней готовить.
Особенности Canvas
Для начала разберемся немного в том, каким образом организован Unity UI. Менеджментом всего, что связано с отрисовкой пользовательского интерфейса в Unity, занимается Canvas (далее просто “канвас”), являющийся родительским объектом для всех дочерних элементов интерфейса в иерархии сцены.
Любое изменение элементов в канвасе, будь то цвет, размер, позиция, материал, текст, активное состояние и т.д. помечает эти объекты как грязные (dirty), заставляя канвас их перерисовывать. Данный процесс включает в себя анализ изменений, перестроение канваса и его дальнейшее кэширование до тех пор, пока хотя бы один из дочерних элементов не будет вновь помечен как грязный.
Перестроение происходит в несколько этапов: расчет положений объектов в лэйаутах, анализ оптимальных способов их отрисовки, применение масок и пересчет графики грязных объектов. Канвас формирует команды для рендера, которые движок Unity отправляет на GPU и в конечном счете визуализирует.
Каждый из перечисленных этапов связан с целым ворохом потенциальных проблем с оптимизацией, которые мы и постараемся осветить далее.
UI Kit на все случаи жизни
В процессе отрисовки макетов пользовательского интерфейса постепенно формируется UI Kit проекта — все разнообразие графических элементов (кнопок, панелей, фоновых изображений, арта, декора, иконок и т.д), а также правил их компоновки. В дальнейшем все эти элементы будут порезаны на ассеты, залиты в движок и станут конструктором для финальной сборки интерфейса игры. Очень важно заранее продумывать, что и в каком виде попадет в Unity и насколько ресурсоемким станет в итоге.
Использование в UI дизайне таких популярных методов как slicing, tiling и окрашивание является одним из наиболее эффективных и распространенных способов экономии и многократного переиспользования ресурсов проекта.

Так, например, slicing и tiling позволяют хранить исходную графику в размере намного меньшем, чем могут оказаться созданные с ее помощью элементы интерфейса. У объектов сохраняется постоянство формы и при грамотной реализации отсутствуют какие-либо видимые артефакты. Эти приемы особенно полезны при адаптивной верстке в зоопарке мобильных устройств, когда при изменении пропорций экрана интерфейс должен эстетично растягиваться или сужаться.
В свою очередь, окрашивание является прекрасным их дополнением. Вместо сохранения линейки разноцветных копий вы можете подкрасить всего один графический объект, чтобы получить гораздо больше вариаций. Кроме того, что данный метод дает возможность программно управлять различными цветовыми схемами, без необходимости для дизайнера менять что-либо в исходных файлах.
Комбинируя приемы и отрисованные вами ассеты, ничто не мешает создавать сложные интерфейсные конструкции из простых, украшая их универсальными декоративными элементами и тем самым максимально эффективно используя готовые ресурсы. Однако у такого подхода есть и свои нюансы.
Overdraw
И в данном случае нюансами является специфика пайплайна рендеринга интерфейса в Unity и понятие overdraw. Если очень коротко, то overdraw — термин, обозначающий ситуацию, при которой один и тот же пиксель перерисовывается несколько раз (т.е. выполняется лишняя работа по его заполнению).
Дело в том, что все элементы интерфейса в сцене Unity отрисовывает в Transparent по очереди, от самого дальнего к самому близкому, смешивая их цвета при прозрачности — по-умолчанию считая их прозрачными, даже если они таковыми и не являются. А это означает, что рендериться будут абсолютно все активные графические объекты в иерархии, независимо от того, загораживаются ли они визуально другими или нет. При этом большое количество перекрывающихся элементов может привести к чрезвычайному высокому количеству перерисовок пикселей, цвета которых нужно смешать, снижению скорости их заполнения (fill rate’a) и, как следствие, возникновению проблем с производительностью.
C помощью одноименного режима отображения “Overdraw” в редакторе Unity позволяет довольно удобно отслеживать такие вот узкие места и буквально видеть перерисовку в сцене. Пусть такая оптимизация на первый взгляд кажется довольно незначительной, но когда объектов на экране становится много (а в сложных интерфейсах их ОЧЕНЬ много) и все они начинают наслаиваться, даже такие мелочи могут дать существенный прирост к производительности.

В нижнем варианте реализации у кнопки FIGHT тень, подложка и рамка слиты в одну текстуру, в отличие от верхнего, где каждый из этих слоев является самостоятельным спрайтом. Идентичный визуальный результат — разный Overdraw.
Поэтому в процессе создания дизайна интерфейса важно понимать и помнить, что для решения проблем с overdraw пока что лучшим выходом является создание ассетов, которые объединяют в себе как-можно больше декоративных элементов в одной текстуре. Важно уметь балансировать в своих визуальных решениях. Ведь недостатком таких объединенных текстур может стать полная инвариантность их применения и увеличение размера текстурных атласов проекта.
Draw Calls и использование атласов
Все это многообразие графических элементов, из которых состоит интерфейс игры, движку нужно каким-то образом собрать и передать графическому процессору. Чтобы отрисовать объект в сцене, вызывается метод отрисовки draw call и данные о нем передаются GPU. Чем больше отправляется таких вот вызовов за один кадр, тем больше времени занимает процесс рендеринга картинки, что ведет к риску уменьшения частоты кадров (FPS).
Один из наиболее эффективных способов снизить количество вызовов метода отрисовки заключается в использовании текстурных атласов — упаковки ваших текстур в единую, более крупную. Дело в том, что Unity старается автоматически объединять (“батчить”) графику в один draw call, отвечающую определенным критериям, тем самым ускоряя отрисовку кадра. В нашем случае наиболее важным здесь является то, что сбатчиться смогут только те объекты, которые используют одинаковые материалы и общий компонент рендерера (Canvas Renderer — дефолтный рендерер для всей UI графики). Таким образом, если элементы интерфейса в сцене используют стандартный UI материал, то, объединив их графику в один общий текстурный атлас, мы сможем получить тот самый желанный один draw call на рендеринг всей группы.

Однако и тут есть свои моменты. Как уже упоминалось ранее, объекты в кадре отрисовываются от самых дальних к самым близким, то есть от верхних к нижним в иерархии сцены. Если Rect Transform одного графического объекта хоть немного пересечется с другим, оба будут считаться накладывающимися друг на друга. При этом если в сцене между объектами, которые могли бы влезть в один draw call, ненароком вклинится объект с другим материалом, то он прервет объединение.
Так, например, текст является одной из основных причин дополнительных вызовов отрисовки, так как имеет отличный от любой другой графики материал и всегда будет беспощадно прерывать батчинг. Более того, размеры текстовых мешей и контейнеров зачастую занимают намного большую площадь, чем само начертание символов, что очень легко упустить из виду. Поэтому крайне важно следить за порядком отрисовки разношерстных объектов, их наслоением и иерархией внутри элементов в сцене.
Отрисовка кадра. Объекты на экране появляются последовательно по иерархии, но неравномерно в связи с использованием разных материалов и прерыванием батчинга.
К преимуществам в использовании текстурных атласов можно отнести и то, что они позволят движку сжимать ассеты произвольных размеров, тем самым уменьшая их общий вес при хранении в памяти. Дело в том, что большинство алгоритмов сжатия требуют, чтобы размер текстур был кратен степени двойки (например, 128×128, 256×256, 512×512 и т.д). Атласы, являющиеся по-умолчанию таковыми, берут все вопросы по сжатию на себя, исключая необходимость подгонять всю исходную графику вашего интерфейса под вышеуказанный критерий.
Также при создании атласов очень важно руководствоваться не просто желанием удобно “складировать” ассеты, но делать это грамотно, эффективно, учитывая логику появления тех или иных элементов в интерфейсе вашей игры. Даже если на экране изображена всего одна малюсенькая иконочка размером в 16×16 пикселей, упакованная в атлас, в память устройства она потянет за собой всю его текстуру в 4К. Еще на этапе дизайна возможно прикинуть, какие элементы станут уникальными и для каких экранов, а какие будут являться сквозным конструктором для интерфейса игры.
Использование масок
Маски — часто применяемый в дизайне пользовательских интерфейсов инструмент. Однако при его использовании очень важно учитывать влияние масок как на overdraw, так и на батчинг.
Unity предлагает нам два по-разному работающих компонента из коробки на выбор: Mask и Rect Mask 2D. При использовании любого из них графика, вылезающая за пределы маски, все равно будет влиять на общий fill rate, хоть она и не отображается на экране. Поэтому старайтесь избегать ситуаций, в которых для достижения желаемого результата маской будет вырезан лишь небольшой участок крупного графического объекта. Возможно, в каких-то местах лучше не скупиться на ассеты и сохранить этот кусочек в виде отдельной картинки.
Что же касается влияния на draw calls, то здесь наши компоненты разнятся. Обе маски прерывают батчинг между замаскированными объектами и их не замаскированными соседями, но по разным причинам. Mask использует stencil буфер (буфер шаблона), создавая в рантайме другой материал для всех своих дочерних объектов. Из-за этого материала они не станут батчиться с другими элементами в сцене, но все еще смогут сбатчиться друг с другом и даже с другими такими же замаскированными объектами под другой Mask.
Rect Mask 2D материал не меняет, stencil не использует и дополнительный overdraw от графики маски не добавляет, что делает ее в сравнении более производительной. Тем не менее использование данного компонента все равно рвет батчинг, причем, в отличие от Mask, элементы интерфейса под разными Rect Mask 2D друг с другом батчиться уже не будут.
Еще одним ограничением нативных масок является их неумение работать с плавным изменением альфа-канала. К сожалению, на данный момент ни один из вышеуказанных компонентов не позволяет добиться эффекта софт маски (soft mask), поддерживающей градиенты и полупрозрачности. За желаемым результатом придется обратиться к сторонним решениям или попробовать реализовать их самим.
В Asset Store можно найти плагины, добавляющие функционал софт маски в редактор. Имея под рукой такой инструмент, ему сразу же находится повсеместное применение в дизайне — скроллы, анимации, визуальные эффекты, декор и т.д.
Все три варианта маскируют картинку, используя один общий исходный спрайт.
Однако не стоит сильно обольщаться. Использование софт масок — хоть и приятная глазу, но довольно тяжелая фича, особенно для мобильных устройств. Так, например, открытие большого списка UI элементов под такой маской на весь экран с красивыми мягкими краями, как-минимум, гарантированно вызовет неприятный фриз игры. В подобной ситуации решением для нашей команды оказалось минимизировать использование софт масок (там, где это нужно), искать обходные пути в виде перекрывающих градиентов (там, где это возможно) или разнести добавление элементов в список на несколько кадров, чтобы размазать просадку производительности от инициализации софт маски.
Работа с текстом
Под каждый текстовый символ нативный UI Text создает отдельный квад (quad), а значит вполне закономерно, что чем больше в интерфейсе текста — тем больше геометрии в сцене. При этом при внесении любых изменений в текстовые значения, повторном включении его компонентов или родительского объекта вся эта геометрия будет перестраиваться.
По умолчанию все шрифты, которые добавляются в Unity, отмечаются как динамические. При всем удобстве их использование имеет свою цену. Атлас динамического шрифта генерируется в рантайме и заполняется только используемыми на данный момент в текстовых компонентах сцены символами. Для каждого шрифта создается свой атлас, учитывающий все встречающиеся в сцене размеры текстовых символов. Каждое появление нового символа в интерфейсе инициирует обновление и перестройку атласа шрифта, что порой может негативно сказаться на производительности.
Более того, если в текущем атласе уже нет свободного места для новых текстовых глифов, Unity пересобирает его заново, удаляя не использующиеся в данный момент символы. А если и это не помогает, то увеличивает его размер.
Если в игре не используется огромное количество символов, их набор заранее предопределен, то вместо динамических шрифтов намного лучше использовать статические. В настройках шрифта в редакторе можно заранее сгенерировать атлас, который будет содержать только необходимые проекту символы и не будет изменяться динамически, минуя все упомянутые в предыдущем абзаце издержки.
Отдельного упоминания заслуживает функция Best Fit, позволяющая автоматически изменять размер шрифта, если текст не помещается в свой Rect Transform. Сами сотрудники Unity в официальных туториалах вспоминают ее с ужасом и не рекомендуют использовать — настолько все плохо, не оптимизировано и быстро перегружает шрифтовый атлас. Старайтесь заранее предусматривать максимально возможные размеры текстовых контейнеров и следить за тем, чтобы все везде помещалось, оставляя запас на все происки локализации.
Альтернативой UI Text может быть сторонний TextMesh Pro, который использует только статичные шрифты, да и его Best Fit работает гораздо лучше и экономнее. Однако минусом работы с данным компонентом может быть то, что под каждую локализацию и текстовый стиль придется создавать свой отдельный набор ассетов шрифтов. Тут уж каждый решает сам, что ему ближе и как удобней.
Блюр
Размытие картинки (блюр) уже много лет как любят и используют в своей работе дизайнеры пользовательских интерфейсов, но в Unity UI его готовой оптимизированной реализации попросту нет.
Большое количество реалтайм блюра, позволяющего оставаться картинке динамичной, в Unity — гарантированный способ убить производительность в мобильной игре. Гораздо легче переносится его статичная версия — создается скриншот экрана, размывается в необходимое количество проходов и используется в дальнейшем как текстуру. Существует множество сторонних ассетов, позволяющих реализовать оба подхода. Однако, если использование реалтайм блюра для интерфейса все-таки оправдано и критично, постарайтесь тщательно следить хотя бы за тем, чтобы материалы блюра батчились вместе, если на экране есть несколько участков с размытием.
Layout группы
Layout группы (Layout Groups) — крайне удобный инструмент в Unity UI, помогающий автоматически располагать произвольное количество элементов с заданой ориентацией, выравниванием и отступами. Однако использовать его нужно с осторожностью. Сортировка, поиск групп и расчет положений элементов при перерисовке атласа — довольно затратное дело, особенно если в сцене есть Layout группы, и тем более если они оказываются вложены друг в друга. Старайтесь всюду, где это возможно, обходится грамотным использованием анкоров и пивотов трансформа UI элементов, предпочитая их Layout группам.

Raycast Target
Галка Raycast Target на графических компонентах UI элементов означает, что последние могут отлавливать клики от пользовательской мыши или тапы на сенсорных экранах. Свежесозданному объекту с компонентами Image или Text редактор ставит эту галку по-умолчанию. Для того, чтобы определить, какой объект поймал “тычок” от пользователя, Graphic Raycaster, обрабатывающий события ввода в Unity UI, проходится по всему списку элементов в иерархии, помеченных как Raycast Target, и сортирует их очередность, рассчитывая пересечения и перекрытия одних объектов другими.
Если оставлять включенной галку только тем объектам, которым это действительно логически необходимо (например, кнопкам), можно значительно сократить список для обхода и сортировки, тем самым ускорив полезную работу движка.

Иерархия. Чем проще — тем проще
И Layout группы, и Graphic Raycaster в процессе работы непрерывно путешествуют по дереву иерархии сцены, от находящихся глубоко дочерних элементов к самому корню, в процессе поиска различных компонентов.
Суть оптимизации данного процесса заключается в том, чтобы стараться этот путь сокращать — держать иерархию максимально плоской, избегая вложений, когда без них можно обойтись. Создавать объекты-папки для менеджмента других элементов интерфейса возможно и более удобно для восприятия, но злоупотреблять ими все-таки не стоит, чтобы не усложнять структуру иерархии.

Разделение канвасов
В самом начале статьи мы уже выяснили, что канвас стремится перестраиваться на любой чих от своих дочерних объектов. Чтобы в этом процессе не участвовали вообще все элементы интерфейса, некоторые из них можно вынести в суб-канвас (sub-canvas). Такой канвас будет перестраиваться самостоятельно, независимо от других, тем самым никак их не загрязняя. Лучший способ использовать это состоит в том, чтобы отсортировать элементы пользовательского интерфейса на статические и динамические. Например, если у вас есть окно инвентаря со скроллом предметов, вынесенный в отдельный канвас, скролл будет перестраиваться самостоятельно, не тревожа лишний раз статичные элементы фона.
Казалось бы, вот оно — решение всех проблемы с ненужными перестроениями. Но у всего есть свои “но”. Контролировать большое количество канвасов может стать банально неудобно. Также очень важно помнить, что элементы принадлежащие разным канвасам гарантированно не будут батчиться друг с другом. Поэтому, принимая структурные решения, разделением элементов интерфейса на разные канвасы лучше пользоваться осторожно и обязательно проверять в Профилировщике (Profiler), действительно ли такой подход может дать заметный прирост к производительности.
В наших проектах в отдельный канвас выносятся только элементы для фонового изображения, которое является общим для большинства экранов в интерфейсе игры и чаще всего статично.
И вот мы все это учли, провели работу над ошибками и получили более-менее оптимизированный статичный интерфейс. Пришло время добавить ему движения, но и тут можно с легкостью наломать дров.
Particle System
Тот, кто когда-нибудь пытался добавить в интерфейс системы частиц (Particle System), прекрасно знает, что это как смешивать жидкости разной плотности — одна обязательно будет отображаться над другой, как ты не крутись.
Стандартные частицы в Unity не учитывают сортировку объектов в канвасе и не умеют работать с масками. Поэтому на помощь опять приходят сторонние компоненты из Asset Store, которые решают вышеперечисленные вопросы, но при этом зачастую увеличивают ресурсную стоимость системы частиц. Следовательно, требования по производительности к частицам в пользовательском интерфейсе куда-более строгие, чем, скажем, на локациях или для персонажей. Кроме того, большое количество крупных наслаивающихся друг на друга частиц может значительно повысить overdraw.
Бывает и такое, что альтернативой системе частиц для достижения определенных визуальных эффектов и вовсе могут стать простые шейдеры (например, со скроллом или искажением текстуры), являющиеся более экономным и оптимизированным решением в вопросе перерисовок.
Анимация
Рассмотрим частую ситуацию. Необходимо заанимировать кнопку и все ее стейты — Unity любезно предлагает нам Animation Controller (далее просто “аниматор”) с базовым суповым набором анимаций для разных состояний, которые останется только отредактировать, да еще и без необходимости запуска рантайма для проверки результата. Удобно? Очень удобно. И снова коварная дефолтная ловушка. Ведь каждый кадр аниматор будет перерисовывать этот объект, помечая как грязный, даже если на экране в данный момент вообще не проигрывается никакая анимация.
Потому для случаев, когда анимируемые элементы интерфейса визуально большую часть времени остаются статичны, нативный аниматор лучше не использовать. Заменой ему в данном вопросе может послужить система tween-анимаций. Например, использование распространяемого бесплатно плагина DOTween. Однако за увеличение производительности придется заплатить необходимостью писать анимации в виде скриптов уже без удобного наглядного таймлайна, как у аниматора из коробки Unity.
Пример анимации произвольного элемента интерфейса.

… и то, как данная анимация может быть реализована с помощью абсолютно разных подходов.
Пока не разошлись
На этом, пожалуй, остановимся. Тема действительно очень обширна — мы коснулись лишь вопросов, которые лежат на поверхности и не требуют вмешательства со стороны кода… Но, как известно, совершенству нет предела.
Оптимизация — это всегда про выбор, про поиск баланса между разными, а порою даже и противоречащими друг другу решениями. Не стоит впадать в крайности с каким-либо из ее методов без предварительного анализа и уверенности в том, что ваши действия гарантированно приведут к положительному результату.
Не нужно сразу ужиматься и делать высоко оптимизированный серый квадрат на сером фоне, отказывая игре во “вкусном” интерфейсе. Тем не менее, каждый раз копая глубже, у вас появляется возможность избежать все большего количества проблем еще на старте. Ситуации варьируются от проекта к проекту, от цели к цели, но общие принципы всегда остаются прежними.
Ребята в Unity, конечно, тоже не сидят сложа руки. Официальные гайды на Unity Learn пополняются хорошими статьями про оптимизацию UI, а новые версии движка потихоньку, но исправляют проблемы старых. Судя по последним анонсам, Unity уже готовят абсолютно новый унифицированный инструмент для редактирования пользовательских интерфейсов — на первый взгляд, очень уж напоминающий CSS для web-разработки. В новой системе обещают полностью переработать отрисовку интерфейса и значительно улучшить производительность. Что ж, посмотрим, что в итоге получится. А пока работаем с тем, что имеем.