Услуги по оптимизации производительности игр

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

От иммерсивных приложений до игровых миров и 3D-сцен

Наша выделенная команда для VR/AR/MR-разработки, Unity-продакшна и 3D-моделирования и анимации с собственными кейсами и презентациями.

Посетить персонализированный сайт
Показано 10 из 10 услугВсе 242 услуг
Сложная
от 3 рабочих дней до 2 недель
Сложная
от 3 рабочих дней до 2 недель
Средняя
от 1 рабочего дня до 1 недели
Сложная
от 2 рабочих дней до 2 недель
Сложная
от 1 рабочего дня до 1 недели
Часто задаваемые вопросы
Наши компетенции
Какие этапы разработки игры?
Последние работы
  • image_games_mortal_motors_495_0.webp
    Разработка игры для компании Mortal Motors
    671
  • image_games_a_turnbased_strategy_game_set_in_a_fantasy_setting_with_fire_and_sword_603_0.webp
    Пошаговая стратегия в фэнтези сеттинге With Fire And Sword
    860
  • image_games_second_team_604_0.webp
    Разработка игры для компании Second term
    490
  • image_games_phoenix_ii_606_0.webp
    3D-анимация — тизер для игры phoenix 2.
    533

Оптимизация производительности

Проект запускается на топовых девайсах разработчика без вопросов. На mid-range Android 2020 года — 20 fps и перегрев через 5 минут. На iPhone 11 — стабильные 60, но на iPhone XR — просадки в тяжёлых сценах. Это стандартная ситуация, когда оптимизация не закладывалась в архитектуру с самого начала, а делается «потом» — что всегда дороже и болезненнее.

Инструменты профилирования

Прежде чем оптимизировать — измерить. Оптимизация без профилирования — это угадывание.

Инструмент Назначение
Unity Profiler CPU/GPU время по системам, GC allocations, audio
Frame Debugger Инспекция каждого draw call в кадре
Memory Profiler Снимок памяти, граф зависимостей ассетов
RenderDoc Глубокий анализ GPU-состояния, актуален для PC/Console
Android GPU Inspector Профилирование GPU на реальном Android-устройстве
Xcode Instruments GPU + memory на iOS (Metal Performance HUD)
Snapdragon Profiler Qualcomm GPU — детальная статистика шейдеров

Правило первое: профилируйте на целевом железе, а не в редакторе. Editor добавляет существенный оверхед — цифры из Play Mode не репрезентативны для сборки.

Правило второе: смотрите на GPU-время и CPU-время раздельно. Bottleneck может быть на CPU (слишком много draw calls, тяжёлая логика), на GPU (сложные шейдеры, overdraw, fillrate), или в памяти (GC allocations, texture streaming). Лечение разное.

Оптимизация Draw Calls: батчинг в деталях

Draw call — команда CPU к GPU «нарисуй это». Каждый вызов имеет overhead на стороне CPU независимо от сложности геометрии. На мобильных 200-300 draw calls за кадр — уже граница, после которой начинаются проблемы. Цель: минимизировать количество draw calls, объединяя геометрию с одинаковым материалом.

Static Batching

Объединяет статичные (неподвижные) меши в единый большой меш при сборке или на старте сцены. Требования:

  • Static флаг на объекте (или хотя бы Batching Static)
  • Одинаковый материал

Плюсы: нет CPU-overhead в рантайме, работает со всеми платформами. Минусы: увеличивает потребление памяти (объединённый меш хранится отдельно от оригинала) и время загрузки сцены. Для сцен с тысячами статичных объектов — осторожно с памятью, смотрим через Memory Profiler.

Dynamic Batching

Объединяет меши в рантайме для каждого кадра. Требования жёстче:

  • Меньше 900 вертексных атрибутов на меш (Unity ограничение)
  • Одинаковый материал
  • Одинаковый масштаб (или не-негативный масштаб по одной оси)

На практике Dynamic Batching эффективен только для мелких объектов (партиклы, UI-элементы, мелкий дебрис). Для персонажей и окружения обычно не подходит из-за ограничения по вертексам. В URP Dynamic Batching по умолчанию отключён — его вытеснил SRP Batcher.

SRP Batcher

SRP Batcher — не классический батчинг геометрии, а оптимизация CPU-overhead при подготовке draw calls. Вместо того чтобы каждый кадр заново загружать uniform-данные шейдера (матрицы, свойства материала), SRP Batcher кэширует их в GPU-памяти и обновляет только изменившиеся.

Результат: draw calls остаются по количеству прежними, но каждый занимает меньше времени CPU. В сценах с большим количеством уникальных материалов SRP Batcher даёт ощутимый прирост — иногда 2-3x по CPU-времени рендера.

Требование: шейдер должен быть совместим с SRP Batcher — объявлять все per-object свойства в UnityPerDraw CBUFFER. Стандартные URP Lit/Unlit шейдеры совместимы. Кастомные шейдеры — проверяем в Inspector материала: покажет SRP Batcher compatible: Yes/No.

GPU Instancing

Для множества копий одного и того же меша с одним материалом (деревья, трава, NPC одного типа, снаряды). GPU Instancing отправляет один draw call с массивом per-instance данных (матрицы трансформации, цвет) — GPU рисует все копии за один раз.

