Програмування логіки відображення графіки інтерфейсу
Намальований та верстаний інтерфейс—це ще не інтерфейс. Потрібна логіка: звідки беруться дані, як вони потрапляють на екран, як екран реагує на зміну стану гри, як працює навігація між екранами. Це програмування UI, і воно складає третину часу розробки інтерфейсу.
Архітектура зв'язку UI з ігровою логікою
Головний архітектурний вибір—як UI дізнається про зміни в стані гри. Три основних підходи:
Observer / Event System—UI підписується на события ігрових систем: HealthSystem.OnHealthChanged += UpdateHealthBar. Слаба зв'язність, можна легко переключати UI-реалізації. Проблема: при знищенні об'єкту до відписки—NullReferenceException. Обов'язково відписуватися в OnDisable() або OnDestroy().
Model-View-Presenter (MVP)—Presenter відповідає за зв'язок між даними (Model) та відображенням (View). View не знає про ігрову логіку, тільки про свої візуальні компоненти. Presenter знає обидві сторони. Добре масштабується, добре тестується. Типова реалізація в Unity—абстрактний клас BaseView з методами Show(), Hide(), Bind(IModel model), та конкретні Presenter'и під кожен екран.
ScriptableObject-based System подій—всі события як ScriptableObject з методом Raise() та списком слухачів. Популяризовано Ryan Hipple на Unite 2017. Зручно для невеликих команд, легко дебажити в Inspector. Мінус: при великій кількості подій важко керувати залежностями.
Для більшості проектів рекомендую MVP з EventSystem для cross-module комунікації. Це дає читаючий код, передбачувану поведінку та хорошу тестуємість UI без запуску сцени.
Управління станами екранів
Screen Manager—типова система, яка контролює, який екран відкритий, та керує переходами. Мінімальна реалізація: стек екранів (для Back навігації), словник screenId → IScreen, методи Push(), Pop(), Replace().
Для мобільних платформ Android Back Button повинен коректно обробляватися через Application.exitCancellationToken або через Input.GetKeyDown(KeyCode.Escape)—при нажатті повинен закриватися верхній екран в стеку, а не виходити з гри. Це те, що часто забувають при розробці та замічають при здачі в Google Play.
CanvasGroup—інструмент для керування видимістю та інтерактивністю групи елементів. canvasGroup.alpha = 0 приховує візуально, але елементи продовжують отримувати события. Обов'язково також встановлювати canvasGroup.interactable = false та canvasGroup.blocksRaycasts = false при приховуванні—інакше невидимі кнопки перехоплюють клики крізь них.
Кейс: інвентар з drag & drop
Завдання: drag & drop перетягування предметів між слотами інвентаря з підтримкою геймпада. На мишці—IBeginDragHandler, IDragHandler, IEndDragHandler. На геймпаді—совсім інша логіка: курсорний режим навігації з вибором джерела та цілі через кнопки.
Реалізація drag & drop у uGUI потребує створення «ghost» об'єкту—копії перетягуваної іконки, яка слідує за курсором через RectTransformUtility.ScreenPointToLocalPointInRectangle(). Ghost повинен знаходитися в окремому Canvas поверх всього іншого (окремий Canvas з Sort Order вище основного)—інакше іконка буде перекриватися іншими елементами при перетяганні.
Для геймпада реалізували окремий режим: перше нажаття A вибирає слот (підсвічується selected state), навігація D-pad переміщує віртуальний курсор між слотами, друге нажаття A завершує перетягування. Два режими керування—два різні кінцеві автомати в одному InventoryController.
Логіка відображення даних: типові проблеми
Оновлення UI кожен кадр—антипаттерн. healthBar.fillAmount = player.health / player.maxHealth в Update() працює, але пересоздаєє mesh для Image компонента кожен кадр навіть якщо значення не змінилось. Коректно: оновлювати тільки при зміні даних через event або property з setter.
Конкатенація рядків у Update—гірше. levelText.text = "Level: " + player.level створює новий рядок кожен виклик, провокуючи GC Allocations. Використовуємо string.Format() або StringBuilder для часто оновлюваних текстів. У TextMeshPro є SetText(string, float)—перевантаження з float-аргументом, яка форматує число без GC allocation.
Z-fighting кнопок: два Button'и з одинаковим Sort Order в одному Canvas, один поверх іншого. EventSystem відправляє событие на верхній по ієрархії, але якщо Raycast Target увімкнений у обох—клики можуть проваліватися. Перевіряємо через UI Debugger (правий клік EventSystem → UI Debug).
Відображення Loading State: при очікуванні на відповідь від сервера UI повинен блокувати повторні нажаття. Типова помилка—просто показати Spinner та забути відключити кнопку. Коректно: блокуємо CanvasGroup.interactable = false для всього екрана + показуємо Loading Overlay + в finally блоці async-методу відновлюємо interactable = true. Інакше при повільному з'єднанні користувач встигає нажати кнопку кілька разів.
Процес та строки
Розробка починається з архітектурного рішення: вибір паттерну зв'язку з ігровою логікою, визначення Screen Manager архітектури, узгодження інтерфейсів між UI та ігровими системами. Потім—реалізація базової інфраструктури (Screen Manager, Base View, Event Bus). Далі—поекранна реалізація з юніт-тестами для Presenter'ів. Фінал—інтеграційне тестування на пристроях.
| Завдання | Строки |
|---|---|
| Логіка 1 екрана (відображення даних, кнопки) | 1–3 дні |
| Screen Manager + навігація для всього проекту | 3–7 днів |
| Складна система (інвентар, drag & drop, геймпад) | 1–2 тижні |
| Повна UI-логіка інді-проекту (10–15 екранів) | 4–10 тижнів |
Вартість розраховується індивідуально після аналізу вимог до ігрової логіки та платформ.





