Как правильно оптимизировать игровой код
Александр Балакшин, работавший над Rainbow Six Siege в роли ведущего геймплей-программиста, объясняет, с чего лучше начинать оптимизацию кода, и какие места в нём лучше лишний раз не трогать.
Автор: Александр Балакшин
Как бы очевидно это ни звучало, в первую очередь оптимизировать нужно то, на что указывает профилировщик. Это специальная программа, которая измеряет потребление памяти и время выполнения тех или иных функций.
Данные о производительности нужно снимать сразу с целевых платформ. То, что на машине разработчиков уверенно показывает 60 кадров в секунду, устроит «слайд-шоу» на консолях. В этом плане самая удобная платформа — Xbox One. Если что-то пошло на ней, значит спокойно запустится и на PlayStation 4, и на рекомендуемой конфигурации ПК.
Unreal Insights — профилировщик для Unreal Engine (Источник)
В оптимизации ААA-проектов важно, чтобы код «дружил» с кэшем. То есть, код должен минимизировать загрузку из оперативной памяти, а ещё — эффективно использовать то, что уже находится в кэше процессора. При этом порой приходится думать не только о кэше данных, но и о кэше инструкций.
Например, разработчики Sea of Thieves смогли избавиться от проблем с кэшем инструкций с помощью агрегации вызовов tick-функции по типу обновляемых компонентов. Это ещё и помогло им поднять FPS с пяти кадров в секунду до приемлемых значений. Крайне интересный доклад на эту тему можно посмотреть здесь.
Источник
Прошлое поколение консолей плохо справлялось с branch prediction, поэтому программистов заставляли минимизировать количество ветвлений в коде — тех самых branch, — чтобы избежать потенциальных проблем с кэшем. Сейчас чем-то подобным приходиться заниматься только при написании шейдеров — из-за особенностей архитектуры современных GPU, — или при создании совсем уж высокооптимизированных систем самого движка .
Оптимизируя кэш данных, нужно внимательно следить за типами коллекций. Например, красно-чёрное дерево может быть гораздо менее эффективно, чем комбинация двух простых массивов, даже если задача — найти значение по ключу. Не стоит забывать и про выравнивание для структур и классов, особенно если те используются чаще всего.
Если приходится работать с объектно-ориентированными фреймворками, лучше использовать как можно меньше динамических приведений типов. В игровых движках обычно используется своя версия RTTI (runtime type introspection) вместо стандартной из С++, — но это всё равно очень ресурсозатратный процесс. Под него трудно рассчитать время, а потом ещё приходится разбираться с парой-тройкой кэш-промахов.
Когда целевой тип известен, лучше использовать статический каст. Но если без динамического приведения не обойтись, то имеет смысл сохранить результат хотя бы как переменную в функции. А ещё лучше — как член класса, если есть такая возможность. Кстати, именно поэтому стоит свести к минимуму получение компонент у того или иного объекта, если работать приходится с соответствующей моделью.
Создавая конкретные игровые функции, нужно контролировать количество и качество (синхронные/асинхронные) пространственных проверок — рейкастов (лучей для определения коллизий), шейпкастов, оверлап-тестов. В разработке современных игр они применяются достаточно часто: в стрельбе, перемещении, паркуре, инверсной кинематике и во многом другом.
Как правило, на такие проверки выделяется ограниченное количество ресурсов, — хотя, конечно, многое зависит от технологий и целевых платформ. Например, я работал над одной игрой, в которой допускалось не более 512 рейкастов за кадр.
Источник
В современных играх используются скелетные анимации. Работая с ними, нужно понимать и контролировать, когда «скелет» должен обновляться (например, если игрок видит врага), а когда это будет просто тратой мощностей (NPC находится в километре от героя, стоит за глухой стеной и вообще ничего не делает).
Обновление всех игровых компонентов можно делить на группы. Иногда каждый отдельный кадр требует запуск своего Update. В других случаях обновление достаточно производить раз в секунду — например, когда враги находятся далеко от игрока. А где-то его вообще можно отключить до активации нужных событий.
Кстати, в Unreal Engine 4 недавно добавили удобный функционал для таких задач — the significance manager. Он, наряду с функцией animation sharing, успешно доказал свою эффективность в Fortnite. На эту тему есть крайне интересное выступление с GDC 2018.
Для сетевых игр также нужно обращать внимание на объём реплицируемых данных. Когда на сервер отправляются 500 Кб в кадр, это кажется мелочью. Но при игре в 60 FPS трафик составит уже 30 Мб/сек (240 мегабит/сек) — и это если не считать репликации другим клиентам. А ведь дешёвый и быстрый интернет есть далеко не везде.
Процесс оптимизации включает в себя не только код, но и контент — он уже ложится на плечи художников. Им нужно следить за соответствием моделей и текстур бюджетам. Последние, в свою очередь, регламентирует технический арт-директор. А ещё нужно помнить о «сложности» скелетов и форме коллизий.
В коллизиях нужно использовать как можно более простые примитивы — капсулы, сферы и параллелепипеды (box’ы). Произвольные выпуклые многогранники лучше оставить до тех случаев, когда без них никак не обойтись. Если с ними не перебарщивать, то фаза обновления физики и определения пройдёт как по маслу.
Наконец, нужно помнить и о тех местах в коде, которые лучше не трогать. Для этого нужно опять обратиться к данным профилировщика. Бывает так, что какое-то место в коде работает идеально, но всё равно кажется неоптимизированным. Если тесты не выявляют в нём никаких проблем, то лучше не тратить на изменение кода неделю-другую, а оставить всё как есть. Получится экономия и личного времени, и денег работодателя. А это — самая главная оптимизация.
Если хочется сильнее углубиться в тему, можно посмотреть крайне интересный доклад Николаса Флёри на CppCon 2016 — там он рассказывает, как оптимизирована Tom Clancy’s Rainbow Six Siege. Ещё посоветую выступление Эрла Хаммонда на GDC 2018 года, — из него можно узнать о реализации определения столкновений в Titanfall 2. Это уже для тех, кто любит погорячее: в ролике присутствуют математика, векторизация и деление на 0.







