【ue4】【架构】模块机制
前言
UE4 从源代码的组织上来看是由__模块 (Module) __组成的
从功能上讲, UE4 对 编辑器(Editor) 和 游戏性(Runtime) 都提供了支持
对于编辑器来说, UE4 提供了丰富的编辑界面,
角色编辑器 (Persona) 动画编辑器 (AnimationEditor) 特效编辑器 (Cascade和Niagara) 材质编辑器 脚本编辑器 (蓝图) 等等
同时还可以自定义插件, 以及使用开放的蓝图节点工具和UI工具 Slate
来定制自己的编辑器界面
对于游戏性来说, UE4提供了比较完整的游戏性类, 以实现对整个游戏生命周期的控制
这些游戏性类从宏观上看也是遵循 MVC 模式的
工程目录
Build -- 编译过程中产生的 可执行文件 及其他
Config -- 配置文件 -- 控制引擎行为 -- 游戏目录下的 Config 配置会覆盖引擎的
Content -- 资源文件 模型 地图 动画 蓝图 -- .uasset (地图为 .umap)
Source -- 源码
Saved -- 自动保存的内容 -- 配置.ini 日志(崩溃日志)
Plugins -- 插件 -- 与源代码内容类似, 但可能包含 Content
Shaders -- 着色器
Module -- 模块
每一个 UE 项目, 包括 UE 引擎项目本身,都是由一个或多个__模块(Module)__组成
且至少有一个主模块, 其他模块都是扩展模块
在编译后, 每一个模块都是一个单独的 dll (也有lib), 供运行时可选择性地相互链接
【Tips】 每一个插件也是由一个或多个模块组成
模块的结构
每一个模块大概包含以下文件
Classes -- 包含所有游戏性类的 .h 文件
Private -- 包含所有 .cpp 文件 -- 包括游戏性类的和模块本身的
Public -- 包含模块本身的头文件
ModuleName.Build.cs -- 包含模块的编译信息
【Tips】 Classes 文件夹不是必须的 【Tips】 每个模块至少有一个 .h 和一个 .cpp
模块的依赖
模块的 Build.cs
文件里可以指定其所依赖的其他模块 -- 定义 ModuleRules
每一个模块也可以在其他模块的 Build.cs
里被其他模块所依赖
1 | // CPTPS.Build.cs |
游戏项目中的 .Target.cs
(一般有两个, 可自己新增) 声明了游戏和编辑器下需要如何将不同模块链接成目标代码
它定义了 TargetRules
1 | // CPTPS.Target.cs |
游戏项目中的模块必须在 .uproject
文件里统一定义才能被 UE 识别
1 | // CPTPS.uproject |
【Tips】 插件中的模块也要在插件的 .uplugin
文件里统一定义 【Tips】 模块大致分为 运行时模块 (Runtime) 和 编辑器模块 (Editor)
模块的建立
当新建一个模块时,我们需要手动创建 ModuleName.h
ModuleName.cpp
ModuleName.Build.cs
三个文件
并按照上面的规则建立好模块间的依赖关系
最重要的是, 每一个模块必须有一个实现了 IModuleInterface
的类
并将其作为暴露给外部使用的接口(指针)
可以看出, 这个接口是用来操作模块本身整体的, 即加载, 初始化, 卸载及一些标记状态量等
除此之外,为了满足每个模块的dll (或lib) 能被顺利地找到, 即把这个接口暴露出来给其他地方使用, 需要声明一些样板宏, 即 IMPLEMENT_MODULE
IMPLEMENT_MODULE(FDefaultModuleImpl, ModuleName) // 一般的模块声明 FDefaultModuleImpl为空实现
IMPLEMENT_MODULE(FModuleName, ModuleName) // 有自己的初始化逻辑的模块 ModuleNameModule为子类
IMPLEMENT_GAME_MODULE(FModuleName, ModuleName) // 包含游戏逻辑的模块
IMPLEMENT_PRIMARY_GAME_MODULE(FModuleName, ModuleName, "ModuleName") // 当前项目的主模块
【Tips】 必须声明一个主模块 【Tips】 一般用 FModuleName
为类名继承 IModuleInterface
, 而 ModuleName
则为实际模块名
模块的引用
在其他地方引用某个模块,只需要调用 FModuleManager
类的 LoadModule()
方法即可
1 | FLevelEditorModule& LEModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor"); |
【Tips】 LoadModule()
不论是动态链接还是静态链接, 都会通过模块名称正确地找到模块的接口。 【Tips】 LoadModuleChecked()
会检查返回的模块接口是否合法