用途
本技能用于指导虚幻引擎(Unreal Engine)C++ 项目中模块(Module)的结构设计与构建系统配置。每个虚幻 C++ 功能都位于一个模块中:该模块目录在模块化构建下编译为独立 DLL,在单体(monolithic)构建下编译为静态库。UnrealBuildTool(UBT)读取每个模块一份的 *.Build.cs(C# 风格 ModuleRules)和每个构建目标一份的 *.Target.cs(TargetRules),据此决定编译与链接内容。资料指出,多数“无法编译/无法链接”问题实质是模块依赖配置错误。需注意:UE 模块与 C++20 语言模块无关。
适用场景
- 为项目或插件新增模块,或将不断增大的模块拆分为更小单元。
- 排查链接/包含错误:未解析的外部符号(*_API 符号)、cannot open include file、启动时 module X not found。
- 新增依赖(例如使用了 UEnhancedInputComponent 而需要 EnhancedInput 模块)。
- 判断使用 PublicDependencyModuleNames 还是 PrivateDependencyModuleNames,以及加载阶段与宿主类型的选择。
- 理解 UBT 如何发现模块,以及何时需要重新生成项目文件。
核心概念
- 模块 = Source/<Module>/ 下的文件夹,包含 <Module>.Build.cs,以及 Public/(供其他模块包含的头文件)和 Private/(实现与内部头文件)。
- <MODULE>_API 宏(如 MYGAME_API)在模块化构建中展开为 __declspec(dllexport/dllimport),在单体构建中为空。其他模块使用的类若缺少该宏,会导致 unresolved external symbol。
- 使用其他模块 API 必须同时满足两点:(1) #include 其公开头文件;(2) 在 Build.cs 依赖列表中列出该模块。
- Target(*.Target.cs)描述一个可执行目标:以哪些模块启动,以及以单体还是独立 DLL 方式链接。
- UBT 构建时不读取 IDE 解决方案,只读取 Build.cs/Target.cs;新增、移动或重命名源文件后需重新生成项目文件。
模块布局与包含路径
典型布局为 Source/MyGame/ 下含 MyGame.Build.cs、Public/(如 MyActor.h)、Private/(.cpp 与仅内部头文件,IMPLEMENT_PRIMARY_GAME_MODULE 放在 MyGameModule.cpp)。#include 路径从 Public/Private 根解析,而非磁盘路径:Public/Weapons/Gun.h 以 #include "Weapons/Gun.h" 包含;这两个规范目录之外的文件自动视为私有。
Build.cs(ModuleRules)要点
每个模块在其根目录有且仅有一个 <Name>.Build.cs,UBT 在构建时编译它。示例中设置 PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs(IWYU 合规所需);把类型出现在本模块公开头文件中的模块放入 PublicDependencyModuleNames(如 Core、CoreUObject、Engine、InputCore);仅在 .cpp 或私有头文件中使用的类型放入 PrivateDependencyModuleNames(如 EnhancedInput、UMG、Slate、SlateCore),以加快构建并精简 API;仅编辑器依赖用 if (Target.bBuildEditor) 加以限制,避免打包膨胀。模块名即包含 Build.cs 的文件夹名(或引擎模块名)。启用 IWYU 安全预编译头后,每个 .cpp 须先包含其对应 .h,且不得使用 Engine.h 这类单体头文件。
Target.cs(TargetRules)要点
项目通常有两个:<Project>.Target.cs(Game)与 <Project>Editor.Target.cs(Editor)。示例设置 Type = TargetType.Game、DefaultBuildSettings = BuildSettingsVersion.V7、IncludeOrderVersion = EngineIncludeOrderVersion.Latest,并通过 ExtraModuleNames 添加主游戏模块。TargetType 取值包括 Game(cooked 单体)、Editor(模块化并含编辑器 DLL)、Server、Client、Program。编辑器目标以模块化方式链接;Game/Server/Client 在多数平台上以单体方式链接。
在 C++ 中注册模块
每个模块需在单个 .cpp 中放且仅放一个注册宏:主游戏模块(名称与 .uproject 匹配)用 IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultGameModuleImpl, MyGame, "MyGame");次级模块或插件模块用 IMPLEMENT_MODULE(FDefaultModuleImpl, MySecondaryModule)。FDefaultModuleImpl 是空的 IModuleInterface,启动/关闭时不做任何事;FDefaultGameModuleImpl 继承自它并让 IsGameModule() 返回 true。若需启动/关闭钩子,可继承 IModuleInterface 并重写 StartupModule() / ShutdownModule();在 StartupModule 中可用 FModuleManager::Get().LoadModuleChecked(TEXT("MyDep")) 加载依赖模块。
在 .uproject / .uplugin 中声明
Modules 数组中每项含 Name、Type、LoadingPhase。Type 取值(ModuleHostType):Runtime、RuntimeNoCommandlet、Editor(仅编辑器,打包游戏中剔除)、EditorNoCommandlet、Developer/DeveloperTool、ServerOnly/ClientOnly、UncookedOnly、Program。LoadingPhase 取值(ModuleLoadingPhase):EarliestPossible(GConfig 就绪后尽快)、PostConfigInit、PreLoadingScreen、PreDefault、Default(游戏模块加载后,最常用)、PostDefault、PostEngineInit、None(不自动加载)。多数游戏代码用 Default;提供工厂或被其他模块所需类型的插件模块常用 PreDefault;若编辑器持续报“class not found”,可尝试 PreDefault。
新增模块清单
- 创建 Source/<New>/ 及 Public/、Private/ 子目录。2. 添加继承 ModuleRules 的 <New>.Build.cs 并列出依赖。3. 添加 Private/<New>Module.cpp,含 IMPLEMENT_MODULE(FDefaultModuleImpl, <New>)。4. 在 .uproject/.uplugin 的 Modules 数组中加入模块条目。5. 在消费该模块的其他模块依赖列表中加入 <New>。6. 重新生成项目文件(右键 .uproject → Generate Visual Studio Project Files)。7. 构建。
<MODULE>_API 导出宏规则
MYGAME_API 在编译该模块时展开为 __declspec(dllexport),被其他模块包含时展开为 __declspec(dllimport),单体构建中为空。可加在 class 关键字或单个函数上(如 class MYGAME_API UMyComponent、MYGAME_API void MyGlobalHelper())。规则:其他模块访问的任何类、函数或数据符号都需应用该宏;内部类与嵌套类型若被外部使用也需自己的 _API;不要用于模板类体(模板为头文件内实现);遗漏会导致链接时 unresolved external symbol。
常见问题
跨模块使用的类缺少 <MODULE>_API 会链接失败;忘记在 Build.cs 添加模块会出现 cannot open include file(即使头文件在磁盘上存在)或未解析外部符号;循环模块依赖会导致链接失败,应把共享类型抽到更低层模块;运行时模块引用编辑器模块会破坏打包,编辑器代码应放在 Editor 类型模块并用 #if WITH_EDITOR 保护;修改 Build.cs 或移动源文件后需重新生成项目文件再构建,仅靠 Live Coding 重载不够;在 StartupModule 中使用 FModuleManager::LoadModuleChecked 可确保依赖在使用前已加载,仅依赖加载顺序可能产生间歇性崩溃;发布版为单体构建、_API 为空,但缺少 _API 的代码会在编辑器(模块化)构建中失败,因此应始终正确使用宏。
版本说明
UE 5.8 中推荐 BuildSettingsVersion.V7(TargetRules.cs:177),引擎生成的新项目使用 V7(= Latest);EngineIncludeOrderVersion.Latest(= Unreal5_8)在 UE 5.8(TargetRules.cs:242)。ModuleRules.cs 中 bRequiresImplementModule 属性(默认 true)在链接时强制要求存在 IMPLEMENT_MODULE 宏。
关联与参考资料
关联技能包括 ue-plugins-and-modules(在 .uplugin 中打包模块、插件模块与项目模块)、ue-project-structure、ue-navigating-engine-source、ue-cpp-fundamentals。资料引用的引擎源码(UE 5.8)涉及 ModuleInterface.h、ModuleManager.h、ModuleBoilerplate.h、ModuleRules.cs、TargetRules.cs、ModuleDescriptor.cs;官方文档包括 Unreal Engine Modules、Creating a Gameplay Module、Module Properties、Module API Specifiers、IWYU、UBT Targets reference;技能内还含 references/build-cs-reference.md、references/target-cs-reference.md、references/module-cpp-and-phases.md 三份深入参考。