目录
一,常用UEC++变量类型
二,理解UBT/UHT
三,反射系统
关键宏
用于生成
用于调用(运行时)
四,UClass
FProperty
UFunction
一,常用UEC++变量类型
// 基础类型 bool bool_val = 1; uint8 byte_val = 255; int32 int32_val = -32; int64 int64_val = -64; double double_val = 3.14; // UE_LOG 的第 3 个参数(Format)必须是格式化字符串字面量,便于编译时检查及节省运行时开销; UE_LOG(LogTemp, Error, TEXT("%d"), bool_val); UE_LOG(LogTemp, Warning, TEXT("%d"), byte_val); UE_LOG(LogTemp, Warning, TEXT("%d"), int32_val); UE_LOG(LogTemp, Display, TEXT("%d"), int64_val); UE_LOG(LogTemp, Display, TEXT("%f"), double_val);// 字符串类型 FString str = TEXT("FString!"); //内部是TArray<TCHAR>的封装,并重载了operator*() FName name = TEXT("FName!"); FText text = FText::FromString(TEXT("FText!")); UE_LOG(LogTemp, Display, TEXT("%s"), *str); UE_LOG(LogTemp, Display, TEXT("%s"), *name.ToString()); UE_LOG(LogTemp, Display, TEXT("%s"), *text.ToString());// 容器类型 TArray<int32> arr = { 1,2,3,4,5 }; TSet<int32> set = { 10,20,30,40,50 }; TMap<int32, FString> map = { {1, TEXT("one")}, {2, TEXT("two")} }; FString arr_ret; for (auto num : arr) arr_ret += FString::FromInt(num) + TEXT(" "); GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, arr_ret); FString set_ret; for (auto num : set) set_ret += FString::FromInt(num) + TEXT(" "); GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, set_ret); FString map_ret; for (auto num : map) map_ret += num.Value + TEXT(" "); GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, map_ret);
// 在.h头文件中定义一个跨文件/全局的自定义日志类别; // 声明(.h 中)必须和定义(.cpp 中)配对使用,否则链接会报错; // .cpp文件定义 DEFINE_LOG_CATEGORY(MyCategory); DECLARE_LOG_CATEGORY_EXTERN(MyCategory, Log, All); // 在.cpp源文件中定义一个仅当前文件可见(静态)的自定义日志类别; DEFINE_LOG_CATEGORY_STATIC(LogBlankProgram, Log, All); // MyCategory,自定义类别名; // Log,运行时的默认详细级别,枚举ELogVerbosity; // Fatal,致命错误(触发断点/崩溃); // Error,严重问题(程序能跑,打印红色文字); // Warning,警告/潜在问题(不影响主流程,打印黄色文字); // Display,显示; // Log,普通日志; // Verbose,详解日志; // VeryVerbose,极详细日志; // All,编译时过滤级别(相当于总开关),与上一参数一样; // NoLogging,不输出日志; // All,保留所有日志;等同于 VeryVerbose;
二,理解UBT/UHT
UBT(Unreal Build Tool )虚幻编译工具,负责管理整个编译流程,包括收集所需编译的模块、处理依赖关系、调用 UHT,最后再调用标准的 C++ 编译器(如 MSVC)完成最终编译;
UHT(Unreal Header Tool)预处理,负责解析头文件中的特殊宏(如
UCLASS(),UFUNCTION()等),读取为 UObject 系统添加的“元数据”,并自动生成*.generated.h和*.gen.cpp等中间代码,从而支持反射、蓝图调用等功能;
点击编译 │ ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 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()标记的变量,把值写入磁盘,加载时再自动恢复;- 垃圾回收,
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通过反射系统将对应属性暴露给蓝图;
// 编辑器可编辑性(Edit / Visible) 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 = (meta = (Category = "移动属性")) // 将属性分组显示在细节面板的指定分类下 UPROPERTY(meta = (ClampMin = "0", ClampMax = "100")) // 硬限制数值编辑范围(仅对浮点和整数) UPROPERTY(meta = (UIMin = "0.0", UIMax = "1.0")) // 软限制数值编辑范围(仅对浮点和整数) UPROPERTY(meta = (ArrayClamp = "MyArray")) // 限制数值在指定数组(对整数) UPROPERTY(meta = (AllowPrivateAccess = "true")) // 允许蓝图访问私有成员(不推荐,但有时有用)// 网络复制(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;
反射入口 → UClass (StaticClass / GetClass) ↓ 属性:FindPropertyByName → FProperty → ContainerPtrToValuePtr → 读写 函数:FindFunctionByName → UFunction → ProcessEvent → 调用 类型:IsA / IsChildOf 创建:NewObject / SpawnActor// 获取 UClass(元数据入口) T::StaticClass() // 获取已知 C++ 类 T 的 UClass(编译期) UObject::GetClass() // 获取实例对象所属的 UClass(运行时),继承于UObject 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*) // 判断对象是否属于某类(或子类),继承于UObject UClass::IsChildOf(UClass*) // 判断类是否继承自另一个类 Cast<T>(UObject*)/CastChecked<T> // 安全转换(编译期已知类型),UObject及其子类 CastField<T>(FProperty*)/CastFieldChecked<T> // 安全转换 FField 及其子类(如 FProperty) DynamicCast<T>(UObject*) // 运行时反射转换(类似 Cast) 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
- 是描述一个 UObject 派生类元数据(属性、函数、父类、大小等)的核心类型;
- 是UE反射系统为每一个
UCLASS(即带有UCLASS()宏的 UObject 派生类)自动生成的元数据类型(继承链UObject -> UField -> UStruct -> UClass);- 可理解为C++ type_info的超级增强版——不仅包含类型名称、父类信息,还存储了该类的所有属性(UPROPERTY)、函数(UFUNCTION)、以及用于序列化、网络复制、实例化等的额外数据;
- 该类型是引擎核心代码中已经写好的(位于
UObject.h/Class.h);- 是UE中“类的类”,提供了运行时对 C++ 类结构(属性、函数、继承、默认值)的完整描述和操作能力,是反射、动态创建、编辑器集成等所有高级功能的基础;
- 每个 UObject 派生类都有一个唯一的 UClass 对象(单例);
- UClass 对象是模块加载时创建的,其元数据内容由UHT生成提供;
- 基类的
UClass与子类的UClass,是两个完全独立、互不包含的UObject对象;
- 通过
GetSuperClass()/SuperStruct,获取父类UClass;注,UClass本身的UClass是其自身;
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: TObjectPtr<UStruct> SuperStruct; public: TObjectPtr<UField> Children; 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 中递归查找 } };
FProperty
FProperty是UE反射系统的核心,用于描述一个类或结构体的成员变量;从 UE 4.25 开始,替代原有UProperty,不再继承自UObject,以降低内存和性能开销;
FProperty是所有属性类型的基类,本身不存储属性的值,只存储属性的"元数据"(名称、类型、大小、偏移量、标志等);
FProperty是一个抽象基类,对于每种基本数据类型,都有具体的派生类来实现其特定行为;
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; 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 }// 简易伪代码 (基于 UE 5.x) 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. 获取属性值的内存地址 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; // 获取函数名称 FFieldClass* 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 };// 查找函数并调用 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); } } }