UE5 反射系统
UE5 反射系统详解
目录
一、什么是反射系统?
在 C++ 中,语言本身不提供运行时类型信息(RTTI)的完整支持,无法在运行时获取一个类有哪些属性、哪些方法、属性叫什么名字、类型是什么等信息。
UE5 自己实现了一套完整的运行时反射系统,核心能力包括:
- 运行时获取类的属性、方法、类型信息
- 支持序列化 / 反序列化(存档、网络同步)
- 支持蓝图与 C++ 互通
- 支持垃圾回收(GC)追踪 UObject 引用
- 支持编辑器属性面板(Details Panel)自动显示
二、核心机制:UHT + 宏标记
UE5 反射系统的实现分两个阶段:编译前预处理 和 运行时访问。
2.1 UHT(Unreal Header Tool)
UHT 是 UE 的代码生成工具,在正式编译 C++ 之前运行。
流程如下:
你写的 .h 文件
↓ UHT 扫描宏标记
生成 xxx.generated.h 和 xxx.gen.cpp
↓ 一起参与 C++ 编译
最终二进制包含完整反射数据
每个启用反射的头文件末尾必须包含:
#include "MyClass.generated.h" // UHT 生成的文件,必须是最后一个 include
2.2 核心宏
| 宏 | 用途 |
|---|---|
UCLASS() |
标记一个类参与反射 |
USTRUCT() |
标记一个结构体参与反射 |
UENUM() |
标记一个枚举参与反射 |
UPROPERTY() |
标记一个成员变量参与反射 |
UFUNCTION() |
标记一个成员函数参与反射 |
GENERATED_BODY() |
插入 UHT 生成的模板代码(必须写在类体最开头) |
示例:
UCLASS(BlueprintType, Blueprintable)
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY()
public:
// 暴露给蓝图读写,并在编辑器面板显示
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stats")
float Health;
// 暴露给蓝图调用
UFUNCTION(BlueprintCallable, Category = "Stats")
void TakeDamage(float Amount);
};
三、运行时类型系统:UClass / UStruct / UEnum
UHT 生成的数据最终以 UClass、UScriptStruct、UEnum 等对象的形式存在于内存中,这些对象本身也是 UObject。
3.1 UClass
每个 UCLASS 标记的类,在运行时都对应一个 UClass 实例:
// 获取 UClass 对象
UClass* ClassInfo = AMyActor::StaticClass();
// 运行时动态获取(从实例)
UClass* ClassInfo = SomeActor->GetClass();
// 打印类名
UE_LOG(LogTemp, Log, TEXT("Class: %s"), *ClassInfo->GetName());
UClass 内部存储了:
- 所有
UPROPERTY→ 以FProperty链表形式存储 - 所有
UFUNCTION→ 以UFunction对象存储 - 父类指针(支持继承链查询)
- CDO(Class Default Object,类默认对象)
3.2 CDO(Class Default Object)
每个 UCLASS 都有一个 CDO,是该类在引擎启动时创建的默认实例,用于:
- 存储属性默认值
- 序列化时做差量存储(只保存与 CDO 不同的部分)
- 编辑器中"重置为默认值"的数据来源
AMyActor* CDO = GetDefault<AMyActor>();
UE_LOG(LogTemp, Log, TEXT("Default Health: %f"), CDO->Health);
四、属性遍历(运行时读写属性)
反射系统最实用的能力之一:在不知道具体类型的情况下,遍历和读写属性。
void PrintAllProperties(UObject* Obj)
{
UClass* Class = Obj->GetClass();
// 遍历所有 UPROPERTY
for (TFieldIterator<FProperty> It(Class); It; ++It)
{
FProperty* Prop = *It;
FString PropName = Prop->GetName();
// 以 float 为例,读取属性值
if (FFloatProperty* FloatProp = CastField<FFloatProperty>(Prop))
{
float Value = FloatProp->GetPropertyValue_InContainer(Obj);
UE_LOG(LogTemp, Log, TEXT("%s = %f"), *PropName, Value);
}
}
}
常用的 FProperty 子类:
| 类型 | 对应 FProperty 子类 |
|---|---|
float |
FFloatProperty |
int32 |
FIntProperty |
FString |
FStrProperty |
bool |
FBoolProperty |
UObject* |
FObjectProperty |
TArray<> |
FArrayProperty |
TMap<> |
FMapProperty |
五、函数调用(运行时动态调用 UFUNCTION)
// 按名字找到函数
UFunction* Func = Obj->FindFunction(FName("TakeDamage"));
if (Func)
{
// 构造参数结构体(内存布局必须与函数签名匹配)
struct FParams
{
float Amount;
};
FParams Params { 50.0f };
Obj->ProcessEvent(Func, &Params);
}
ProcessEvent是 UE 反射系统调用函数的底层入口,蓝图调用 C++ 函数、RPC 网络调用本质上都走这条路。
六、类型检查与转换
UE 提供了自己的类型转换,不推荐用原生 dynamic_cast:
// 安全向下转型(失败返回 nullptr)
AMyActor* MyActor = Cast<AMyActor>(SomeActor);
// 判断是否是某个类或其子类
if (SomeActor->IsA(AMyActor::StaticClass()))
{
// ...
}
// 判断两个 UClass 的继承关系
bool bIsChild = ChildClass->IsChildOf(ParentClass);
七、UPROPERTY 修饰符详解
UPROPERTY 的修饰符控制了属性的可见性、可编辑性、蓝图访问权限、网络同步等行为:
UPROPERTY(
EditAnywhere, // 在任何地方的 Details 面板可编辑
BlueprintReadWrite, // 蓝图可读可写
Replicated, // 网络同步
Category = "Combat", // 编辑器分类
meta = (ClampMin = "0.0", ClampMax = "100.0") // 元数据限制
)
float Health;
常用修饰符速查:
| 修饰符 | 含义 |
|---|---|
EditAnywhere |
原型和实例都可在编辑器编辑 |
EditDefaultsOnly |
只能在原型(CDO)编辑 |
EditInstanceOnly |
只能在场景实例编辑 |
VisibleAnywhere |
编辑器只读显示 |
BlueprintReadWrite |
蓝图可读写 |
BlueprintReadOnly |
蓝图只读 |
Replicated |
网络同步 |
Transient |
不序列化(不存档) |
SaveGame |
参与存档系统 |
八、反射与垃圾回收的关系
UE 的 GC 依赖反射系统追踪 UObject 引用:
- 只有被
UPROPERTY标记的UObject*指针,GC 才能感知到引用关系 - 裸指针(没有
UPROPERTY)不会被 GC 追踪,对象可能被回收导致野指针
// ❌ 危险:GC 不追踪这个指针,对象可能被回收
UMyObject* RawPtr;
// ✅ 安全:GC 追踪此引用,对象存活
UPROPERTY()
UMyObject* SafePtr;
九、总结
宏标记(UCLASS/UPROPERTY/UFUNCTION)
↓
UHT 预处理
↓
生成反射元数据(.gen.cpp)
↓
运行时 UClass / FProperty / UFunction 对象
↓
支撑:蓝图互通 / 编辑器面板 / 序列化 / GC / 网络同步 / 动态调用
UE5 的反射系统是整个引擎的基础设施,理解它能帮助你更好地理解蓝图原理、网络同步、存档系统、编辑器扩展等几乎所有上层系统的工作方式。

浙公网安备 33010602011771号