Топ 17 практик оптимизации игры
Игрокам нравится играть в хорошие игры которые не тормозят на их железе. Но разработчики зачастую совершают одни и те же ошибки, которые ведут к низкому фпс (частота кадров в секунду) в игре.
Чтобы игра не тормозила, нужно её оптимизировать!
- Профайлинг (Profiling)
Самое важное при тестировании - это измерение показателей. Что именно в игре работает хорошо, а что именно тормозит и почему? Изучите в официальной документации какие вам доступны профайлеры, и как с ними работать.
В движке Unreal Engine самый полезный профайлер - Unreal Insights.
https://docs.unrealengine.com/5.3/en-US/testing-and-optimizing-your-content/
https://docs.unrealengine.com/5.3/en-US/unreal-insights-in-unreal-engine/
- Настройка среды под тесты производительности
Во время профайлинга вы будете сравнивать старые и новые результаты измерений.
Чтобы было проще замерять показатели рендера, можно расставить на карте (Map/Level) в нескольких местах Камеры (Camera Actor), и записывать показатели с этих камер. В итоге у вас будут максимально достоверные результаты измерений нагрузки на GPU.
Для замера нагрузки на CPU, имеет смысл изолировать объекты/акторы (Blueprint Actor) на отдельной не-игровой карте. В этом варианте на CPU не будут влиять другие системы игры/другие блюпринт-акторы, и вы получите точные замеры производительности.
В мультиплеер-играх можно записывать повторы (replays) и использовать их для профилирования. С таким подходом не нужно будет лишний раз собирать игроков онлайн для сетевого теста.
- Задумывайтесь об оптимизации с первого дня разработки
Если вы задумали, что игра будет работать в 60 фпс на консолях или ПК, то на протяжении всего процесса разработки нужно стремиться к этому показателю. Не обязательно всегда держать частоту кадров на уровне 60 кадров в секунду, но рекомендуется стремиться хотя бы к 55 кадрам на ранних стадиях разработки. Это даст уверенность в том, что игра точно будет хорошо работать, и позволит вам избежать кранчей на финальных стадиях разработки.
- «Сколько всего» в игре
В начале разработки игры обычно определяют количество «сколько всего» в игре будет главных героев, персонажей одновременно на экране, интерактивных и других объектов.
Если персонаж всего один, то не смысла в лишней оптимизации. Но когда на экране одновременно дерутся 10 персонажей, тогда нужно быть осторожным в использовании слишком детальной анимации, слишком детальной физике и слишком непроизводительном коде.
Если в игре планируется 100 персонажей, то об этом нужно знать и помнить с самого начала разработки. В этом случае нельзя откладывать оптимизацию на потом. Если персонажей в игре тысячи, то разработку игры сразу нужно делать на C++, а не на Блюпринтах.
- Scene Components - тяжёлые
Каждый тик в игре происходит обновление всех Акторов, Компонентов и их дочерних Компонентов. Будьте осторожны с большими иерархиями компонентов.
Очень часто разработчики навешивают на машину и на модульного-персонажа кучу компонентов, а потом удивляются, что производительность упала.
Иногда стоит рассмотреть возможность объединения Акторов, Мешей (Static Mesh) и Скелетал-мешей (Skeletal Mesh) с помощью инструментов объединения. В Unreal Engine для этого есть Merge Tool.
https://docs.unrealengine.com/5.3/en-US/merging-actors-in-unreal-engine/
- Избегайте в Блюпринтах функции Tick
Функция Tick в Блюпринтах значительно медленнее, чем в C++. Например, 100 пустых Акторов, в которых ничего не происходит, просто так тратят 1 миллисекунду процессорного времени. Если нужен Актор с функцией Tick, то желательно чтобы класс этого Актора был написан на C++. В Блюпринтах можно пользоваться Таймерами или Таймлайнами (Timeline) для временных эвентов (Time Events).
- Включайте/отключайте Tick во время игры
Во время игры нам не всегда нужны «тикающие» Акторы. Убедитесь что Акторы и их Компоненты «тикают» только тогда, когда это действительно нужно. Пользуйтесь функцией SetActorTickEnabled.
- Снижайте Tick Rate
Большинству Акторов в игре ну нужно «тикать» раз 60 в секунду. Многие из них прекрасно работают и с более низким Tick Rate. Этот параметр можно менять прямо во время игры. Хорошей практикой будет снизить Tick Rate до 2 раз в секунду, пока объект не «активируется», тогда можно увеличить Tick Rate до максимума.
Например, Актор - Дверь или Лифт. Пока этот Актор находится в состоянии покоя, у него Tick Rate = 2. Когда мы подошли к этому Актору, пересекли его коллизию, то у него Tick Rate = 0, что значит Актор «тикает» на полной скорости.
- LOD для логики
Если вы работаете над ролевой игрой, то хорошая практика - иметь значительно упрощенного Актера для ваших NPC. Когда игрок приближается, он заменяется на правильного актера NPC с хорошей анимацией и более сложной логикой. Также рассмотрите возможность отключения физики движущихся объектов, которые находятся слишком далеко, чтобы игрок мог на них повлиять.
- «Не работай, если не смотрят»
Подумайте о том, чтобы проверить, влияет ли что-то на игрока, и просто не делайте этого, если игрок этого не увидит. Объекты, находящиеся далеко от игрока, не должны создавать эффекты частиц или звуки, поскольку игрок их не увидит и не услышит.
- Упрощение репликации
Сеть движка Unreal собирает объекты, находящиеся рядом с игроком, а затем проверяет их на наличие измененных свойств. Стоимость проверки измененных свойств во многом зависит от количества Акторов/Компонентов, а не от количества реплицируемых свойств. Лучше иметь PlayerCharacter со 100 реплицируемыми свойствами вместо PlayerCharacter + 9 компонентов по 10 реплицируемых свойств каждый. Это противоречит хорошей архитектуре, но нет хорошего способа обойти это.
https://cedric-neukirchen.net/docs/category/multiplayer-network-compendium/
- Пул Акторов
В Unreal Engine создание (Spawn) Акторов очень медленный. Если для снарядов/пуль и взрывов постоянно Спавнить Акторы, то это создаст слишком большую нагрузку на Сборщик Мусора (Garbage Collector, GC).
Сборка мусора в Unreal Engine: https://habr.com/ru/articles/661469/
- Аккуратнее с анимацией
Анимация в Unreal Engine - это известный Боттлнэк (узкое место, Bottleneck). Когда смешиваете анимации в АнимБП (AnimBP, Animation Blueprint), следите чтобы использовался быстрый путь (маленький значок молнии). Предпочитайте использовать State Machine вместо смешивания анимации через Blend.
https://docs.unrealengine.com/5.3/en-US/animation-blueprints-in-unreal-engine/
- Избегайте Overlap-events для движущихся объектов
Каждый компонент у которого включены Оверлап-события (Overlap-events, одна коллизия перекрывает другую коллизию), будет проверять физические перекрытия каждый раз при движении. Такие события могут срабатывать несколько раз за время 1 Draw Call.
Основное использование Оверлап-событий — для триггеров, которые определяют персонажа игрока.
Предпочитайте использовать трассировки лучей/сфер, вместо Оверлапов для снарядов/пуль и оружия, где это возможно.
- Минимизация количества трансформаций
Каждый раз, когда вы вызываете SetLocation или SetRotation, Unreal Engine обновляет всю цепочку преобразований, и если есть физические объекты, он обновляет физику. Необходимо стремиться к строго ОДНОМУ вызову SetLocation или связанным с ним вызовам на каждый кадр движущихся объектов.
Движок Unreal имеет вызовы SetTransform и SetActorLocationAndRotation, которые позволяют изменять вращение и местоположение за один вызов вместо двух. Вызов SetActorLocationAndRotation в два раза быстрее, чем вызов SetLocation и SetRotation по отдельности.
- Объединение логики Акторов в Менеджеры
Наличие одного Актора-Менеджера, который управляет группой других Акторов, зачастую быстрее, чем наличие у них собственной логики.
Если у вас будет большое количество чего-либо, вероятно, будет хорошей идеей объединить их с помощью какого-нибудь «менеджера», который будет обновлять все объекты. Отличные кандидаты для группировки в Менеджер: снаряды, показатели урона или некоторые части логики ИИ. Это хорошо сочетается с идеей «LOD для логики».
- Граф репликации в Мультиплеере
Граф репликации (Replication Graph) сделали для оптимизации производительности сервера в Fortnite. Он позволяет вам переопределить логику «какие объекты имеют отношение к этому игроку». Благодаря этому, можно динамически изменять скорость обновления сети для каждой зоны на карте. И определить какие объекты находятся в диапазоне репликации.
В ShooterGame есть его полная реализация. В проекте можно найти интересные вещи, например, граф репликации ограничивает частоту обновления PlayerInfo. При каждом обновлении отправляются только данные двух игроков.
https://docs.unrealengine.com/4.27/en-US/Resources/SampleGames/ShooterGame/
При правильном использовании графа репликации вы сможете сделать игру на 200 или более игроков в многопользовательской игре.
https://docs.unrealengine.com/5.3/en-US/replication-graph-in-unreal-engine/
---
Оригинальная статья: https://dev.epicgames.com/community/learning/tutorials/3o6/expert-s-guide-to-unreal-engine-performance