Включается на материале: Enable GPU Instancing checkbox. В шейдере — поддержка через UNITY_INSTANCING_BUFFER (стандартные URP шейдеры поддерживают). Ограничение: все инстансы в одном batch должны иметь одинаковый материал и меш.

Graphics.DrawMeshInstanced / Graphics.DrawMeshInstancedIndirect — для процедурного рендеринга инстансов без GameObject overhead (трава, частицы, процедурный контент). Indirect-версия позволяет формировать список инстансов на GPU через Compute Shader.

Сравнение подходов:

Метод Лучший сценарий Ограничение
Static Batching Статичное окружение Память
SRP Batcher Много уникальных материалов CPU overhead только
GPU Instancing Много копий одного объекта Одинаковый материал/меш
Dynamic Batching Мелкие объекты в URP Вертексное ограничение

Оптимизация памяти для мобильных

Мобильные платформы — жёсткие ограничения по RAM. iOS убивает приложение при превышении памяти без предупреждения (memory pressure kill). Android — аналогично, но с onLowMemory callback. Целевые бюджеты:

  • iOS: < 1 GB для современных устройств, < 512 MB для поддержки iPhone 8/X
  • Android: < 800 MB для широкой совместимости, учитывать что Android сам занимает ~400-600 MB

Addressables и Asset Bundles

Загружать всё сразу при старте — неприемлемо для больших проектов. Addressables (надстройка над Asset Bundles) — система адресуемой асинхронной загрузки ассетов.

Ключевые принципы:

Явная выгрузка: Addressables.ReleaseInstance / Addressables.Release. Addressables не выгружают ассеты автоматически при уничтожении объекта. Типичная ошибка: Addressables.InstantiateAsync в цикле без Release — память растёт до краша.

Reference counting: ассет выгружается только когда все его handles освобождены. Архитектурный паттерн: сервис/менеджер держит handle загруженного ассета, освобождает при переходе между сценами или явном вызове.

Groups и Bundle Strategy: группируем ассеты по логике загрузки:

  • Pack Together — все ассеты группы в одном bundle (загрузка одним запросом)
  • Pack Separately — каждый ассет в своём bundle (гранулярная загрузка)
  • Pack Together by Label — по меткам (гибкий вариант)

Для уровней: все ассеты одного уровня в одном bundle. Шаренные ассеты (общие текстуры UI, шрифты) — в отдельной группе с Prevent Updates для стабильного кэша.

Texture Memory

Текстуры — основной потребитель памяти в большинстве игр. Анализ через Memory Profiler: вкладка All Of MemoryTexture2D — сразу виден список самых тяжёлых текстур.

Практические меры:

  • Mipmap: для 3D-текстур — включить, для UI — выключить (Advanced > Generate Mip Maps: false). UI-текстуры рендерятся в фиксированном пространстве, mipmap только тратит память
  • Max Size: проверить, не завышен ли Max Size в Import Settings. 4096 для мобильной иконки — типичная ошибка
  • Крассовская проблема: текстуры, на которые ссылаются неиспользуемые Materials в памяти — Memory Profiler покажет reference chain
  • Streaming Mipmaps: для open world — Texture Streaming в Quality Settings. Загружает mip-уровни по мере приближения камеры

GC Allocations

C# garbage collector в Unity — stop-the-world. Если за кадр аллоцировано много heap-памяти, GC-пауза вызовет видимый фриз. Цель: нулевые аллокации в hot path (Update, FixedUpdate, рендер).

Типичные источники аллокаций, которые находим в Profiler:

  • string конкатенация в Update ("Score: " + scoreStringBuilder или string.Format)
  • LINQ в hot path (Where, Select, ToList → ручные циклы с предаллоцированными списками)
  • GetComponent<T>() каждый кадр → кэшировать в Awake/Start
  • new Vector3() и другие value types в некоторых паттернах — проверять Profiler
  • Boxing value types при передаче в object параметры

LOD и Culling

LOD Group — переключение на упрощённую геометрию при удалении объекта от камеры. Стандарт для 3D окружения: LOD0 (100%), LOD1 (30-50% треугольников), LOD2 (10-15%), Culled (объект невидим). Для мобильных порог Culled ставим агрессивнее — меньше рисуем за кадр.

Occlusion Culling — Unity не рендерит объекты за стенами и препятствиями. Требует запечённые occlusion данные (Window > Rendering > Occlusion Culling > Bake). Для открытых пространств эффект минимален, для indoor сцен — существенен.

Frustum Culling работает автоматически — объекты вне FOV камеры не рендерятся. Но draw call на проверку всё равно происходит. Для сцен с тысячами объектов — кастомный spatial partitioning (Quadtree, Octree) для ускорения culling-теста.

Оптимизация VR

VR — отдельный класс задач. Фреймрейт 72/90 Hz нельзя нарушать, иначе motion sickness. Дополнительно к стандартным методам:

  • Single Pass Instanced Rendering — рендер для обоих глаз за один проход (см. VR-раздел)
  • Fixed Foveated Rendering (Quest) — снижение разрешения на периферии
  • Late Latching (Quest 3) — обновление позиции контроллера максимально поздно перед рендером, снижает perceived latency
  • Dynamic Resolution в URP/HDRP — автоматическое снижение разрешения рендера при просадке fps

Для Quest профилируем через OVR Metrics Tool — показывает CPU/GPU time прямо в гарнитуре в рантайме, что удобнее чем профилирование через USB.