Баланс C++ и Blueprints в UE4
В продолжение моего прошлого поста:
В этот раз поговорим про С++ и Blueprints и о том, как они могут повлиять на память и производительность игры. И, как обычно, категорически приветствуется обсуждение статьи в комментариях! Делимся своим опытом и становимся лучше 😁
C++ vs Blueprints
Можно ли написать игру используя только Blueprints? Можно. Можно ли написать игру используя только C++? Можно. А что тогда выбрать? А выбирать не надо, надо использовать оба языка. Главное - правильно соблюсти баланс.
Можно писать на чем-нибудь другом? Конечно: Python, C #, SkookumScript, Lua и др. Вот только, за исключением питона - это неофициальные плагины. Да и на питоне можно писать код только для редактора, но не самой игры. Это плохо? С точки зрения эпиков - С++ и блюпринты покрывают все потребности разработчиков. Я с ними согласен. Вы - решайте сами.
С++ - сложнааааа!!! В общем случае - да 😃 Если говорить об UE4, то не сложнее C # в Unity. Если не лезть глубоко внутрь исходников движка, то ужасы С++ обойдут стороной. Основная сложность любого движка заключается в его структуре, в понимании взаимодействия его модулей. UE4 не исключение.
Особенности работы движка
- Если что-то подгружаем в С++ (ConstructorHelpers), то это будет загружено при старте модуля. Грубо говоря, в момент старта игры. Неважно, используется это или нет, создан этот объект или нет. И висеть все будет в оперативке или видеопамяти на протяжении всей работы модуля.
- Зависимости (references). Например, меш зависит от материала(ов), а материал зависит от текстур. Когда мы грузим меш, подтягиваются и все его зависимости. Ням-ням-ням и кирпичик из 8-ми вершин, тянет за собой пару-тройку текстур в 4К.
- Чтобы вызвать маленькую функцию из какого-нибудь блюпринта, движок загружает его весь. Благодаря зависимостям, это может потянуть за собой половину проекта, включая текстуры и звуки.
- Блюпринты сильно медленнее С++, даже нативизированные. Часто звучат такие цифры: от 7 до 30 раз.
C++ и Blueprints
Даже если вы решили писать игру только на Blueprints, создавайте именно С++ проект. В скором времени вы убедитесь, что многие вещи гораздо проще решить с использованием С++. А переконвертировать Blueprint проект в С++ не так и просто.
Первое и основное правило - если что-то можно написать на С++, пишите на С++. Основная прелесть в том, что зависимостей в С++ нет. Нужно вызвать метод класса - не проблема, нужно скастовать к другому типу - все хорошо, кастуем. Blueprints такое не прощают.
Одно условие - не надо в конструкторе загружать ассеты. Откуда взялось такое условие? Дело в том, что для каждого AActor автоматически создается его default версия. И, естественно, для нее вызывается конструктор.
Эм... а блюпринты куда? А блюпринты грузятся только тогда, когда они нужны. И в этом их прелесть - ассеты подтянутся, только тогда, когда блюпринт начали использовать (в другом блюпринте или поместили на уровень). Поэтому, часто разбивают код так: всю логику работы выносят в базовый класс на С++, а настройки, меши, материалы и эффекты - в блюпринты-наследники. Даже если этот блюпринт-наследник будет единственным.
Дальше я буду писать только о блюпринтах, потому что не всегда получается следовать "первому и основному правилу".
Блюпринт�� великолепны для прототипирования. Вот только с усложнением кода, становится все сложнее разбираться в получившейся "лапше". Как только идея устаканилась, старайтесь перенести ее на С++. Анализ кода, поиск и читаемость - не лучшие стороны блюпринтов:
Надо сводить к минимуму зависимость блюпринтов друг от друга. Зависимость Controller и Character обоснована - они не могут работать друг без друга. Но, ситуация, когда А тянет за собой В, который тянет C и D и все дальше и глубже, приведет к печальным последствиям. Причем простейший "Cast to" уже введет зависимость и начнет грузить другой блюпринт! Интерфейсы или взаимодействие на уровне С++ помогут разрулить ситуацию.
Циклические зависимости могут привести проект в полностью нерабочее состояние. У меня такое тоже было... Пришлось переписывать весь код игры.
Я написал блюпринт и хочу использовать его переменную или вызвать его функцию в С++. Это возможно? Если коротко, то: Да. Однако, по словам эпиков: "Если бы вы видели этот код, то никогда не захотели бы его использовать". Так что правильный ответ: Нет. Хотите что-то использовать в С++ из блюпринтов - объявите это в С++.
Производительность блюпринтов. Ее достаточно для игровой логики. Что-то сложнее, лучше выносить в С++. Крайне не рекомендуется делать блюпринты с Tick'ом. "Тикать" лучше на С++. С другой стороны, нет ничего страшного, если блюпринт изредка включает свой тик на короткое время, или тикает не каждый кадр, а пару раз в секунду. Также стоит помнить, что Tick - это просто таймер по умолчанию. Вынос "тикающего" кода в другие таймеры или Timeline не исправит ситуацию.
Все блюпринты, унаследованные от AActor, создаются по умолчанию с включенным Tick. Это можно поправить настройками проекта (Can Blueprints Tick by Default). Но лучше всего перепроверять вручну�� соответствующие галочки. Ну и консольная команда "dumpticks" в помощь.
Что еще посмотреть или почитать?
Я не рассказал про hard references и soft references. Исправляюсь тут:
Отличный пример того, как правильно сбалансировать С++ и Blueprints, показан в демо-проекте Action RPG:
И советую посмотреть эти видео:
PS: Еще, я не рассказал про блюпринты и совместную работу. Но это тема для отдельного разговора...