Разработка игр на Unity
Проект уже на середине пути, а сцены не собираются в билд без ошибок компилятора, AssetBundle-кэш разбух до 4 ГБ, а на целевом Android-устройстве URP рендерит 8 FPS вместо 30. Знакомая картина. Unity — мощный инструмент, который при неправильной архитектуре превращается в источник технического долга быстрее, чем успеваешь дописать геймплей.
Где обычно ломается Unity-проект
Самая частая проблема — неправильное управление жизненным циклом объектов. MonoBehaviour.Update() на 400 активных объектах, каждый из которых дёргает GetComponent<Rigidbody>() каждый кадр, — это не архитектура, это катастрофа. На PC это незаметно. На iOS A14 это 12 мс оверхеда только на рефлексию.
Второй классический грабль — AddressableAssets без стратегии выгрузки. Проект подгружает локации через Addressables.LoadAssetAsync, но забывает вызвать Addressables.Release(). Через час игровой сессии RSS процесса вырастает с 800 МБ до 2.4 ГБ, и iOS убивает приложение. Это не баг Unity — это баг архитектуры.
Третья боль — смешение логики в сцене и в ScriptableObject. Команда начинает с MonoBehaviour-синглтонов, потом переходит на ScriptableObject-based EventSystem, но в итоге система событий живёт в трёх местах одновременно. Каждый новый разработчик добавляет слой поверх, и через полгода никто не знает, откуда прилетает OnPlayerDied.
Менее очевидная, но регулярная проблема: Physics.Raycast в Update() без LayerMask. Каждый вызов проверяет все коллайдеры сцены. При 50 агентах и сложной геометрии это 1-2 мс на кадр только на физику.
Как мы строим Unity-проекты
Архитектура и стек
Основа — разделение на три слоя: GameplayCore (чистая C# логика без зависимостей от Unity API), UnityGlue (MonoBehaviour-обёртки), и Infrastructure (сервисы: сохранения, аналитика, сеть). Это позволяет тестировать геймплей без запуска редактора через NUnit + Unity Test Framework.
Рендер-пайплайн выбираем под платформу:
| Платформа | Пайплайн | Причина |
|---|---|---|
| Mobile (iOS/Android) | URP | Batching, SRP Batcher, низкий overhead |
| PC / Console | URP или HDRP | HDRP — только если нужен AAA-рендер |
| WebGL | URP | Built-in устарел, HDRP не поддерживается |
| 2D проект | URP 2D | Tilemap, Sprite Atlas, 2D Lighting |
Шейдеры пишем через ShaderGraph там, где нужна визуальная итерация с художником. Производительные низкоуровневые эффекты — вручную на HLSL с Custom Function Node. Amplify Shader Editor используем только если проект уже на нём.
Оптимизация draw calls
На мобильных проектах стандартная цель — не более 100-150 draw calls на кадр. Достигается через:
- GPU Instancing на повторяющейся геометрии (деревья, пропсы). MaterialPropertyBlock для per-instance данных без разрыва батча.
- SRP Batcher — работает автоматически с URP, но требует, чтобы все шейдеры были SRP-совместимы. Один non-compatible материал рвёт весь батч.
- Sprite Atlas для UI — критично. UI Canvas с Overlay режимом и 60+ отдельными спрайтами даёт 60+ draw calls только на интерфейс.
- Occlusion Culling для 3D сцен — запекаем через Window → Rendering → Occlusion Culling. На уровнях с непрозрачной геометрией снижает draw calls на 40-60%.
Профилируем через Unity Profiler + Frame Debugger + RenderDoc (для детального анализа GPU). На Android дополнительно — Android GPU Inspector для Mali/Adreno.
Многопоточность и ECS
Для проектов с большим количеством агентов или симуляций рассматриваем DOTS (Unity ECS + Burst Compiler + Jobs System). Burst компилирует C# до нативного SIMD-кода — на задачах вроде pathfinding для 1000 агентов это разница между 16 мс и 0.8 мс на основном потоке.
Для обычных проектов без DOTS — UniTask вместо корутин. Корутины работают на MainThread и не отменяются корректно при уничтожении объекта. UniTask с CancellationToken решает оба вопроса.
Мультиплеер
Для реального времени: Photon Fusion 2 (server-authoritative, rollback netcode) или Mirror (self-hosted, открытый исходник). Выбор зависит от требований к latency и бюджету на инфраструктуру. Для пошаговых и асинхронных взаимодействий — PlayFab CloudScript + Azure Functions.
Сохранения и облачная синхронизация — Firebase Realtime Database для простых случаев, PlayFab для полноценного game backend (лидерборды, матчмейкинг, экономика).
Как выглядит процесс работы
Пре-продакшн (1-2 недели). Разбираем ТЗ, определяем целевые платформы и технические ограничения. Создаём вертикальный срез — минимально работающую механику в изоляции. Это важнее полного дизайн-документа: лучше потратить неделю на прототип, чем три месяца на разработку механики, которая не работает на целевом железе.
Продакшн. Спринты по 1-2 недели. Каждый спринт заканчивается рабочим билдом. Используем Git с LFS для ассетов, Jira или Linear для задач. Code review обязателен — особенно на системах, которые затронут несколько сцен.
Тестирование. Unit-тесты на геймплейную логику (Unity Test Framework, Play Mode). Интеграционные тесты через Playwright для WebGL. Ручное тестирование на реальных устройствах — симулятор iOS не воспроизводит реальное потребление памяти.
Запуск. Автоматические билды через Unity Cloud Build или GitHub Actions с fastlane для iOS. Android — Google Play Internal Testing, iOS — TestFlight.
Сроки по типу проекта
| Тип проекта | Масштаб | Примерные сроки |
|---|---|---|
| Гипер-казуальная игра | 1-3 механики, без бэкенда | 2-4 недели |
| Казуальная мобильная | Прогрессия, монетизация, облако | 2-4 месяца |
| Мидкор мобильная | Мета-геймплей, PvP, экономика | 4-8 месяцев |
| PC инди | Одиночная кампания | 3-9 месяцев |
| PC мультиплеер | Сеть, матчмейкинг, античит | 6-18 месяцев |
Стоимость рассчитывается индивидуально после анализа технического задания и целевых платформ.
Типичные ошибки при запуске Unity-проекта
Игнорировать Profiler до полировки. «Сначала сделаем, потом оптимизируем» работает до тех пор, пока не выясняется, что архитектурное решение, принятое в первый месяц, не поддаётся оптимизации без переписывания половины игры.
Хранить все ассеты в Resources/. Папка Resources загружается в память при старте приложения целиком. 500 МБ текстур в Resources — это 500 МБ RAM до запуска первой сцены. Addressables решают это, но требуют планирования с самого начала.
Один огромный Canvas для всего UI. Unity перерисовывает весь Canvas при изменении любого дочернего элемента. Разбивайте UI на статичные и динамические Canvas-компоненты.
Физика на триггерах вместо расчётов. OnTriggerEnter надёжен при низких скоростях. Пуля, летящая 200 единиц/сек, проходит сквозь тонкие коллайдеры между кадрами. Для таких случаев нужен Physics.SphereCast или Rigidbody с Continuous Collision Detection.





