1. Архитектура UE5 C++: модули, сборка, UHT и макросы отражения
Архитектура UE5 C++: модули, сборка, UHT и макросы отражения
UE5 — это не просто C++ проект с библиотеками, а целая экосистема со своей системой сборки, генерацией кода и отражением (reflection). Если воспринимать её как обычный CMake/Visual Studio проект, вы быстро упрётесь в загадочные ошибки линковки, невозможность подключить типы из соседнего модуля или «почему UPROPERTY не работает».
В этой статье разберём:
Ментальная модель: что именно вы компилируете в UE
В UE проект обычно состоит из нескольких модулей:
Модуль в UE — это единица компоновки и зависимостей: он превращается в отдельную библиотеку (или часть общей сборки в зависимости от режима), имеет явные зависимости и правила сборки.
Официальная точка входа в тему модулей: Unreal Engine Modules.
!Схема зависимости модулей в типичном UE-проекте
Unreal Build Tool: кто реально управляет сборкой
Unreal Build Tool (UBT) — это система сборки UE, которая:
.Build.cs и .Target.csДокументация: Unreal Build System.
Важные файлы сборки
ProjectName.uproject — описание проекта и его модулей/плагиновSource/<ModuleName>/<ModuleName>.Build.cs — правила сборки модуляSource/<ProjectName>.Target.cs — правила сборки таргета (например, Game)Source/<ProjectName>Editor.Target.cs — правила сборки таргета редактораЧто такое Target и почему их обычно минимум два
Target — это конфигурация сборки конечного приложения.
Чаще всего у проекта есть:
ProjectName (Game Target) — для запуска игрыProjectNameEditor (Editor Target) — для работы в редактореУ них разные зависимости и разные наборы модулей: редактору нужны Editor-модули, а packaged-игре — нет.
Unreal Header Tool: зачем нужен отдельный генератор кода
UE использует собственную систему отражения (reflection), чтобы:
Для этого движку нужно знать метаданные о ваших классах, структурах, свойствах и функциях. C++ сам по себе не даёт UE безопасно и переносимо извлечь эти метаданные на этапе компиляции, поэтому используется UHT — отдельный инструмент, который парсит заголовки и генерирует вспомогательный C++ код.
Документация: Unreal Header Tool.
Общий пайплайн: UBT → UHT → компилятор
Build.cs/Target.cs.Intermediate/Build/... и создаёт *.generated.h.!Процесс сборки: как UBT и UHT участвуют до компилятора C++
Ключевое правило: *.generated.h подключается последним
Если в заголовке есть UCLASS/USTRUCT/UENUM, то почти всегда должен быть:
#include "YourType.generated.h" и он должен идти последним include в файлеЭто требование связано с тем, как UHT вставляет и ожидает увидеть определённые объявления/макросы.
Система отражения: что она даёт и что требует
Отражение в UE — это механизм, позволяющий движку в рантайме знать структуру ваших типов, их свойства и функции.
Документация: Reflection System.
Главные макросы отражения
UCLASS(...) — класс UObject/Actor, видимый системе отраженияUSTRUCT(...) — структура, видимая отражению (например, для DataTable)UENUM(...) — enum, видимый отражению (например, для Blueprint)UPROPERTY(...) — свойство, видимое отражению (редактор, сериализация, репликация)UFUNCTION(...) — функция, видимая отражению (Blueprint, RPC, события)GENERATED_BODY() — точка вставки кода, сгенерированного UHTМинимальный пример класса, который понимает UHT
Что здесь важно:
UCLASS() делает класс участником системы UObjectMYGAME_API управляет экспортом символов модуля (об этом ниже)GENERATED_BODY() обязателен, иначе UHT не сможет подставить нужные объявленияUPROPERTY/UFUNCTION добавляют метаданные для редактора/Blueprint/сериализацииЧто не делает отражение автоматически
Build.csМодули UE: границы кода, зависимости и API
Модуль задаёт:
Папки Public и Private
Типичная структура модуля:
Source/MyModule/Public — заголовки, которые разрешено включать другим модулямSource/MyModule/Private — реализация и приватные заголовкиПрактический смысл:
PublicPrivateModule API macro: MYMODULE_API
MYMODULE_API (например MYGAME_API) — это макрос экспорта/импорта символов при сборке модулей.
MYMODULE_API экспортируютсяИменно поэтому публичные классы, которыми пользуются другие модули, должны иметь ..._API.
Типы модулей: Runtime и Editor
Самое частое разделение:
Правило:
Иначе упрётесь в невозможность собрать/упаковать игру.
Файл *.Build.cs: как правильно описывать зависимости
<ModuleName>.Build.cs — C#-класс, который описывает правила сборки конкретного модуля.
Ключевая идея:
Минимальный пример:
PublicDependencyModuleNames vs PrivateDependencyModuleNames
Смысл не в «мне так удобнее», а в том, куда просачиваются include и типы:
PublicDependencyModuleNames — если типы/заголовки зависимого модуля используются в ваших публичных заголовкахPrivateDependencyModuleNames — если зависимость нужна только для .cpp или приватных заголовковПрактическое правило:
Public, почти всегда нужна PublicDependencyModuleNamesЧастая ошибка: «у меня компилируется локально, но ломается у коллеги/на CI»
Причины обычно такие:
Build.cs, и сборка прошла лишь из-за косвенных зависимостейЛечение:
Target.cs: как включать/выключать возможности на уровне приложения
*.Target.cs управляет настройками таргета: тип (Game/Editor/Server), некоторые флаги сборки, включение модулей и т.д.
У вас обычно минимум два таргета, потому что редактор — отдельное приложение со своими требованиями.
Документация по смежной теме (сборочная система в целом): Unreal Build System.
Практический пример: добавляем новый модуль и подключаем его
Сценарий: вы хотите вынести часть кода (например, боевую систему) в отдельный Runtime-модуль Combat.
Шаги на уровне структуры
Source/Combat.Combat.Build.cs.Public/ и Private/.Private/CombatModule.cpp с реализацией.Пример CombatModule.cpp:
Подключение из игрового модуля
Если в MyGame вы хотите использовать публичные заголовки Combat, добавьте зависимость:
И убедитесь, что классы, которые вы используете снаружи, помечены COMBAT_API.
Live Coding, Hot Reload и почему архитектура модулей важна для итерации
UE позволяет быстро пересобирать код, но разные режимы ведут себя по-разному:
Когда вы активно работаете с отражением (добавляете UPROPERTY, меняете сигнатуры UFUNCTION), чаще требуется более «чистая» пересборка, потому что меняется сгенерированный код.
Практика отладки сборки:
Binaries/ и Intermediate/Типовые ошибки и как их избегать
#include "X.generated.h" не последний include в заголовкеGENERATED_BODY() внутри UCLASS/USTRUCTPublic-заголовке, но зависимость указана как PrivateDependencyModuleNames..._API, из-за чего появляются ошибки линковки при использовании в другом модулеИтоги
UCLASS/UPROPERTY/UFUNCTION — контракт с UHT и системой UObjectPublic/Private и корректные зависимости в Build.cs — основа масштабируемой кодовой базыДальше по курсу логично углубляться в то, как UObject-жизненный цикл, GC и подсистема World/Actor/Component опираются на отражение и модульную архитектуру.