Лига Разработчиков Видеоигр
10K пост23.4K подписчиков
Правила сообщества
ОБЩИЕ ПРАВИЛА:
- Уважайте чужой труд и используйте конструктивную критику
- Не занимайтесь саморекламой, пишите качественные и интересные посты
- Никакой политики
СТОИТ ПУБЛИКОВАТЬ:
- Посты о Вашей игре с историей её разработки и описанием полученного опыта
- Обучающие материалы, туториалы
- Интервью с опытными разработчиками
- Анонсы бесплатных мероприятий для разработчиков и истории их посещения;
- Ваши работы, если Вы художник/композитор и хотите поделиться ими на безвозмездной основе
НЕ СТОИТ ПУБЛИКОВАТЬ:
- Посты, содержащие только вопрос или просьбу помочь
- Посты, содержащие только идею игры
- Посты, единственная цель которых - набор команды для разработки игры
- Посты, не относящиеся к тематике сообщества
Подобные посты по решению администрации могут быть перемещены из сообщества в общую ленту.
ЗАПРЕЩЕНО:
- Публиковать бессодержательные посты с рекламой Вашего проекта (см. следующий пункт), а также все прочие посты, содержащие рекламу/рекламные интеграции
- Выдавать чужой труд за свой
Подобные посты будут перемещены из сообщества в общую ленту, а их авторы по решению администрации могут быть внесены в игнор-лист сообщества.
О РАЗМЕЩЕНИИ ССЫЛОК:
Ссылка на сторонний ресурс, связанный с игрой, допускается только при следующих условиях:
- Пост должен быть содержательным и интересным для пользователей, нести пользу для сообщества
- Ссылка должна размещаться непосредственно в начале или конце поста и только один раз
- Cсылка размещается в формате: "Страница игры в Steam: URL"