- Ещё о проектировании (движки и история) — про идею разделения игрового объекта на части “для программистов” и “для гейм-дизайнеров”.
Схема создания разделённых типов в Unreal Engine выглядит приблизительно так:
C++ <—> Blueprints#
Общая схема#
- Исходный код обрабатывается препроцессором
Unreal Header Tool
(DSL), который генерирует метаинформацию для системы рефлексии - Код на C++ собирается в exe-файл редактора/игры
- Код на С++ также может собираться в динамическую библиотеку, которая загружается уже после запуска редактора
- После запуска редактор загружает мета-информацию о типах (Reflection data), и может использовать для визуального отображения и редактирования типов
- Runtime geneated reflection data — новые типы могут быть созданы в рантайме, с помощью наследования от уже существующих типов и определения/переопределения новых полей, и кода из языка Blueprints (который компилируется в байткод виртуальной машины)
Некоторые неотмеченные на схеме связи:
- Нативизация скриптов (вместо компиляции в байт-код происходит транспиляция в C++ код)
- Типы могут использоваться не в качестве базовых для настройки и расширения из скриптов Blueprint, а ради расширения редактора (макросы) или самого языка Blueprint (регистрация новых типов нод)
Building Tools Quickly: Blueprints, Menus, Utilities, and Widgets — доклад про различные способы расширения редактора Unreal от Embark Studios
Рефлексия#
Поскольку C++ не имеет встроенных средств рефлексии, движки изобретают свои средства для описания метаинформации. Так как часто уже существует основа движка на C++, выбирается вариант с добавлением разметки в сам C++ код, и стадии препроцессинга перед компиляцией (Unreal Header Tool
в случае с Unreal), на которой разбирается метаинформация и генерируется мета-класс и код работы с ним.
Кроме генерация мета-класса в виде C++-типа, необходим способ создания типов в ран-тайме — генерация кода регистрации типа в системе рефлексии времени выполнения.
Blueprints vs C++ — статья с разбором системы рефлексии, а также способов ссылок на типы C++->Blueprint и Blueprint->C++
Unreal Engine C++ Complete Guide — гайд по DSL для описания метаинформации, а также других макросов Unreal (Unrealisms
)
- С++ в геймдеве — раздел “Высокоуровневый C++”, про ограничения языка в рефлексии, кодогенерации, compile-time вычислениях
- C++ Metaprogramming - ссылки — возможности мета-программирования
Привязка текстового скриптового языка#
Выбор между визуальным/текстовым программированием#
Выбор можно типом скриптового языка (визуальный/текстовый) зависит от конфигурации команды (кто будет пользоваться этим языком).
- По статистике от Epic — до 80% кода в больших проектах переписывается с Blueprint на C++ (линк).
Т.е. с визуальным языком вместо идеи “делаем игру без программистов” получается “программисты регулярно занимаются переписыванием логики игры на нативный язык”. Текстовые языки удобнее в плане того, что могут быть быстрее визуальных, и ближе к натуральному представлению программистов (переписывать с текстового скрипта на C++ проще, чем с Blueprint).
Скриптовый язык должен быть быстрым, или хотя бы иметь возможности для ускорения (транспиляция/jit/профайлер/управление памятью/быстрый интероп с C++/примитивные типы/опции оптимизаций компиляции), чтобы минимизировать необходимость переписывания.
- Дизайн некоторых игр может подразумевать наличие большого количества level one-offs (мест с кастомной логикой), что требует большого объёма кода. Объёмные Blueprint-скрипты нечитаемы.
Из старых примеров игр, в которых уровней с кастомной логикой больше, чем основной — Earth World Jim 2, BattleToads, сборники мини-игр (Mario Party, Adventures in The Magic Kingdom, Tiny Toon 2, симуляторы олимпиад и многоборий). Из новых — It Takes Two
. Разработчики выбрали для этого AngelScript.
Текстовый язык не заменяет полностью Blueprint, небольшие кусочки всё равно удобнее реализовывать в виде Blueprint-скриптов, чтобы сохранять возможность использовать уже накопленные Unreal-разработчиками приёмы. Т.е. язык должен быть интегрирован не только с C++, так и с системой рефлексии и виртуальной машиной Blueprint.
В идеале, язык должен быть не хуже Blueprint в том, в чем силён Blueprint, и не хуже C++ в том, в чём хорош C++
Группы скриптовых языков#
Lua/AngelScript/Wren/Squirrel
Lightweight-языки с простой виртуальной машиной, и небольшой стандартной библиотекой, дополнительные фичи могут реализовываться дописыванием функционала на C++.
Аддоны AngelScript, препроцессор UnrealAngelScript на 4к строк.
C#/Python/JavaScript
Языки с тяжелой виртуальной машиной, развесистой стандартной библиотекой и большим количеством готовых 3rdParty библиотек.
Odin/Jai/daScript/Nim/Haxe
Языки с gamedev-related фичами. Рефлексия, настройка управления памятью, компайл-тайм eval и макросы, транспиляция в C++, выбор между исполнением байт-кода и компиляцией, jit-компиляция (полный набор не поддерживает ни один язык из списка, но все поддерживают хотя бы несколько).
Эта группа представляет наибольший интерес, потому что потенциально может заменить не только Blueprint, но и большую часть C++.
C++ <—> Nim <—> Blueprint#
NimForUE — привязка Nim к Unreal Engine, в разработке.
Общая схема#
Макросы позволяют написать на Nim DSL описания метаинформации.
Код на Nim может компилироваться в статическую библиотеку, как и код на C++. Также существует возможность собрать код в DLL для хот-релоадинга и транспилировать в C++.
Для рантайм выполнения без компиляции существует формат интерпретации байт-кода виртуальной машиной NimVM.
Задачи NimForUE#
- Привязки методов C++ для динамического добавления типов в систему рефлексии Unreal (создание модуля, типа, полей и функций в нём, информирования рантайма о новых типах)
- Привязки базовых типов Unreal, строк, указателей, контейнеров, делегатов и вспомогательных классов системы рефлексии (UObject, UClass, UFunction, TSubclassOf, TSoftObjectPtr)
- Соответствие базовых типов языки типам системы рефлексии, идиоматические для nim хелперы вокруг unreal-типов
- Обёртки вокруг конвенций Unreal - конструкторов, создания компонент, статических данных и связью с системой сборки мусора, Out-параметры для Blueprint-нод
- Базовая инфраструктура Nim — виртуальная машина, мониторинг библиотеки для hot-reload, тесты и отладка
- DSL для создания типов, похожий на тот, который используется Unreal Header Tool, прокидка типов и флагов описания метаинформации
- Генерация привязок для всех типов системы рефлексии
- Генерация привязок для типов C++ (и допривязка свойств/функций), которые не добавлены в систему рефлексии, но необходимы для удобной работы со скриптами
- Override свойств и функций системы рефлексии (наследование от Blueprint-типов)
- Override для C++ функций (генерацией c++-типа обёртки)
- Разделение кода обёрток игры/редактора, поддержка всех платформ
Способы связи между языками#
Nim <--> Cpp
Nim может описать используемый метод и тип из C++ из самого языка (pragma importcpp). Аналогично код на C++ может описать прототип extern функции, которая находится в lib-файле, собранном из nim-кода.
Для динамической привязки — стандартные для ОС методы вызова функций из библиотек (со стороны Nim — обмазанные макросами).
Nim <--> Blueprint
Связь через систему рефлексии (Emit Type + Broadcast Asset -> Blueprint знает о типе).
UFunctionCaller — вызов зарестрированной функции по имени.
Пример сгенерённой привязки функции