Unreal对C++做了什么 · 第 20 章 · 编辑器扩展:Slate 与元编程的极致
第 20 章 · 编辑器扩展:Slate 与元编程的极致
反射的最后一类回报是 编辑器:Details 面板根据 UPROPERTY 自动生成控件,资产浏览器能识别并操作 UObject。编辑器本身也是用 Unreal 的 C++ 方言写的,其中 UI 大量使用 Slate——一套在 C++ 里用宏和运算符重载实现的声明式 UI 框架。本章简要说明 Slate 的风格,以及反射如何驱动编辑器行为。
20.1 Slate:C++ 里的声明式 UI
传统 C++ UI 是命令式的:创建控件、设置属性、把子控件加入父控件。Slate 用宏和链式调用,在 C++ 里写出"树形结构"的 UI 描述:
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(STextBlock).Text(FText::FromString(TEXT("Label")))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SEditableTextBox)
]
SNew 创建控件;Slot() 返回一个槽位,可链式设置布局属性;[] 里放子控件。这是合法的 C++,通过运算符重载和宏把"槽位 + 子控件"绑在一起。这样不需要单独的 UI 描述语言(如 QML),就能在 C++ 里写出层次清晰的界面结构。
Slate 的 operator[] 实际上接受一个 TSharedRef<SWidget>,所以 [ SNew(STextBlock)... ] 返回的是该槽位持有的子控件引用。整棵 UI 树在 C++ 里以嵌套的 SNew + Slot + [] 表达,编译期就确定结构,运行时只做布局与事件绑定。因此编辑器 UI 和游戏内 UMG 的底层一样,都是"在 C++ 里用同一套方言描述界面"。
与 Qt 和 ImGui 的对比
Slate 不是唯一在 C++ 中构建 UI 的方案,但它的设计取舍与 Qt、ImGui 截然不同。
Qt(QWidget + MOC / QML)。 Qt 和 Unreal 有一个有趣的相似点:两者都使用预处理代码生成工具(Qt 的 MOC 对应 Unreal 的 UHT)来弥补 C++ 缺少反射的问题。Qt 的信号与槽通过 MOC 生成元数据,与 Unreal 的委托系统思路接近。但在 UI 层面,Qt 走了"分离描述语言"的路线——QML 用独立的声明式语言写界面,C++ 只做后端逻辑。Slate 拒绝引入第二门语言,坚持在 C++ 里用宏和运算符重载达成同样的声明式效果。好处是编辑器 UI 代码与引擎其余 C++ 共享同一套类型系统、编译检查和调试工具链;代价是 Slate 代码的可读性门槛更高——你必须习惯 SNew + operator+ + operator[] 这套"看着不像 C++"的写法。
ImGui(即时模式)。 Dear ImGui 是另一种极端:没有控件树、没有状态管理,每帧重新调用 ImGui::Button("Click") 等函数来"画"界面。优点是极简、零架构,适合调试工具和原型;缺点是无法处理复杂布局、动画、样式继承和大规模编辑器 UI。Slate 是保留模式(Retained Mode)——控件树在内存中持久存在,只在属性变更时重新布局,适合编辑器这种需要复杂交互和高性能刷新的场景。
| 维度 | Slate | Qt (QWidget/QML) | ImGui |
|---|---|---|---|
| 模式 | 保留模式(Retained) | 保留模式 | 即时模式(Immediate) |
| UI 描述方式 | C++ 内声明式(宏 + 运算符重载) | QML(独立语言)或 C++ 命令式 | 每帧函数调用 |
| 代码生成工具 | UHT(反射) | MOC(元对象) | 无 |
| 复杂布局/动画 | 完善 | 完善 | 有限 |
| 与引擎集成 | 原生(同一套类型/编译/调试) | 需桥接 | 需桥接 |
| 典型用途 | 编辑器、游戏 HUD | 桌面应用 | 调试/原型 |
Unreal 选 Slate 而不是嵌入 Qt 或 ImGui,核心原因还是本书的主题——用同一套方言覆盖尽可能多的维度。编辑器 UI 代码与游戏逻辑、反射、序列化共享同一组基础设施(UObject、TSharedPtr、委托、FText),不需要额外的语言运行时或第三方依赖。
20.2 Details 面板与反射
编辑器中选中一个 Actor 或资源时,Details 面板会列出其可编辑属性。流程是:
- 取当前选中对象的 UClass;
- 通过反射遍历 UClass 的 FProperty;
- 对每个带 Edit 系说明符的属性,根据类型(float、bool、UObject* 等)创建对应的 Slate 控件(数字框、勾选框、对象选择器等);
- 用 FProperty 的偏移量读写对象内存,实现编辑与对象联动。
因此 Details 面板是反射驱动的:不手写每个属性的 UI,只靠 UPROPERTY 和说明符。自定义表现可通过 IDetailCustomization(定制整个对象的 Details 布局)或 IPropertyTypeCustomization(定制某一类型的属性控件)扩展。
例如,为 AMyCharacter 做定制时,实现 IDetailCustomization::CustomizeDetails,拿到当前选中的对象和 IDetailLayoutBuilder,可以隐藏某些属性、改变分组、插入自定义 Slate 控件,甚至根据另一个属性的值动态显示/隐藏某一行。所有读写仍通过 FProperty 的偏移完成,与反射一致;UI 只是"怎么摆、怎么显示"的差异。
20.3 自定义资产与反射
自定义资产类型通常继承 UDataAsset 或 UObject,加上 UCLASS/UPROPERTY。这样自动获得:
- 反射信息(UClass、FProperty),供 Details 和资产浏览器使用;
- 序列化(保存/加载 uasset),无需手写格式。
再配合 UFactory(定义"新建"时如何创建实例)、FAssetTypeActions(图标、右键菜单、双击行为),就能在内容浏览器里完整集成自定义资产。编辑器扩展的 C++ 代码同样遵守 UObject、反射、Slate 的同一套方言。
| 扩展点 | 作用 | 依赖 |
|---|---|---|
| UCLASS / UPROPERTY | Details 自动生成、资产可被识别 | 反射 |
| IDetailCustomization | 定制某类的 Details 布局 | Slate + 反射 |
| IPropertyTypeCustomization | 定制某类属性的编辑控件 | Slate + FProperty |
| UFactory | 内容浏览器"新建"时创建资产 | UObject |
| FAssetTypeActions | 图标、右键菜单、双击打开方式 | 模块与资产类型 |
20.4 编辑器与运行时
编辑器模块(如 UnrealEd、Slate、SlateCore)依赖运行时模块,但只在编辑器中链接。这样既能在编辑器里用完整引擎能力做扩展,又保证打包后的游戏不包含编辑器代码。扩展编辑器的 C++ 和写游戏逻辑的 C++ 是同一门方言,只是运行的"目标"不同。
实验:反射与 Details
- 改说明符看 Details。 在任意 Actor 上把一个
UPROPERTY(EditAnywhere)改成EditDefaultsOnly或VisibleAnywhere,保存后选中该 Actor 的蓝图或实例,观察 Details 面板中该属性的可编辑性与分组变化。 - 看自动分组。 给几个属性设置不同的
Category = "Combat"、"Movement"等,在 Details 里确认分组标题与属性归属是否与 Category 一致,理解"反射 + 元数据"如何驱动 UI。 - (可选)做一个 IDetailCustomization。 为你的某个 C++ 类实现
IDetailCustomization,在CustomizeDetails里用DetailBuilder.HideCategory("CategoryName")隐藏一个分组,或增加一行自定义说明文字,编译后在编辑器中选中该类型对象,确认 Details 布局已变化。
一句话总结
Slate 在 C++ 内用宏和运算符重载实现声明式 UI 树;Details 面板和资产系统由反射驱动,通过 UPROPERTY 和自定义 Customization 扩展;自定义资产与编辑器扩展都建立在同一套 UObject + 反射 + 序列化之上,编辑器本身是 Unreal C++ 方言的大型应用。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)