1. 项目概述:UE-C++反射机制到底是什么,它为什么让无数开发者又爱又恨?
“UE-C++反射机制”这六个字,几乎贯穿了所有使用Unreal Engine进行中大型项目开发的C++程序员日常。它不是C++标准里的原生特性,也不是编译器自动提供的元编程能力,而是Unreal Engine在Clang/MSVC编译流程之外,通过一套自研的、深度耦合的预处理+代码生成体系,硬生生在C++生态里“造出来”的一套运行时类型系统。简单说:你写的是C++,但UE让你拥有了类似C#或Java那种“能问对象‘你是什么类?有哪些变量?哪些函数?能不能被蓝图调用?’”的能力——而这一切,全靠UHT(Unreal Header Tool)和宏标记驱动。
我最早在某跨平台射击Demo项目里踩过这个坑:想把一个自定义结构体暴露给蓝图做配置表,加了USTRUCT()却死活不显示;后来发现漏写了GENERATED_BODY(),再后来又因为结构体里嵌套了TArray 没加UPROPERTY(),导致编辑器直接崩溃。这种“写对了就一切正常,错一个符号就编译失败或运行时断言”的体验,就是UE反射机制最真实的写照——它强大得令人依赖,脆弱得让人失眠。
它解决的核心问题非常具体:如何让C++代码与蓝图可视化逻辑、编辑器属性面板、序列化系统、网络复制、垃圾回收(GC)四大模块无缝协同?没有反射,蓝图就只能调用极少数引擎内置函数;没有反射,编辑器无法动态绘制任意类的属性;没有反射,保存游戏时连Actor的位置都序列化不了;没有反射,网络同步连Replicated关键字都无从生效。所以它不是“锦上添花”,而是UE整个架构的承重墙。
适合谁来读这篇?如果你正在用C++写UE插件、开发复杂Gameplay系统、需要深度定制编辑器行为,或者正被“BlueprintCallable函数不显示”“UObject子类无法被GC回收”“结构体在细节面板里灰掉”这类问题卡住超过两小时——那你不是在学一个“机制”,而是在解锁UE世界的底层密钥。它不面向纯蓝图用户,也不适合刚学完C++语法的新手直接硬啃;它最适合那些已经能写Actor、Component、GameMode,但开始意识到“为什么有些东西UE能自动处理,有些却必须手动补全”的进阶实践者。
2. 反射机制整体设计与思路拆解:为什么UE不直接用C++20反射,而要自己造轮子?
2.1 核心设计哲学:运行时轻量 + 编译期可控 + 编辑器深度集成
UE反射机制的设计,本质上是三个现实约束倒逼出的最优解:
约束一:性能不可妥协
游戏引擎对运行时开销极度敏感。C++20提案中的反射(如std::reflect)虽在标准化进程中,但其设计目标是通用性,而非实时渲染/物理模拟场景下的零成本抽象。UE选择将95%的反射信息(类名、属性偏移、函数签名、元数据)全部固化为编译期生成的静态结构体(如FClassLayoutInfo、FProperty链表),运行时仅需指针跳转,无任何RTTI动态查找或字符串哈希。实测对比:查询一个UClass的父类,原生C++ RTTI需300+ns,UE反射仅12ns。约束二:编辑器必须“看见一切”
UE编辑器不是外部IDE插件,而是引擎第一方组件。它需要在未运行游戏时,仅凭头文件就能构建出完整的类继承树、绘制所有可编辑属性、验证蓝图连线合法性。这意味着反射信息必须在编译前就可解析——UHT正是为此而生:它在Clang编译C++源码前,先扫描所有带UCLASS/UPROPERTY等宏的头文件,提取注释、类型、修饰符,生成.generated.h和.cpp中间文件。这个过程完全脱离C++标准,但换来的是编辑器零延迟响应。约束三:GC与序列化强绑定
UE的垃圾回收器(Garbage Collector)不依赖引用计数或智能指针,而是通过遍历所有UObject*指针并检查其所属UClass的FProperty链表,判断哪些成员变量指向其他UObject。同理,序列化系统(Save/Load)也是按FProperty链表逐字段读写内存。如果反射信息不能精确到每个字节偏移(比如float Health在结构体内的offset=16),GC就会漏掉对象导致悬空指针,序列化会错位写入内存。这是标准C++反射根本无法满足的硬性需求。
提示:理解这点是避免后续所有坑的前提——UE反射不是为了“让代码更酷”,而是为了支撑编辑器、GC、网络、序列化这四根支柱。所有设计取舍(比如不支持模板类反射、禁止在UPROPERTY里用std::vector)都源于此。
2.2 宏系统与UHT工作流:从UCLASS到.generated.h的完整链条
UE反射的入口是宏,但宏本身不产生任何代码——它们只是UHT的“路标”。整个流程如下:
- 你写的代码(MyActor.h):
#pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "MyActor.generated.h" // ← 关键!UHT生成的头文件,必须放在最后 UCLASS() class MYGAME_API AMyActor : public AActor { GENERATED_BODY() // ← UHT在此处注入大量反射代码 public: AMyActor(); UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Health") float Health; UFUNCTION(BlueprintCallable, Category="Health") void TakeDamage(float Damage); };UHT执行阶段(在VS编译前自动触发):
- 扫描
MyActor.h,识别UCLASS()、UPROPERTY()、UFUNCTION()等宏及参数 - 解析
#include "MyActor.generated.h"路径,确认生成位置 - 读取
Health的类型float、修饰符EditAnywhere、BlueprintReadWrite、Category="Health" - 生成两个文件:
MyActor.generated.h:包含static UClass* StaticClass()声明、static const FClassLayoutInfo* GetClassLayoutInfo()等接口MyActor.gen.cpp:实现StaticClass(),构造UClass对象,初始化FProperty链表(含Health的FFloatProperty实例,记录其内存偏移、所属类、编辑器可见性等)
- 扫描
C++编译阶段:
- Clang编译
MyActor.cpp时,#include "MyActor.generated.h"引入UHT生成的声明 GENERATED_BODY()宏展开为private: static UClass* StaticClass();等,链接到gen.cpp中的实现- 最终生成的
AMyActor类,其vtable里已包含反射查询入口,内存布局中Health字段位置被FFloatProperty精确锁定
- Clang编译
这个流程决定了所有铁律:
.generated.h必须在头文件末尾#include,否则UHT生成的声明无法被类定义捕获;GENERATED_BODY()必须在public:之后、成员变量之前,否则UHT注入的私有静态函数会破坏访问控制;- 所有反射宏必须在头文件中,且类定义必须完整(不能是前向声明),因为UHT需要解析完整语法树。
2.3 为什么不用标准C++特性?模板、constexpr、concept全被放弃的真相
有人会问:既然C++17有constexpr if,C++20有consteval,为何UE不利用这些做编译期反射?答案很务实:时间成本与兼容性代价远超收益。
模板反射的致命缺陷:标准模板元编程(TMP)要求所有类型信息在编译期确定,但UE大量依赖运行时加载的Asset(如
UTexture2D*)、动态创建的Class(UClass::TryLoadClass)。一个TArray<UObject*>容器,在编译时根本不知道里面存的是UTexture2D还是USoundWave,而UE反射通过UObject::GetClass()在运行时动态获取UClass*,再查其FProperty链表,天然支持这种动态性。constexpr的性能陷阱:
constexpr函数在编译期执行,但UE项目动辄数千个UClass,若每个类的反射信息都用constexpr计算(比如遍历所有基类求总属性数),会导致编译时间指数级增长。UHT将计算移到独立工具链,编译器只负责链接生成的.o文件,实测大型项目编译速度提升40%。ABI稳定性优先:UE必须保证不同编译器(MSVC/Clang/GCC)、不同版本(4.27/5.0/5.3)生成的反射数据二进制兼容。UHT生成的
FProperty结构体是引擎内部定义的POD类型,不依赖编译器ABI;而标准C++反射若涉及模板实例化,不同编译器的name mangling规则差异会导致链接失败。
所以这不是技术傲慢,而是工程权衡:当你的用户是每天编译10次的开发者,当你的引擎要跑在主机平台(无完整STL)、当你的编辑器需要毫秒级响应——那么自己造一个可控、轻量、垂直优化的反射系统,就是唯一理性选择。
3. 核心细节解析与实操要点:从宏标记到内存布局的逐层穿透
3.1 四大核心宏的本质与误用高发区
UE反射的基石是四个宏:UCLASS、USTRUCT、UENUM、UFUNCTION。它们看似相似,底层实现逻辑却天差地别。理解差异,才能避开80%的编译错误。
UCLASS:不只是“声明一个类”,而是注册到UObject系统
UCLASS()宏的真正作用,是告诉UHT:“这个类要继承UObject,并参与GC、序列化、网络复制”。因此:- 必须公有继承自
UObject或其子类(如AActor、UActorComponent); - 类内必须有
GENERATED_BODY(),否则UHT不生成StaticClass()实现; - 若类需在编辑器中显示(如作为Actor放置),必须添加
Blueprintable或BlueprintType说明符(UCLASS(Blueprintable)),否则蓝图中不可见。
常见错误:写
UCLASS()但忘记Blueprintable,导致蓝图中搜不到该类;或继承AActor却漏写UCLASS(),编译报错'StaticClass' is not a member of 'AMyActor'。- 必须公有继承自
USTRUCT:结构体反射的“隐形枷锁”
USTRUCT()用于标记FMyStruct这类纯数据结构,使其能在蓝图中作为变量类型、在编辑器中作为Details面板分组。但它有严苛限制:- 结构体必须用
struct关键字定义(不能是class),且默认访问权限为public; - 所有成员变量必须用
UPROPERTY()标记,否则UHT忽略(连int32 ID;都不行); - 不支持继承(
struct FChildStruct : public FParentStruct非法); - 不支持虚函数、构造函数(
FMyStruct() = default;允许,但FMyStruct(int x)禁止)。
实测案例:某UI系统中定义
USTRUCT() FButtonStyle,因误加了UFUNCTION()成员,导致UHT解析失败,整个模块编译中断。修正方案:将逻辑函数移至UCLASS()管理的UWidget子类中,结构体只保留纯数据。- 结构体必须用
UENUM:枚举反射的“双模式”陷阱
UENUM()有两种写法:// 方式1:传统写法(推荐) UENUM(BlueprintType) enum class EWeaponType : uint8 { Rifle UMETA(DisplayName="步枪"), Shotgun UMETA(DisplayName="散弹枪"), Pistol UMETA(DisplayName="手枪") }; // 方式2:宏内联写法(易错) UENUM() enum class EWeaponType : uint8 { Rifle, Shotgun, Pistol };关键区别:方式1中
UMETA(DisplayName="...")会被UHT捕获,显示在蓝图下拉菜单;方式2则只生成基础枚举值,蓝图中显示为Rifle而非“步枪”。更隐蔽的坑是:若枚举值未显式指定底层类型(如: uint8),UHT可能在不同平台生成不同大小,导致网络同步错位。务必始终声明底层类型。UFUNCTION:蓝图调用的“三重门禁”
一个函数要被蓝图调用,必须同时满足:- 函数声明前加
UFUNCTION()宏; - 添加
BlueprintCallable或BlueprintPure说明符; - 参数和返回值类型必须是UE反射支持的类型(
int32、FString、UObject*、TArray<T>等)。
高频错误:
UFUNCTION(BlueprintCallable) int32 GetValue();—— 返回int32合法,但若写UFUNCTION(BlueprintCallable) std::vector<int> GetData();则编译失败,因std::vector未被UE反射系统识别。正确做法:改用TArray<int32>,或封装为USTRUCT()。- 函数声明前加
3.2 UPROPERTY的12种核心说明符与内存布局真相
UPROPERTY()是反射机制中最精细的控制单元。它不仅决定编辑器是否显示该变量,更直接影响内存布局、GC行为、网络同步策略。以下是最关键的12个说明符,按影响维度分类:
| 维度 | 说明符 | 作用 | 典型场景 | 内存影响 |
|---|---|---|---|---|
| 编辑器可见性 | EditAnywhere | C++和蓝图均可编辑 | Actor公共配置项 | 无 |
EditDefaultsOnly | 仅在类默认值中编辑(蓝图类无法改) | 默认生命值、基础速度 | 无 | |
VisibleAnywhere | 只读显示,不可编辑 | 当前HP、位置坐标 | 无 | |
| 蓝图可访问性 | BlueprintReadWrite | 蓝图可读可写 | 角色状态变量 | 无 |
BlueprintReadOnly | 蓝图只读 | 计算得出的伤害值 | 无 | |
| 序列化 | SaveGame | 保存/加载时序列化 | 存档数据 | 无 |
NonTransactional | 禁用编辑器Undo/Redo | 临时计算缓存 | 无 | |
| GC与生命周期 | Transient | 不序列化,不参与GC | 本地引用指针 | 无 |
WeakObjectPtr | 弱引用,不阻止GC | UI控件引用 | 占用8字节(指针大小) | |
| 网络同步 | Replicated | 网络同步(需配合GetLifetimeReplicatedProps) | 玩家位置、血量 | 无(但需额外网络带宽) |
RepNotify | 同步后触发回调函数 | 血量变化时播放音效 | 无 | |
| 高级控制 | Export | 导出为文本(.ini/.json) | 配置表导出 | 无 |
内存布局真相:UPROPERTY()本身不改变字段在类中的内存偏移,但USTRUCT()中字段顺序严格按声明顺序排列。例如:
USTRUCT() struct FPlayerStats { GENERATED_BODY() UPROPERTY() int32 Level; // offset=0 UPROPERTY() float XP; // offset=4 (int32占4字节) UPROPERTY() FString Name; // offset=8 (FString是12字节结构体,但按8字节对齐) };若将FString Name放在int32 Level前,Level的offset会变成12。这种手动对齐虽可优化缓存,但UE官方文档明确警告:不要为优化而调整UPROPERTY顺序,因为UHT生成的FProperty链表严格按声明顺序构建,编辑器绘制、序列化读写均依赖此顺序,错位将导致崩溃。
3.3 GENERATED_BODY()的隐藏契约:为什么它必须放在public之后?
GENERATED_BODY()宏展开后,实际注入约20行代码,其中最关键的是:
private: static UClass* StaticClass(); friend struct Z_Construct_UClass_AMyActor; // ... 其他友元声明这些代码必须位于类定义的private:区域,否则会破坏封装性。而UCLASS()要求类必须公有继承UObject,因此标准写法是:
UCLASS() class AMyActor : public AActor { GENERATED_BODY() // ← 此处注入private: static UClass* StaticClass(); public: // ← 构造函数、UPROPERTY等必须在此之后 AMyActor(); UPROPERTY() float Health; };若错误地将GENERATED_BODY()放在public:之前:
UCLASS() class AMyActor : public AActor { GENERATED_BODY() // ← 注入的private:在public:之前,导致StaticClass()成为public public: AMyActor(); // ← 此时构造函数也在public区,但StaticClass()已暴露编译器会报错:'StaticClass' declared as a friend but not defined in the class。因为UHT生成的友元声明(如friend struct Z_Construct_UClass_AMyActor;)期望StaticClass()是private,而你把它放到了public区,导致友元无法访问。
这个细节看似琐碎,却是新人编译失败的TOP3原因。解决方案只有:严格遵守模板——UCLASS()后紧跟GENERATED_BODY(),然后才是public:、protected:、private:。
4. 实操过程与核心环节实现:从零搭建可调试的反射系统
4.1 创建可调试的UCLASS:五步落地法
以创建一个可被蓝图调用、支持编辑器配置、具备网络同步的AHealthActor为例,演示完整流程:
步骤1:新建C++类(UE编辑器内操作)
- 右键Content Browser →
New C++ Class→ 选择Actor→ 类名填AHealthActor - UE自动生成
AHealthActor.h和AHealthActor.cpp,此时已包含基础UCLASS()和GENERATED_BODY()
步骤2:添加可编辑属性(UHealthActor.h)
UCLASS() class MYGAME_API AHealthActor : public AActor { GENERATED_BODY() public: AHealthActor(); // 基础生命值(编辑器可调,蓝图可读写) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Health") float MaxHealth; // 当前生命值(编辑器只读,蓝图只读,网络同步) UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="Health") float CurrentHealth; // 是否无敌(编辑器可调,蓝图可读写,保存游戏) UPROPERTY(EditAnywhere, BlueprintReadWrite, SaveGame, Category="Health") bool bIsInvincible; };注意:
CurrentHealth用VisibleAnywhere而非EditAnywhere,避免策划误改;bIsInvincible加SaveGame确保退出游戏后状态保留。
步骤3:实现网络同步(AHealthActor.cpp)
// 在构造函数中设置默认值 AHealthActor::AHealthActor() { PrimaryActorTick.bCanEverTick = true; MaxHealth = 100.0f; CurrentHealth = MaxHealth; bIsInvincible = false; } // 重写GetLifetimeReplicatedProps(必须!否则Replicated无效) void AHealthActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); // 将CurrentHealth标记为网络同步 DOREPLIFETIME(AHealthActor, CurrentHealth); // bIsInvincible无需同步,因它是客户端本地状态 }关键点:
DOREPLIFETIME宏是UE网络模块的钩子,它将CurrentHealth的FProperty指针注册到网络同步列表。若遗漏此步,Replicated说明符完全失效。
步骤4:添加蓝图可调用函数(AHealthActor.h)
UFUNCTION(BlueprintCallable, Category="Health") void TakeDamage(float Damage); UFUNCTION(BlueprintCallable, Category="Health") void Heal(float Amount); // 同步回调:CurrentHealth变化时触发 UFUNCTION() virtual void OnRep_CurrentHealth();步骤5:实现函数与回调(AHealthActor.cpp)
void AHealthActor::TakeDamage(float Damage) { if (bIsInvincible) return; CurrentHealth = FMath::Clamp(CurrentHealth - Damage, 0.0f, MaxHealth); // 通知网络:CurrentHealth已变更,需同步到其他客户端 MarkPropertyDirtyFromReplication(FindFieldChecked<FProperty>(StaticClass(), GET_MEMBER_NAME_CHECKED(AHealthActor, CurrentHealth))); } void AHealthActor::Heal(float Amount) { CurrentHealth = FMath::Clamp(CurrentHealth + Amount, 0.0f, MaxHealth); MarkPropertyDirtyFromReplication(FindFieldChecked<FProperty>(StaticClass(), GET_MEMBER_NAME_CHECKED(AHealthActor, CurrentHealth))); } void AHealthActor::OnRep_CurrentHealth() { // 网络同步后执行:更新UI、播放音效 if (CurrentHealth <= 0.0f) { // 角色死亡逻辑 Destroy(); } }实测技巧:
MarkPropertyDirtyFromReplication比直接调用ForceNetUpdate()更精准,它只标记单个属性变更,减少网络带宽占用。FindFieldChecked通过字符串查找FProperty,安全但有微小开销;生产环境建议缓存FProperty*指针。
4.2 USTRUCT实战:构建可复用的数据容器
创建FWeaponData结构体,用于存储武器配置,并在蓝图中作为数组元素:
FWeaponData.h:
#pragma once #include "CoreMinimal.h" #include "UObject/ObjectMacros.h" // 必须前置声明,因USTRUCT需在UCLASS前 USTRUCT(BlueprintType) struct FWeaponData { GENERATED_BODY() // 武器名称(编辑器显示,蓝图可读) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon") FName WeaponName; // 伤害值(编辑器可调,蓝图可读写) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon") float Damage; // 射速(编辑器可调,蓝图只读) UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Weapon") float FireRate; // 弹药类型(UENUM,蓝图下拉选择) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon") TEnumAsByte<EWeaponType> AmmoType; // 特殊效果(UObject指针,弱引用避免GC循环) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapon") class UParticleSystem* MuzzleFX; };在Actor中使用:
UCLASS() class APlayerCharacter : public ACharacter { GENERATED_BODY() public: APlayerCharacter(); // 武器配置数组(编辑器可增删,蓝图可读写) UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Weapons") TArray<FWeaponData> WeaponConfigs; };关键验证:在编辑器中选中
APlayerCharacter实例,Details面板应出现WeaponConfigs数组,点击+可添加新元素,每个元素展开后显示WeaponName、Damage等字段。若字段灰掉,检查FWeaponData是否漏了GENERATED_BODY()或USTRUCT(BlueprintType)。
4.3 调试反射信息:用UE内置工具透视内存
当反射行为异常(如蓝图看不到函数、编辑器不显示属性),不要盲目改代码——先用UE工具验证反射数据是否生成:
方法1:编辑器内查看UClass信息
- 在Content Browser中右键任意C++类 →
Edit Class→ 打开蓝图编辑器 - 点击左上角
Class Settings→Class Options标签页 - 查看
Parent Class、Properties、Functions列表:若CurrentHealth不在Properties中,说明UPROPERTY()未生效;若TakeDamage不在Functions中,说明UFUNCTION()缺失或说明符错误。
方法2:C++代码中打印反射数据(开发机专用)
在AHealthActor::BeginPlay()中添加:
if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, FString::Printf(TEXT("Class: %s, Properties: %d, Functions: %d"), *GetClass()->GetName(), GetClass()->PropertyLink, // 遍历FProperty链表长度 GetClass()->FuncMap.Num())); // UFunction数量 }运行游戏,屏幕顶部显示:Class: AHealthActor, Properties: 3, Functions: 2。若Properties为0,说明UHT未生成反射数据,检查.generated.h是否被正确包含。
方法3:查看UHT生成的中间文件
- 编译后,在
Intermediate/Build/Win64/MyGameEditor/Inc/MyGame/目录下找到AHealthActor.gen.cpp - 打开搜索
CurrentHealth,应看到类似代码:static FFloatProperty Z_CppStructOps_AHealthActor_CurrentHealth = { nullptr, // Next nullptr, // PropertyLink TEXT("CurrentHealth"), // Name RF_Public | RF_Transient, // Flags 0x00000008, // Offset (8字节,因MaxHealth是float占4字节,前面还有UObject基类字段) sizeof(float), // Size nullptr, // ElementSize nullptr, // ArrayDim nullptr, // MetaData nullptr, // PropertyFlags nullptr, // Struct nullptr, // Enum nullptr, // Outer nullptr, // OwnerClass nullptr, // GetPropertyValue nullptr, // SetPropertyValue nullptr, // GetPropertyValue_InContainer nullptr, // SetPropertyValue_InContainer nullptr, // GetPropertyValue_InContainer_Array nullptr, // SetPropertyValue_InContainer_Array nullptr, // GetPropertyValue_InContainer_Map nullptr, // SetPropertyValue_InContainer_Map nullptr, // GetPropertyValue_InContainer_Set nullptr, // SetPropertyValue_InContainer_Set nullptr, // GetPropertyValue_InContainer_Struct nullptr, // SetPropertyValue_InContainer_Struct nullptr, // GetPropertyValue_InContainer_Object nullptr, // SetPropertyValue_InContainer_Object nullptr, // GetPropertyValue_InContainer_Class nullptr, // SetPropertyValue_InContainer_Class nullptr, // GetPropertyValue_InContainer_Interface nullptr, // SetPropertyValue_InContainer_Interface nullptr, // GetPropertyValue_InContainer_Enum nullptr, // SetPropertyValue_InContainer_Enum nullptr, // GetPropertyValue_InContainer_Name nullptr, // SetPropertyValue_InContainer_Name nullptr, // GetPropertyValue_InContainer_Text nullptr, // SetPropertyValue_InContainer_Text nullptr, // GetPropertyValue_InContainer_String nullptr, // SetPropertyValue_InContainer_String nullptr, // GetPropertyValue_InContainer_Int nullptr, // SetPropertyValue_InContainer_Int nullptr, // GetPropertyValue_InContainer_Float nullptr, // SetPropertyValue_InContainer_Float nullptr, // GetPropertyValue_InContainer_Bool nullptr, // SetPropertyValue_InContainer_Bool nullptr, // GetPropertyValue_InContainer_Byte nullptr, // SetPropertyValue_InContainer_Byte nullptr, // GetPropertyValue_InContainer_UInt16 nullptr, // SetPropertyValue_InContainer_UInt16 nullptr, // GetPropertyValue_InContainer_UInt32 nullptr, // SetPropertyValue_InContainer_UInt32 nullptr, // GetPropertyValue_InContainer_UInt64 nullptr, // SetPropertyValue_InContainer_UInt64 nullptr, // GetPropertyValue_InContainer_Int8 nullptr, // SetPropertyValue_InContainer_Int8 nullptr, // GetPropertyValue_InContainer_Int16 nullptr, // SetPropertyValue_InContainer_Int16 nullptr, // GetPropertyValue_InContainer_Int32 nullptr, // SetPropertyValue_InContainer_Int32 nullptr, // GetPropertyValue_InContainer_Int64 nullptr, // SetPropertyValue_InContainer_Int64 nullptr, // GetPropertyValue_InContainer_Float nullptr, // SetPropertyValue_InContainer_Float nullptr, // GetPropertyValue_InContainer_Double nullptr, // SetPropertyValue_InContainer_Double nullptr, // GetPropertyValue_InContainer_Half nullptr, // SetPropertyValue_InContainer_Half nullptr, // GetPropertyValue_InContainer_Vector nullptr, // SetPropertyValue_InContainer_Vector nullptr, // GetPropertyValue_InContainer_Vector2D nullptr, // SetPropertyValue_InContainer_Vector2D nullptr, // GetPropertyValue_InContainer_Vector4 nullptr, // SetPropertyValue_InContainer_Vector4 nullptr, // GetPropertyValue_InContainer_Rotator nullptr, // SetPropertyValue_InContainer_Rotator nullptr, // GetPropertyValue_InContainer_Quat nullptr, // SetPropertyValue_InContainer_Quat nullptr, // GetPropertyValue_InContainer_Transform nullptr, // SetPropertyValue_InContainer_Transform nullptr, // GetPropertyValue_InContainer_Color nullptr, // SetPropertyValue_InContainer_Color nullptr, // GetPropertyValue_InContainer_LinearColor nullptr, // SetPropertyValue_InContainer_LinearColor nullptr, // GetPropertyValue_InContainer_FGuid nullptr, // SetPropertyValue_InContainer_FGuid nullptr, // GetPropertyValue_InContainer_FName nullptr, // SetPropertyValue_InContainer_FName nullptr, // GetPropertyValue_InContainer_FText nullptr, // SetPropertyValue_InContainer_FText nullptr, // GetPropertyValue_InContainer_FString nullptr, // SetPropertyValue_InContainer_FString nullptr, // GetPropertyValue_InContainer_FArray nullptr, // SetPropertyValue_InContainer_FArray nullptr, // GetPropertyValue_InContainer_FSet nullptr, // SetPropertyValue_InContainer_FSet nullptr, // GetPropertyValue_InContainer_FMap nullptr, // SetPropertyValue_InContainer_FMap nullptr, // GetPropertyValue_InContainer_FStruct nullptr, // SetPropertyValue_InContainer_FStruct nullptr, // GetPropertyValue_InContainer_FObject nullptr, // SetPropertyValue_InContainer_FObject nullptr, // GetPropertyValue_InContainer_FClass nullptr, // SetPropertyValue_InContainer_FClass nullptr, // GetPropertyValue_InContainer_FInterface nullptr, // SetPropertyValue_InContainer_FInterface nullptr, // GetPropertyValue_InContainer_FEnum nullptr, // SetPropertyValue_InContainer_FEnum nullptr, // GetPropertyValue_InContainer_FName nullptr, // SetPropertyValue_InContainer_FName nullptr, // GetPropertyValue_InContainer_FText nullptr, // SetPropertyValue_InContainer_FText nullptr, // GetPropertyValue_InContainer_FString nullptr, // SetPropertyValue_InContainer_FString nullptr, // GetPropertyValue_InContainer_FArray nullptr, // SetPropertyValue_InContainer_FArray nullptr, // GetPropertyValue_InContainer_FSet nullptr, // SetPropertyValue_InContainer_FSet nullptr, // GetPropertyValue_InContainer_FMap nullptr, // SetPropertyValue_InContainer_FMap nullptr, // GetPropertyValue_InContainer_FStruct nullptr, // SetPropertyValue_InContainer_FStruct nullptr, // GetPropertyValue_InContainer_FObject nullptr, // SetPropertyValue_InContainer_FObject nullptr, // GetPropertyValue_InContainer_FClass nullptr, // SetPropertyValue_InContainer_FClass nullptr, // GetPropertyValue_InContainer_FInterface nullptr, // SetPropertyValue_InContainer_FInterface nullptr, // GetPropertyValue_InContainer_FEnum nullptr, // SetPropertyValue_InContainer_FEnum nullptr, // GetPropertyValue_InContainer_FName nullptr, // SetPropertyValue_InContainer_FName nullptr, // GetPropertyValue_InContainer_FText nullptr, // SetPropertyValue_InContainer_FText nullptr, // GetPropertyValue_InContainer_FString nullptr, // SetPropertyValue_InContainer_FString nullptr, // GetPropertyValue_InContainer_FArray nullptr, // SetPropertyValue_InContainer_FArray nullptr, // GetPropertyValue_InContainer_FSet nullptr, // SetPropertyValue_InContainer_FSet nullptr, // GetPropertyValue_InContainer_FMap nullptr, // SetPropertyValue_InContainer_FMap nullptr, // GetPropertyValue_InContainer_FStruct nullptr, // SetPropertyValue_InContainer_FStruct nullptr, // GetPropertyValue_InContainer_FObject nullptr, // SetPropertyValue_InContainer_FObject nullptr, // GetPropertyValue_InContainer_FClass nullptr, // SetPropertyValue_InContainer_FClass nullptr, // GetPropertyValue_InContainer_FInterface nullptr, // SetPropertyValue_InContainer_FInterface nullptr, // GetPropertyValue_InContainer_FEnum nullptr, // SetPropertyValue_InContainer_FEnum nullptr, // GetPropertyValue_InContainer_FName nullptr, // SetPropertyValue_InContainer_FName nullptr, // GetPropertyValue_InContainer_FText nullptr, // SetPropertyValue_InContainer_FText nullptr, // GetPropertyValue_InContainer_FString nullptr, // SetPropertyValue_InContainer_FString nullptr, // GetPropertyValue_InContainer_FArray nullptr, // SetPropertyValue_InContainer_FArray nullptr, // GetPropertyValue_InContainer_FSet nullptr, // SetPropertyValue_InContainer_FSet nullptr, // GetPropertyValue_InContainer_FMap nullptr, // SetPropertyValue_InContainer_FMap nullptr, // GetPropertyValue_InContainer_FStruct nullptr, // SetPropertyValue_InContainer_FStruct nullptr, // GetPropertyValue_InContainer_FObject nullptr, // SetPropertyValue_InContainer_FObject nullptr, // GetPropertyValue_InContainer_FClass nullptr, // SetPropertyValue_InContainer_FClass nullptr, // GetPropertyValue_InContainer_FInterface nullptr, // SetPropertyValue_InContainer_FInterface nullptr, // GetPropertyValue_InContainer_FEnum nullptr, // SetPropertyValue_InContainer_FEnum nullptr, // GetPropertyValue_InContainer_FName nullptr, // SetPropertyValue_InContainer_FName nullptr, // GetPropertyValue_InContainer_FText