IO — Ivan Labs

Оптимизация Unity-игры под слабое железо

3 мин чтения
UnityГеймдев

У оптимизации Unity длинный список возможных техник, но большая часть реального выигрыша производительности приходит от короткого списка, применённого правильно.

Сначала профилируйте — не гадайте насчёт узкого места

Оптимизация без предварительного профилирования — один из самых частых способов потратить время на изменения, не сдвигающие стрелку. Встроенный Profiler Unity показывает точно, куда реально уходит время кадра — логика процессора, рендеринг, физика или сборка мусора — прежде чем вы что-либо тронете.

Исправления с наибольшим эффектом, примерно по порядку

  1. Сократите draw call. Батчинг (статический батчинг для неподвижных объектов, динамический батчинг или GPU instancing для движущихся) и текстурный атлас (объединение нескольких текстур в одну) сокращают количество отдельных инструкций рендеринга, которые должен выдать процессор — обычно самый большой единичный выигрыш для более слабого железа.
  2. Пулинг объектов для всего, что часто порождается. Instantiate() и Destroy() относительно дороги; переиспользование пула заранее созданных объектов (пули, частицы, враги) вместо постоянного создания и уничтожения избегает как этой стоимости, так и связанного давления на сборку мусора.
  3. Сократите давление на сборку мусора. Частые маленькие аллокации (особенно в Update(), выполняющемся каждый кадр) вызывают паузы сборки мусора, проявляющиеся как подёргивание. Кэширование переиспользуемых объектов и избегание аллокаций в горячих путях (вроде поэкадровой конкатенации строк) значительно это сокращает.
  4. Понизьте частоту обновления физики там, где точность не критична. Не каждому объекту нужна частота фиксированного шага по умолчанию — менее критичные физические взаимодействия иногда могут работать на пониженной частоте без заметной разницы.
  5. LOD (уровень детализации) и отсечение. Снижение детализации на дальних объектах и нерендеринг того, что вне экрана, напрямую сокращает поэкадровую работу рендеринга.
// Наивно: аллоцирует каждый кадр
void Update() {
    string status = "HP: " + currentHp; // новая аллокация строки каждый кадр
}
 
// Лучше: обновляет только когда значение реально меняется
void OnHealthChanged(int newHp) {
    statusText.text = $"HP: {newHp}"; // аллоцируется только когда это реально срабатывает
}

Что конкретно проверять на слабом железе

СимптомВероятная причина
Стабильно низкая частота кадровСлишком много draw call, или чрезмерно сложные шейдеры
Периодическое подёргивание/зависанияВсплески сборки мусора от частых аллокаций
Медленное время загрузкиНесжатые или слишком большие текстуры/аудио
Падения кадров во время боя/частицОтсутствие пулинга объектов для порождаемых эффектов

Компромисс, который легко переисправить

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

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

Частые вопросы

Оптимизировать по ходу дела или ждать до конца?

В основном ждать — преждевременная оптимизация систем, которые могут быть вырезаны или переработаны, тратит усилия впустую. Исключение — архитектурные решения (пулинг объектов, паттерны draw call), которые дорого встраивать позже — их стоит решать рано, даже если вы ещё не полностью их реализуете.

Какая единственная оптимизация с наибольшим эффектом для большинства Unity-игр?

Сокращение draw call, обычно через батчинг (статический или динамический) и текстурный атлас — это решает самое частое узкое место (процессор тратит слишком много времени на выдачу инструкций рендеринга), прежде чем трогать что-то более экзотическое.

Стоит ли пулинг объектов дополнительной сложности?

Для всего, что часто порождается и уничтожается — пули, частицы, враги — да, почти всегда. Вызовы Instantiate/Destroy относительно дороги, и частое порождение — один из самых частых реальных виновников проблем с производительностью.

Нужна помощь с этим?

Свяжитесь со мной, и я помогу разобраться.

Связаться со мной

Похожие статьи

Поделиться:X / TwitterLinkedIn