目录
一,理解UBT/UHT
UBT(Unreal Build Tool )虚幻编译工具,负责管理整个编译流程,包括收集所需编译的模块、处理依赖关系、调用 UHT,最后再调用标准的 C++ 编译器(如 MSVC)完成最终编译;
UHT(Unreal Header Tool)预处理,负责解析头文件中的特殊宏(如
UCLASS(),UFUNCTION()等),读取为 UObject 系统添加的“元数据”,并自动生成*.generated.h和*.gen.cpp等中间代码,从而支持反射、蓝图调用等功能;
为UClass对象提供数据;
在.generated.h内的DECLARE_CLASS2宏声明
StaticClass();
点击编译
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. UBT (Unreal Build Tool) 启动 │
│ - 读取 .uproject 文件,获取项目信息 │
│ - 读取 Target.cs 和所有模块的 Build.cs 文件 │
│ - 构建完整的模块依赖关系图(包括引擎模块和游戏模块) │
│ - 基于时间戳和依赖关系,判断哪些源文件需要重新编译 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. UBT 调用 UHT (Unreal Header Tool) │
│ - 扫描所有需要反射的头文件 (.h) │
│ - 识别 UCLASS, USTRUCT, UFUNCTION, UPROPERTY 等宏 │
│ - 验证反射规范的合法性(如 UFUNCTION 必须在 UCLASS 内等) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. UHT 生成反射所需的中间代码 │
│ - 为每个反射类型生成 .generated.h 文件(供其他文件包含) │
│ - 生成 .gen.cpp 文件,包含: │
│ • 静态反射数据表(如 UClass 的初始化结构) │
│ • 属性(UPROPERTY)的序列化/反序列化代码 │
│ • 函数(UFUNCTION)的参数封包(RPC 或蓝图调用)代码 │
│ - 所有中间文件输出到 Intermediate 文件夹 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 4. UBT 准备编译环境 │
│ - 生成编译命令行参数(优化级别、调试信息等) │
│ - 设置包含目录(Include Paths),包括生成的 Intermediate 目录│
│ - 配置预处理器宏(如 _DEBUG, _EDITOR 等) │
│ - 确定编译配置(Debug / Development / Shipping) │
│ - (可选)生成预编译头(PCH)以加速编译 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 5. UBT 调用底层编译器(MSVC / Clang) │
│ - 编译所有 .cpp 源文件,包括原始代码和 .gen.cpp │
│ - 输出 .obj 目标文件 │
│ - 将 .obj 文件链接成动态库(.dll)或可执行文件(.exe) │
│ - 若为模块,生成对应的 .module 文件(用于模块管理) │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 6. UBT 处理编译输出 │
│ - 将生成的 .dll / .exe 复制到项目的 Binaries 目录 │
│ - 更新模块热重载信息(如生成 .hotreload 文件,供编辑器使用) │
│ - 若为游戏目标,可能复制到最终打包目录 │
│ - (注意:着色器编译由引擎资源系统在运行时或 Cook 时处理) │
└─────────────────────────────────────────────────────────────────┘
│
▼
引擎可以运行新代码
二,反射系统
反射 是程序在运行时检查、访问和操作自身结构(类、属性、函数等)的能力,其在UE中的主要用途:
- 编辑器交互(最核心),能直接在编辑器的Details面板里修改它的值,无需任何额外代码,如在
UPROPERTY(EditAnywhere);- 蓝图与C++交互,可直接在蓝图节点中调用C++函数,如UFUNCTION(BlueprintCallable);反之,C++也能调用蓝图中实现的事件如BlueprintImplementableEvent;
- 序列化(保存与加载),保存游戏时,反射系统能自动遍历对象上所有
UPROPERTY()标记的变量,把值写入磁盘,加载时再自动恢复;只操作“属性数据”(填充UClass实例),不涉及定义(包括属性名称/类型等);- 垃圾回收,
UPROPERTY()最重要的职责之一是阻止垃圾回收,被标记的指针,GC就知道“这是正在使用的”,不会错误地清理掉,防止内存泄漏和野指针的关键;- 网络复制,
UPROPERTY(Replicated)可让某个属性在服务器和所有客户端之间自动同步,省去了手写大量网络通信代码的麻烦;注:必须是UObject派生类,完整的反射功能(如垃圾回收)只在继承自UObject的类上生效,普通的C++类不行;
关键宏
// 创建新类型时“对外声明”,告诉构建工具“该类需要被纳入反射系统”;否则UBT在扫描头文件时会直接忽略该类,不会为它生成 .generated.h 和 .gen.cpp 文件;
// 可在其后附加说明符,用于该类与引擎编辑器及蓝图系统的沟通交互(如Blueprintable,BlueprintType,meta=(...));
// 标记类型错误(如USTRUCT用在了UObject上),UHT会编译错误;
UCLASS():对继承自UObject的类反射,提供完整反射支持(如UFUNCTION、垃圾回收、网络复制等);
USTRUCT():对普通C++结构体轻量级反射(支持UPROPERTY、序列化、蓝图作为数据类型,但不支持函数暴露和垃圾回收);
UENUM():对C++枚举反射(可在蓝图、编辑器下拉菜单和序列化中直接使用);
UINTERFACE():接口类,标记可被蓝图实现的接口,必须配合GENERATED_BODY();
// “对内填充”,在类体内补齐必要的代码;
// 提供必要的函数声明(如StaticClass(),返回本类专属的UClass指针),必要的类型定义(如Super);
GENERATED_BODY()
// 对类型成员的反射,标记需要进行反射的宏
// 必须用于被UCLASS或USTRUCT标记的类型内部成员
// 不可用于全局变量或全局函数,也不可用于普通 C++ 类/结构体中;
UPROPERTY():反射成员变量(支持序列化、蓝图访问、网络复制等),用于UCLASS/USTRUCT;
UFUNCTION():反射函数(支持蓝图调用、网络远程调用、控制台命令等),用于UCLASS/UINTERFACE;
UPROPERTY() FString GlobalName; // ❌ 错误:全局变量不能用
class MyNormalClass {
UPROPERTY() int Value; // ❌ 错误:普通C++类不能用
};
// 定义一个蓝图可识别的类,引用类型,受垃圾回收(GC)管理
UCLASS() // ✅ 正确:在UCLASS内部使用
class AMyActor : public AActor {
GENERATED_BODY()
UPROPERTY() int Score;
UFUNCTION() void DoAction();
};
// 定义一个蓝图可识别的结构体,值类型,不受垃圾回收(GC)直接管理
// 可将.CSV文件导入UE并选择该结构体,导入数据
USTRUCT(BlueprintType) // ✅ 正确:在USTRUCT内部使用
struct FMyTable : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString Name;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 Score;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float Num;
};
// 定义一个蓝图可识别的枚举
UENUM(BlueprintType)
enum class EMyEnum : uint8
{
type1 UMETA(DisplayName = "类型一"),
type2 UMETA(DisplayName = "类型二"),
type3 UMETA(DisplayName = "类型三")
};
用于生成
UPROPERTY 通过反射系统将对应属性暴露给蓝图;
// 编辑器可编辑性(Visible / Edit) UPROPERTY(VisibleAnywhere) // 始终可见但不可编辑(只读显示),实例和默认值均可见 UPROPERTY(VisibleDefaultsOnly) // 仅类默认值中可见(只读) UPROPERTY(VisibleInstanceOnly) // 仅实例细节面板中可见(只读) UPROPERTY(EditAnywhere) // 实例和类默认值(蓝图/CDO)均可编辑 UPROPERTY(EditDefaultsOnly) // 仅类默认值可编辑,实例不可 UPROPERTY(EditInstanceOnly) // 仅实例细节面板可编辑,类默认值不可// 蓝图访问控制(Blueprint) UPROPERTY(BlueprintReadWrite) // 蓝图可读写,即可get/set UPROPERTY(BlueprintReadOnly) // 蓝图只读,即只可get UPROPERTY(BlueprintGetter) // 指定一个自定义函数作为 Getter(替代默认的读操作) UPROPERTY(BlueprintSetter) // 指定一个自定义函数作为 Setter(替代默认的写操作)// 资产与元数据 UPROPERTY(Category = "名称") // 指定属性在编辑器 Details 面板中所属的分类 UPROPERTY(SimpleDisplay) // 优先显示在简单列表中(而不是高级折叠页) UPROPERTY(AdvancedDisplay) // 默认折叠到“高级”区域 UPROPERTY(AssetRegistrySearchable) // 属性会加入资产注册表,允许在内容浏览器中搜索// meta元数据说明符主要用于精细控制属性在编辑器、蓝图等环境中的显示与交互方式,不影响核心逻辑,只存在于编辑器环境中; UPROPERTY(meta = (DisplayName = "xxx")) // 在编辑器中显示的名称,而不是变量名 UPROPERTY(meta = (EditCondition = "mybool")) // 只有在mybool变量为真时才可编辑 UPROPERTY(meta = (ToolTip = "角色的基础移动速度")) // 鼠标悬停时显示的提示文本 UPROPERTY(meta = (ClampMin = "0", ClampMax = "100")) // 硬限制数值编辑范围(仅对浮点和整数) UPROPERTY(meta = (UIMin = "0.0", UIMax = "1.0")) // 软限制数值编辑范围(仅对浮点和整数) UPROPERTY(meta = (ArrayClamp = "MyArray")) // 限制数值在指定数组(对整数) UPROPERTY(meta = (AllowPrivateAccess = "true")) // 允许蓝图访问私有成员(不推荐,但有时有用) UPROPERTY(Category = "移动属性", meta = (ClampMin = "0.0", ClampMax = "1000.0", ToolTip = "角色的基础移动速度"))// 网络复制(Replication) UPROPERTY(Replicated) // 属性会进行网络复制(需在 GetLifetimeReplicatedProps 中注册) UPROPERTY(ReplicatedUsing) // 属性复制时,会调用指定的回调函数(通常在客户端执行)// 序列化与持久化 UPROPERTY(SaveGame) // 属性会被包含在 SaveGame 存档系统中 UPROPERTY(Transient) // 属性不会被序列化(保存/加载时忽略),加载后为默认值 UPROPERTY(DuplicateTransient) // 复制(Duplicate)对象时,此属性不会被复制到新对象 UPROPERTY(TextExportTransient) // 导出为文本(如复制到剪贴板)时忽略 UPROPERTY(NonTransactional) // 在编辑器中操作(如移动、修改)时不参与 Undo/Redo 系统// 实例化与引用 UPROPERTY(Instanced) // 对于 UObject* 或 TSubclassOf,每个实例都会拥有自己独立的对象,而不是共享默认对象 UPROPERTY(Ref) // 标记一个引用参数(通常用于函数参数,很少用在 UPROPERTY) UPROPERTY(Export) // 将引用的对象作为子对象序列化(内嵌)而不是通过引用路径// 配置相关 UPROPERTY(Config) // 此属性可从配置文件(*.ini)读取/写入 UPROPERTY(GlobalConfig) // 类似 Config,但保存在全局基类配置文件中(不保存在子类) UPROPERTY(Localized) // 属性是本地化的(UI 文本等)// 其他实用说明符 UPROPERTY(Native) // 仅用于结构体,标记为本地类型 UPROPERTY(NoClear) // 对象引用不能设置为 None(必须总是有效)
UFUNCTION 通过反射系统将对应函数暴露给蓝图;
// 蓝图交互 UFUNCTION(BlueprintCallable) // 蓝图可调用,允许蓝图节点调用此C++函数,是暴露函数最常见的方式; UFUNCTION(BlueprintPure) // 蓝图纯函数(无副作用),在蓝图中不显示执行引脚,直接连出数据,const函数默认隐含此标记; UFUNCTION(BlueprintNativeEvent) // C++默认实现,C++需在函数名后加_Implementation实现,蓝图可重写(重写后使用重写的内容); UFUNCTION(BlueprintImplementableEvent) // 纯蓝图实现,C++声明无实现,完全由蓝图实现;有返回值是函数,无返回值的是事件;// 网络与权限 UFUNCTION(NetMulticast) // 网络多播 UFUNCTION(Server) // 服务器执行 UFUNCTION(Client) // 客户端执行 UFUNCTION(Reliable / Unreliable) // 配合RPC使用,保证/不保证网络调用必达; UFUNCTION(BlueprintAuthorityOnly) // 限制蓝图代码仅在拥有网络权限的机器上执行; UFUNCTION(BlueprintCosmetic) // 标记为装饰性函数,在专用服务器上会被跳过;// 其他实用功能 UFUNCTION(Exec) // 将函数暴露为游戏内控制台命令 UFUNCTION(CallInEditor) // 在编辑器中选中对象,便可在细节面板看到调用按钮; UFUNCTION(Category = "Name") // 在蓝图节点面板中,为函数指定分类名称; UFUNCTION(meta = (DisplayName="...")) // 自定义函数在蓝图中的显示名称 UFUNCTION(BlueprintSetter) // 当蓝图修改特定UPROPERTY时,关联并自动调用此函数;
// MyClass.h
UCLASS()
class UMyClass : public UObject {
GENERATED_BODY()
public:
// 1. 可在蓝图中调用的函数
UFUNCTION(BlueprintCallable, Category = "MyFunctions")
void DoSomething();
// 2. 带默认实现,但允许蓝图重写的函数
UFUNCTION(BlueprintNativeEvent, Category = "MyFunctions")
int32 CalculateValue(int32 Input); // 需要额外实现 CalculateValue_Implementation
// 3. 纯函数,无副作用
UFUNCTION(BlueprintPure, Category = "MyFunctions")
float GetHealthPercent() const;
// 4. 在服务器上执行的 RPC 函数
UFUNCTION(Server, Reliable, WithValidation)
void ServerDoAction(); // 需要额外实现 ServerDoAction_Implementation 和 ServerDoAction_Validate
// 5. 控制台命令
UFUNCTION(Exec)
void MyConsoleCommand();
};
用于调用(运行时)
- 在程序运行期间,通过字符串名字或类型信息动态地查找、访问、调用对象的属性和方法;
主要用途:
运行时类型识别(RTTI)
判断对象是否为某个类型或其子类:
MyActor->IsA(AActor::StaticClass());判断类之间的继承关系(包含自身):
MyClass->IsChildOf(UObject::StaticClass());- 反射访问属性(
UPROPERTY)
通过
FindPropertyByName获取FProperty描述符;结合对象实例,读写成员变量的值(通过
ContainerPtrToValuePtr或CopyCompleteValue);- 反射调用函数(
UFUNCTION)
通过
FindFunctionByName获取UFunction;调用
UObject::ProcessEvent动态执行函数(支持参数和返回值);- 动态创建对象
NewObject<UObject>(Outer, UClass*):创建任意UObject派生对象;
World->SpawnActor<AActor>(UClass*, ...):在关卡中生成Actor;- 获取类默认对象(CDO)
UClass::GetDefaultObject()返回该类的默认实例;用途:
读取/修改类的默认属性值(影响所有新实例);
比较对象属性是否与默认值不同(用于网络同步、序列化差异);
快速获取类的一些静态配置;
- 支撑引擎底层系统
序列化:遍历
UClass的所有UPROPERTY,自动保存/加载;网络复制:标记
Replicated的属性会通过UClass查找并同步;垃圾回收:通过
UClass遍历对象引用的属性;编辑器 Details 面板:根据
UClass中的属性元数据动态生成 UI;蓝图虚拟机:通过
UClass查找函数并执行ProcessEvent;
// 该函数是UHT生成的.generated.h内的DECLARE_CLASS2宏声明并最终在GENERATED_BODY()展开
// 所有参与反射的类型均有此函数(即标记了 UCLASS()/USTRUCT()/UENUM() )
// 通过该函数可保证每个UObject类均只有一个全局唯一的 UClass 单例对象
// 懒加载,首次调用该函数时创建UClass对象(首次可能是模块加载/首次调用/其他类引用)
static UClass* StaticClass()
// 针对UObject*,依据反射系统,本质上是利用IsA()遍历继承链比较UClass*,可跨模块
// 不同于原生的dynamic_cast依赖遍历虚表
Cast<T> / CastChecked<T>
// 针对FProperty*,遍历FField继承链比较FFieldClass*
CastField<T> / CastFieldChecked<T>
auto EnumName = StaticEnum<EMyEnum>()->GetName(); // 结果:EMyEnum auto StructName = FMyStruct::StaticStruct()->GetName(); // 结果:MyStruct auto UObjectName = UObject::StaticClass()->GetName(); // 结果:Object
反射入口 → UClass (StaticClass / GetClass)
↓
属性:FindPropertyByName → FProperty → ContainerPtrToValuePtr → 读写
函数:FindFunctionByName → UFunction → ProcessEvent → 调用
类型:IsA / IsChildOf
创建:NewObject / SpawnActor
// 获取 UClass(元数据入口) T::StaticClass() // 获取已知 C++ 类 T 的 UClass UObject::GetClass() // 获取实例对象所属的 UClass(运行时),有可能指向子类UClass UClass* LoadClass<UObject>() // 加载蓝图类的全局模板函数(只要指定类型UObject的子类即可)// 读写属性(UPROPERTY) UClass::FindPropertyByName(FName("PropName")) // 按名称查找 FProperty TFieldIterator<FProperty> // 遍历类的所有属性(含继承) FProperty::ContainerPtrToValuePtr<void>(Object) // 获取对象中该属性的内存指针 FProperty::CopyCompleteValue(Dest, Src) // 复制属性值(不关心类型) FProperty::GetValue_InContainer(Object, &OutVal) // 读取属性值(模板版) FProperty::SetValue_InContainer(Object, &NewVal) // 设置属性值 FIntProperty::GetPropertyValue() // 获取FProperty的具体子类属性值 FIntProperty::SetPropertyValue() // 设置FProperty的具体子类属性值// 调用函数(UFUNCTION) UClass::FindFunctionByName(FName("FuncName")) // 按名称查找 UFunction TFieldIterator<UFunction> // 遍历类的所有函数 UObject::ProcessEvent(UFunction*, void* Parms) // 执行函数(支持参数/返回值) UFunction::Invoke(Object, Parms) // 另一个调用方式(较少用)// 动态创建对象 // 创建 UObject 派生对象(只是创建一个变量),全局模板函数 T* NewObject<T>(Outer); T* NewObject<T>(Outer, TEXT("Newname")); T* NewObject<T>(Outer, UClass*) // 在世界中生成 Actor(必须是 AActor 子类,因为Actor具备世界空间坐标/生命周期管理接口,由UWord定义) World->SpawnActor<AActor>(UClass*, FTransform, ...)// 类型判断与转换 UObject::IsA(UClass*) // 判断对象是否属于某类或子类,其底层调用的是 IsChildOf UClass::IsChildOf(UClass*) // 判断类是否继承自另一个类 Cast<T>(UObject*)/CastChecked<T> // 安全转换(编译期已知类型),UObject及其子类 CastField<T>(FProperty*)/CastFieldChecked<T> // 安全转换 FField 及其子类(如 FProperty) StaticClass()->IsChildOf(AActor::StaticClass()) // 类继承检查// 其他辅助 FindField<FProperty>(UClass*, FName) // 等价于 FindPropertyByName FindField<UFunction>(UClass*, FName) // 等价于 FindFunctionByName UClass::GetDefaultObject() // 获取类默认对象(CDO) UClass::GetSuperClass() // 获取父类 UObject::GetFName() // 获取对象名称(可用于反射查找),继承于UObject// 常用宏(编译期帮助反射生成) GENERATED_BODY() // 必须在类声明中,展开反射所需的代码 DECLARE_DYNAMIC_MULTICAST_DELEGATE(...) // 声明动态委托(支持蓝图) DECLARE_FUNCTION // 自定义 UFUNCTION 底层(极少用)
三,UClass
- 一个UClass对象是描述一个 UObject 派生类元数据(属性、函数、父类、大小等);
- UClass 是UE反射系统为每一个
UCLASS(即带有UCLASS()宏的 UObject 派生类)自动生成的元数据类型(继承链UObject -> UField -> UStruct -> UClass );- 可理解为C++ type_info的超级增强版——不仅包含类型名称、父类信息,还存储了该类的所有属性(UPROPERTY)、函数(UFUNCTION)、以及用于序列化、网络复制、实例化等的额外数据;
- 该类型是引擎核心代码中已经定义好的(位于
UObject.h/Class.h);- UClass 是UE中“类的类”,提供了运行时对 C++ 类结构(属性、函数、继承、默认值)的完整描述和操作能力,是反射、动态创建、编辑器集成等所有高级功能的基础;
- 每个 UObject 派生类都有一个唯一的 UClass 对象(单例);
- UClass 对象是模块加载时创建的,其元数据内容由UHT生成提供;
- 基类的
UClass与子类的UClass,是两个完全独立、互不包含的UObject对象;
- 通过
GetSuperClass()/SuperStruct,获取父类UClass;注,描述
UClass类型的那个UClass对象,引擎已事先通过硬编码方式手动设置好的,不会陷入无限循环;
| 类型 | 描述类型 |
| E枚举 | UEnum |
| F结构体 | UScriptStruct |
| UObject | UClass (类型名称、成员属性、成员函数、父类、类大小,及元数据(Tooltip、Category等)) |
| UObject 成员变量 | FProperty 描述变量本身(变量名称、大小、偏移量、标志等) FFieldClass 描述变量类型(描述FProperty) |
| UObject 成员函数 函数参数 | UFunction FProperty |
AMyActor 编译
|
| UHT 扫描 .h 文件
|
生成 .generated.h 和 .gen.cpp
|
| .gen.cpp 中包含:
| 1. 静态结构体:TClassCompiledInDefer<AMyActor> 的实例
| 2. 该类的元数据(属性名、偏移量、函数指针等)
|
┌─────────────────────────────────────────────────────────────┐
│ 模块加载 / 热重载(DLL 加载) |
└─────────────────────────────────────────────────────────────┘
|
| ① 静态对象 TClassCompiledInDefer<AMyActor> 的构造函数执行
| → 将类名、大小、CDO 创建函数指针等信息,
| 添加到一个全局的“待注册类列表”中(GetDeferredClassRegistration)
|
| ② 模块加载完成后,统一处理所有登记的类,引擎调用 ProcessNewlyLoadedUObjects()
| → 遍历全局“待注册类列表”
| → 对每个类调用 UClass::CreateClass()
|
生成:UClass 对象(分配内存,存入元数据表)
| ① 填充属性 (FProperty)、函数 (UFunction) 列表
| ② 链接父类 (UClass::Super)
| ③ 将该 UClass* 注册到全局名称映射表 (GUObjectArray)
|
| UClass 准备就绪,调用 UClass::CreateDefaultObject()
| → 内部调用 StaticConstructObject_Internal
| → 调用 AMyActor 的 C++ 构造函数(FObjectInitializer.bIsCDO = true)
| ├─ 初始化成员变量
| └─ CreateDefaultSubobject → NewObject(组件) ✅ 真正分配内存
| → 调用 PostInitProperties()
| ├─ 将组件注册到 UClass 的子对象列表
| └─ 处理 config 文件覆盖
|
生成:CDO 对象(Class Default Object)
| ├─ 标记 RF_ClassDefaultObject,加入 Root Set 防回收
| └─ 编辑器中修改 "Class Defaults" 即修改此 CDO 实例的属性值
|
运行时:AMyActor* NewActor = GetWorld()->SpawnActor<AMyActor>(...);
| → SpawnActor 内部调用 UClass::GetDefaultObject()(即 CDO)
| → 为 Actor 本体分配原始内存(FMemory::Malloc)
| → 通过 memcpy 复制 CDO 的内存布局(极快的浅拷贝)
| → 再次调用 C++ 构造函数(FObjectInitializer.bIsCDO = false,且携带 CDO 指针)
| ├─ 修正虚表指针(指向正确的子类虚函数表)
| ├─ CreateDefaultSubobject → DuplicateObject(组件) ✅ 深拷贝组件
| └─ 基础类型变量不修正,保持 memcpy 的值(除非显式赋值)
| → 调用 PostInitProperties()
| ├─ 将深拷贝的组件注册到当前实例的 UObject 链表
| └─ 触发 OnSubobjectAdded(编辑器感知)
| → SpawnActor 后处理:
| ├─ PostSpawnInitialize(设置 Owner/Instigator,执行碰撞调整)
| └─ OnConstruction(如果允许运行时构建)
| → 将 Actor 添加到 World 的 Actor 数组(开启 Tick 和复制,同时建立 GC 引用链,防止被回收)
|
生成:AMyActor 的运行时实例
// 简化的 UStruct 源代码 class UStruct : public UField { private: // 表示描述对象的父类的描述类型指针(如描述AMyActor类的 SuperStruct 指向AMyActor父类AActor的 UClass 对象) TObjectPtr<UStruct> SuperStruct; public: // 表示包含描述对象自身拥有重量级成员的链表头(成员为函数或UENUM()/USTRUCT()定义的嵌套类型的描述),迭代器使用 TObjectPtr<UField> Children; // 表示包含描述对象自身拥有轻量级成员变量的链表头(成员为 FProperty 及其子类),迭代器使用 FField* ChildProperties; int32 PropertiesSize; int16 MinAlignment; public: FProperty* PropertyLink; FProperty* RefLink; FProperty* DestructorLink; FProperty* PostConstructLink; public: UStruct(const FObjectInitializer& ObjectInitializer); int32 GetPropertiesSize() const; int32 GetMinAlignment() const; UStruct* GetSuperStruct() const FTopLevelAssetPath GetStructPathName() const; FProperty* FindPropertyByName(FName InName) const; bool IsChildOf( const UStruct* SomeBase ) const; }// 简化的 UClass 源代码 class UClass : public UStruct // 继承自 UStruct,继承 ChildProperties 链表 { public: // 继承自父类 UStruct // 记录所有UPROPERTY()标记的属性 FProperty* PropertyLink; // 从最高派生到基类的属性链表,内存数据,序列化时使用; // 记录所有UPROPERTY()标记的对象引用属性(UObject/UObject*/TObjectPtr<UObject>等包含对象引用的属性),阻止GC回收 FProperty* RefLink; // 从最高派生到基类的对象引用属性的链表,内存数据; // 自身定义 EClassFlags ClassFlags; // 类标志(枚举类型) TObjectPtr<UObject> ClassDefaultObject; // CDO(类默认对象实例,记录成员变量值包括父类默认值),便于快速实例化 TMap<FName, TObjectPtr<UFunction>> FuncMap; // 函数名 -> UFunction 的哈希表(快速查找) // ----- 构造 ----- UClass(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); explicit UClass(const FObjectInitializer& ObjectInitializer, UClass* InSuperClass); // ----- 常用接口 ----- UClass* GetSuperClass() const { return (UClass*)GetSuperStruct(); } UObject* GetDefaultObject(bool bCreateIfNeeded = true) const { return ClassDefaultObject; } FName GetDefaultObjectName() const; UObject* GetDefaultSubobjectByName(FName ToFind); void GetDefaultObjectSubobjects(TArray<UObject*>& OutDefaultSubobjects); EClassFlags GetClassFlags() const; bool HasAnyClassFlags( EClassFlags FlagsToCheck ) const; bool HasAllClassFlags( EClassFlags FlagsToCheck ) const; // ----- 查找属性(继承于UStruct) ----- FProperty* UStruct::FindPropertyByName(FName InName) const { for (FProperty* Property = PropertyLink; Property != nullptr; Property = Property->PropertyLinkNext) { if (Property->GetFName() == InName) return Property; } return nullptr; } // ----- 查找函数(沿继承链递归) ----- UFunction* FindFunctionByName(FName InName, EIncludeSuperFlag::Type IncludeSuper = EIncludeSuperFlag::IncludeSuper) const; { if 如果排除父类,或既没有接口也没有父类,直接在当前类的 FuncMap 中查找 Result = FuncMap.FindRef(InName); else 考虑接口和父类 先检查全局缓存 AllFunctionsCache 缓存未命中,开始实际查找 在本类 FuncMap 中查找 本类未找到,则遍历接口递归查找(Interfaces) 在父类 SuperClass 中递归查找 } };
// 利用UClass对象的成员变量ChildProperties,遍历自身拥有的属性
auto child_proes = GetClass()->ChildProperties;
while (child_proes)
{
auto name = child_proes->GetName();
UE_LOG(LogTemp, Warning, TEXT("FProperty: %s;"), *name);
child_proes = child_proes->Next;
}
// 利用UClass对象的成员变量Children,遍历自身拥有的函数
auto ChildFuncs = GetClass()->Children;
while (ChildFuncs)
{
auto name = ChildFuncs->GetName();
UE_LOG(LogTemp, Warning, TEXT("UFunction: %s;"), *name);
if (auto func = Cast<UFunction>(ChildFuncs))
this->ProcessEvent(func, nullptr);
ChildFuncs = ChildFuncs->Next;
}
FProperty
FProperty 是UE反射系统的核心,用于描述一个类或结构体的成员变量(不是变量类型);从 UE 4.25 开始,替代原有 UProperty,不再继承自 UObject,以降低内存和性能开销;
FProperty 是所有属性类型的基类,本身不存储属性的值,只存储属性的"元数据"(名称、类型、大小、偏移量、标志等);
FProperty 是一个抽象基类,对于每种基本数据类型,都有具体的派生类来实现其特定行为;
// 描述 FField 及其子类的类型
class FFieldClass
{
private:
FName Name;
EClassFlags ClassFlags;
uint64 Id;
FFieldClass* SuperClass;
FField* DefaultObject;
public:
FName GetFName() const;
FString GetName() const;
bool IsChildOf(const FFieldClass* InClass) const;
FFieldClass* GetSuperClass() const;
FField* GetDefaultObject();
}
class FField
{
FFieldClass* ClassPrivate;
public:
FFieldVariant Owner;
FField* Next;
FName NamePrivate;
EObjectFlags FlagsPrivate;
EObjectFlags GetFlags() const;
FName GetFName() const;
FString GetName() const;
FFieldClass* GetClass() const;
// 向上遍历外部对象链(定义该属性的所属类型)
FFieldVariant GetOwnerVariant() const; // 获取所有者容器(通常用于引擎内部判断所有者是 FField 还是 UObject)
UObject* GetOwnerUObject() const; // 获取所有者的任意UObject及其派生类
UClass* GetOwnerClass() const; // 获取所有者的UClass
UStruct* GetOwnerStruct() const; // 获取所有者的UStruct(最常用)
UField* GetOwnerUField() const; // 获取所有者的UField
bool HasAnyFlags(EObjectFlags FlagsToCheck) const;
bool HasAllFlags(EObjectFlags FlagsToCheck) const;
bool IsA(const FFieldClass* FieldType) const;
FString GetPathName(const UObject* StopOuter = nullptr) const;
void GetPathName(const UObject* StopOuter, FStringBuilderBase& ResultString) const;
FString GetFullName() const;
FFieldVariant GetOwnerVariant() const
}
// 简易伪代码 (描述成员变量本身包括类型/名称/内存偏移量等)
class FProperty : public FField
{
public:
// ----- 元数据 -----
int32 ArrayDim; // 数组维度(如果是静态数组)
int32 ElementSize; // 单个元素的大小(字节)
EPropertyFlags PropertyFlags; // 属性标志 (如 UPROPERTY 的说明符)
FProperty* PropertyLinkNext; // 从派生程度最高的属性到基类的属性链表
int32 Offset_Internal; // 内存偏移量
...
// ----- 构造 -----
FProperty(FFieldVariant InOwner, const FName& InName, EObjectFlags InObjectFlags);
FProperty(FFieldVariant InOwner, const UECodeGen_Private::FPropertyParamsBaseWithOffset& Prop, EPropertyFlags AdditionalPropertyFlags = CPF_None);
FProperty(FFieldVariant InOwner, const UECodeGen_Private::FPropertyParamsBaseWithoutOffset& Prop, EPropertyFlags AdditionalPropertyFlags = CPF_None);
// ----- 常用方法 -----
// 继承自FField
FName GetName() const; // 获取属性变量名称
FFieldClass* GetClass() const; // 获取属性类型(描述FProperty类,类似UClass描述UObject类)
virtual FName GetID() const; // 获取所属类型的唯一ID标签名称
int32 GetElementSize() const; // 获取容器元素的大小
virtual bool SameType(const FProperty* Other) const; // 是否深度类型等价
// ----- 核心方法 -----
// 给定一个指向包含此属性的对象/结构体的指针 (Container),计算出此属性值的确切内存地址
template<typename ValueType> // UObject容器
ValueType* ContainerPtrToValuePtr(UObject* ContainerPtr, int32 ArrayIndex = 0) const
return (uint8*)ContainerPtr + Offset_Internal + static_cast<size_t>(GetElementSize()) * ArrayIndex;
template<typename ValueType> //结构体等非UObject容器
ValueType* ContainerPtrToValuePtr(void* ContainerPtr, int32 ArrayIndex = 0) const
return (uint8*)ContainerPtr + Offset_Internal + static_cast<size_t>(GetElementSize()) * ArrayIndex;
// 复制单个元素 (基类默认Memcpy,派生类如FStringProperty重写为深拷贝)
virtual void CopySingleValue(void* Dest, const void* Src) const;
// 复制完整值 (自动处理 ArrayDim > 1 的静态数组循环复制,容器类型重写为深拷贝整个容器)
virtual void CopyCompleteValue(void* Dest, const void* Src) const;
// 在指定内存地址上构造默认值(调用构造函数)
virtual void InitializeValue(void* Dest) const;
// 销毁指定内存地址的值(调用析构函数)
virtual void DestroyValue(void* Dest) const;
...
};
// 属性遍历
UObject* MyObject = ...;
// 获取其类的反射信息
UClass* MyClass = MyObject->GetClass();
// 遍历该类及其父类的所有属性
for (TFieldIterator<FProperty> It(MyClass); It; ++It)
{
FProperty* Property = *It;
// 1. 获取元数据
FString PropertyName = Property->GetName();
FString PropertyType = Property->GetClass()->GetName();
// 2. 获取属性值的内存地址,该Property对应成员变量在对象实例上的内存位置
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(MyObject);
// 3. 根据属性类型执行不同操作
if (FIntProperty* IntProp = CastField<FIntProperty>(Property))
{
int32 Value = IntProp->GetPropertyValue(ValuePtr);
// 处理 int32 值...
}
else if (FStrProperty* StrProp = CastField<FStrProperty>(Property))
{
FString Value = StrProp->GetPropertyValue(ValuePtr);
// 处理 FString 值...
}
// ... 处理其他类型
}
UFunction
UFunction 是UE中一个核心的类类型,是 UE 反射系统可识别的 C++ 函数在引擎内部的运行时数据表示;类似 C++ 函数的“元数据镜像”,存储了函数的所有信息,使得函数可在蓝图、网络通信、控制台等系统中被动态识别和调用;
核心作用:蓝图调用,网络复制;
class UFunction : public UStruct {
public:
EFunctionFlags FunctionFlags; // 函数标志位
uint8 NumParms; // 参数总数
uint16 ParmsSize; // 参数总大小(表示该函数所有参数 + 返回值 + 额外辅助空间占用的总字节数(按最大对齐方式对齐))
uint16 RPCId; // RPC ID
uint16 ReturnValueOffset; // 返回值偏移
FProperty* FirstPropertyToInit; // 第一个本地结构体属性的指针
UFunction* EventGraphFunction; // 该函数调用的蓝图事件图表
private:
FNativeFuncPtr Func; // 绑定到的C++函数
// ---- 常用方法(继承于UObject) ----
FName GetName() const; // 获取函数名称
UClass* GetClass() const; // 获取反射类信息
UObject* GetOuter() const; // 获取外部对象(可选)
// --- 核心方法 ---
// 继承于UObject
void ProcessEvent(UFunction* Function, void* Params);
// 继承于UStruct
FProperty* FindPropertyByName(FName PropName) const;
UFunction* GetSuperFunction() const; // 获取父函数
FProperty* GetReturnProperty() const; // 获取返回值属性
void Invoke(UObject* Obj, FFrame& Stack, RESULT_DECL); // 在UObject上调用UFunction
};
// 查找函数并调用,TargetFunc 描述成员函数
UFunction* TargetFunc = MyClass->FindFunctionByName(TEXT("MyFunction"));
if (TargetFunc)
{
// 分配参数内存
uint8* Params = (uint8*)FMemory_Alloca(TargetFunc->ParmsSize);
FMemory::Memzero(Params, TargetFunc->ParmsSize);
// 设置参数(假设有一个 int 参数 "Value")
if (FIntProperty* IntParam = CastField<FIntProperty>(TargetFunc->FindPropertyByName(TEXT("Value"))))
{
int32 ValueToSet = 42;
IntParam->SetPropertyValue(IntParam->ContainerPtrToValuePtr<void>(Params), ValueToSet); // 查找的是属于成员函数的参数内存位置
}
// 调用函数(继承于UObject)
MyObject->ProcessEvent(TargetFunc, Params);
// 如函数有返回值,从 Params 中读取
if (FProperty* ReturnProp = TargetFunc->GetReturnProperty())
{
// 获取返回值(例如 int)
if (FIntProperty* IntReturn = CastField<FIntProperty>(ReturnProp))
{
int32 Result = IntReturn->GetPropertyValue(ReturnProp->ContainerPtrToValuePtr<void>(Params));
UE_LOG(LogTemp, Warning, TEXT("Return value: %d"), Result);
}
}
}
四,TFieldIterator
// 可迭代任何继承自 UField 或 FField 的反射类型 <T> template <class T> class TFieldIterator { private: const UStruct* Struct; typename T::BaseFieldClass* Field; // 指定迭代类型的最基类(UField/FField) int32 InterfaceIndex; const bool bIncludeSuper; const bool bIncludeDeprecated; const bool bIncludeInterface; public: TFieldIterator(const UStruct* InStruct, EFieldIterationFlags InIterationFlags = EFieldIterationFlags::Default) explicit operator bool() const; bool operator !() const; bool operator==(const TFieldIterator<T>& Rhs); operator!=(const TFieldIterator<T>& Rhs); void operator++(); // 移动迭代位置 Field->Next; T* operator*(); // 返回对应类型指针 (T*)Field T* operator->(); // 返回对应类型指针 (T*)Field const UStruct* GetStruct(); }
// 遍历成员变量
for (TFieldIterator<FProperty> it(GetClass(), EFieldIterationFlags::None); it; ++it) {
auto pro_name = (*it)->GetName();
auto protype_name = (*it)->GetClass()->GetName();
UE_LOG(LogTemp, Warning, TEXT("FProperty: %s; FFieldClass: %s;"), *pro_name, *protype_name);
}
//遍历成员函数
for (TFieldIterator<UFunction> it(GetClass(), EFieldIteratorFlags::ExcludeSuper); it; ++it) {
auto func_name = (*it)->GetName();
UE_LOG(LogTemp, Warning, TEXT("UFunction: %s;"), *func_name);
this->ProcessEvent(*it, nullptr);
}

6032



被折叠的 条评论
为什么被折叠?



