Верстка интерфейсов игр в Unity UI (uGUI)
Unity UI (uGUI) — система, которая работает через Canvas, LayoutGroup и EventSystem. При правильной настройке она даёт стабильный, предсказуемый интерфейс с минимальными draw calls. При неправильной — 200+ draw calls на одном экране инвентаря и Canvas.ForceUpdateCanvases() вызовы, которые подвисают на 8–12 мс раз в несколько кадров.
Большинство проблем с производительностью uGUI закладываются в архитектуре Canvas, а не в количестве элементов.
Canvas и батчинг: где теряются draw calls
Canvas собирает все UI-элементы в batch — группу объектов, которые рендерятся одним draw call. Условие батчинга: одинаковый материал + одинаковая текстура (Sprite Atlas) + одинаковая глубина без перекрытий с чужеродными элементами.
Разрушают батчинг три вещи. Первое — смешение источников текстур: иконка из Атласа A, кнопка из Атласа B, текст через TextMeshPro со своим атласом шрифта. Каждое переключение текстуры — новый draw call. Решение: все визуальные элементы одного экрана — в один Sprite Atlas. TextMeshPro-тексты группируем отдельно, они всегда будут своим batch'ем, но хотя бы одним.
Второе — вложенные Mask компоненты. Каждый Mask в uGUI добавляет 2 stencil-прохода и разрывает батч всего, что находится внутри и снаружи маски. Для прокручиваемых списков это неизбежно, но для статичных элементов с «обрезанными углами» лучше нарисовать текстуру с нужной формой, чем добавлять Mask.
Третье — неправильный порядок Sibling Index при перекрытии элементов. Если Image A и Image B перекрываются и они из одного атласа, батч всё равно разрывается. Unity batches работают только если нет перекрытий между элементами из разных атласов. Это значит, что z-порядок UI-элементов влияет на батчинг так же, как и текстурный атлас.
Диагностика: Frame Debugger → кнопка Enable → разворачиваем рендер-дерево до секции UI → считаем количество draw calls на UI. Хорошая цифра для одного HUD — 5–15. Больше 50 — что-то пошло не так.
RectTransform: правила, которые спасают от боли
RectTransform — основа позиционирования в uGUI. Якоря (Anchors) и Pivot — не то же самое. Якорь определяет, к какой точке родительского контейнера привязан элемент. Pivot — точка вращения и масштабирования самого элемента.
Типичная ошибка: якоря выставлены в левый верхний угол, а элемент должен находиться по центру экрана независимо от разрешения. При смене разрешения элемент уезжает. Правильно: якорь → center/middle, позиция через anchored position = (0, 0). Тогда элемент остаётся по центру на любом разрешении.
Для адаптации к разным разрешениям используем Canvas Scaler. Режим Scale With Screen Size с Reference Resolution 1920×1080 и Match = 0.5 (средняя между Width и Height) — рабочая база для большинства проектов. Для мобильных с разными соотношениями сторон Match лучше поднять до 0.8–1.0 в пользу Height, иначе на iPhone с соотношением 19.5:9 UI расплывется по горизонтали.
ContentSizeFitter + VerticalLayoutGroup — комбинация для динамических списков. ContentSizeFitter растягивает контейнер под содержимое, VerticalLayoutGroup расставляет дочерние элементы. Проблема: каждое изменение в иерархии вызывает Layout Rebuild — пересчёт позиций всех элементов. Для ScrollView с сотнями элементов это катастрофа. Решение — Virtual Scroll List (pool-based): держим в памяти только видимые элементы, остальные возвращаем в пул при прокрутке. Это требует ручной реализации, но снижает количество активных UI-объектов с сотен до 10–15.
TextMeshPro: шрифты и производительность
TextMeshPro — стандарт для текста в Unity UI. Использует Signed Distance Field (SDF) рендеринг: шрифт выглядит чётко на любом масштабе без растра. Для каждого языка нужен свой Font Asset с нужными глифами.
Проблема при локализации: кириллица, китайский и японский требуют разных Font Asset'ов. При смешении языков на экране это несколько Font Asset'ов, несколько атласов шрифтов, несколько draw calls только на текст. Решение — Fallback Font Asset: основной шрифт для латиницы с прописанным Fallback на кириллический/CJK шрифт. TextMeshPro автоматически использует fallback для недостающих глифов, и весь текст остаётся в рамках одного-двух draw calls.
Atlas Population Mode: Dynamic (по умолчанию) добавляет глифы в атлас по мере встречи в тексте — хорошо для dev-сборок, плохо для продакшна (возможны микрофризы при первом рендере нового глифа). Для релиза используем Static с предварительно заполненным атласом через Font Asset Creator.
Кейс: 200 draw calls на экране инвентаря
Пришёл проект с жалобой на 15% FPS просадку при открытии инвентаря на мобильных устройствах. Frame Debugger показал 214 draw calls для одного экрана с 48 слотами предметов. Причина: каждый слот был отдельным Prefab с иконкой из индивидуального PNG (не атлас), TextMeshPro-количеством, фоновой рамкой из другого PNG, и Outline-компонентом (который в uGUI генерирует дополнительную mesh).
Решение: упаковка всех иконок в два Sprite Atlas (предметы и UI-хром раздельно), замена TextMeshPro Outline на SDF Outline в настройках материала TMP, добавление Virtual Scroll List для сетки предметов. Итог: 22 draw calls для того же экрана. FPS на мобильных устройствах вернулся к норме.
Процесс вёрстки
Начинаем с получения Figma-макетов с указанием размеров, отступов и правил адаптации. Определяем структуру Canvas'ов и атласов. Верстаем базовый layout на RectTransform'ах, подключаем шрифты и текстуры. Настраиваем EventSystem и навигацию для геймпада/клавиатуры. Тестируем на нескольких разрешениях через Game View с кастомными разрешениями. Профилируем через Frame Debugger.
| Объём | Сроки |
|---|---|
| 1–3 экрана (простые, без анимаций) | 2–5 дней |
| Полный UI-комплект инди-проекта (10–15 экранов) | 3–6 недель |
| Сложные экраны с virtual scroll, drag & drop | 1–3 недели за систему |
| Мультиплатформенная адаптация существующего UI | 1–4 недели |
Стоимость определяется после анализа Figma-макетов и технических требований.





