1 UE反射的原理
1.1 UE是如何实现反射机制
Unreal Engine(UE)通过一套宏、元数据和自定义的对象系统实现了反射机制。这些机制允许在运行时获取类型信息、访问和修改对象的属性和方法。以下是UE反射机制的核心部分及其实现方式:
1.1.1 核心组件
- UObject和UClass
- 宏系统
- 元数据系统
- 反射API
1. UObject和UClass
UObject:所有支持反射的类都必须继承自UObject。UObject是UE的所有反射类的基类,提供了反射所需的基础设施。
UClass:UClass是UObject的元类,包含了反射信息,如类的名称、属性和方法。
2. 宏系统
UE使用一套宏来标记和生成反射信息,这些宏包括UCLASS、USTRUCT、UPROPERTY和UFUNCTION等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| UCLASS() class MYGAME_API AMyActor : public AActor { GENERATED_BODY() }
USTRUCT() struct FMyStruct { GENERATED_BODY() UPROPERTY() int32 MyProperty; };
UCLASS() class MYGAME_API AMyActor : public AActor { GENERATED_BODY() public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Custom") int32 MyProperty; }
UCLASS() class MYGAME_API AMyActor : public AActor { GENERATED_BODY()
public: UFUNCTION(BlueprintCallable, Category="Custom") void MyFunction(); }
|
3. 元数据系统
UE的反射系统通过生成元数据文件来记录类、属性和方法的信息。这些元数据在编译时生成,并在运行时使用。
编译时生成:使用宏系统,UE在编译时生成元数据文件,描述类、属性和方法的反射信息。
运行时使用:在运行时,UE使用这些元数据来实现反射功能,如动态属性访问、方法调用等。
4. 反射API
UE提供了一套反射API,用于在运行时访问和操作反射信息。这些API包括但不限于:
1 2 3 4 5 6
| UClass* MyActorClass = FindObject<UClass>(ANY_PACKAGE, TEXT("MyActor")); if (MyActorClass) { AMyActor* MyActorInstance = NewObject<AMyActor>(MyActorClass); }
|
访问属性:UProperty类及其派生类(如UIntProperty、UFloatProperty等)用于访问和修改对象的属性。
1 2 3 4 5 6 7 8
| UClass* MyActorClass = AMyActor::StaticClass(); UProperty* MyProperty = FindField<UProperty>(MyActorClass, TEXT("MyProperty"));
AMyActor* MyActorInstance = NewObject<AMyActor>(MyActorClass); if (MyProperty) { MyProperty->SetPropertyValue_InContainer(MyActorInstance, 42); }
|
调用方法:UFunction类用于调用对象的方法。
1 2 3 4 5 6 7
| UClass* MyActorClass = AMyActor::StaticClass(); UFunction* MyFunction = MyActorClass->FindFunctionByName(TEXT("MyFunction"));
AMyActor* MyActorInstance = NewObject<AMyActor>(MyActorClass); if (MyFunction) { MyActorInstance->ProcessEvent(MyFunction, nullptr); }
|
1.2.2 反射的实现步骤
Unreal Engine(UE)通过一套宏、元数据和自定义的对象系统实现了反射机制,这些机制依赖于编译时的代码生成和运行时的反射API。
以下是反射机制的实现过程,详细解释了.generated.h文件、GENERATED_BODY()宏、UHT(Unreal Header Tool)和UBT(Unreal Build Tool)的作用及其在不同阶段的功能:
1. Unreal Header Tool (UHT)
作用: UHT是一个在编译时运行的工具,只用于解析带有UE4宏的C++头文件,并生成反射信息和必要的代码。
工作流程:
- 解析C++头文件:
- UHT读取和解析C++头文件,识别其中的反射宏(如
UCLASS、USTRUCT、UPROPERTY、UFUNCTION等)。
- 生成代码
- 根据解析到的元数据,UHT生成对应的
.generated.h文件,这些文件包含反射系统所需的额外代码。
- 生成的代码包括类型信息、属性和方法的元数据注册代码等。
2. .generated.h 文件
作用:.generated.h文件由UHT生成,包含了反射系统所需的自动生成的代码。它们通常会在每个类或结构体的头文件中被包含。
3. GENERATED_BODY() 宏
作用:GENERATED_BODY()宏是一个复杂的宏,它扩展为一系列声明和定义,这些声明和定义由UHT生成,用于支持UE4的反射系统。
1 2 3 4 5 6 7 8 9 10 11 12 13
| UCLASS() class MYGAME_API AMyActor : public AActor { GENERATED_BODY()
public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom") int32 MyProperty;
UFUNCTION(BlueprintCallable, Category = "Custom") void MyFunction(); };
|
展开后的效果: GENERATED_BODY()宏会展开为在.generated.h文件中生成的实际代码,包括反射信息和元数据注册函数的声明。
4. Unreal Build Tool (UBT)
作用: UBT是UE4的构建系统,用于管理项目的编译过程。它调用UHT并处理生成的代码文件。
工作流程:
- 调用UHT
- 在编译过程中,UBT会调用UHT来解析头文件,并生成
.generated.h文件
- 编译生成的代码
- UBT会将生成的
.generated.h文件与原始代码一起编译,确保所有反射信息和元数据都被正确集成到最终的二进制文件中。
1.2.2.1 反射机制的工作流程
- 代码编写
- 开发者在C++头文件中使用
UCLASS、USTRUCT、UPROPERTY、UFUNCTION等宏标记类、结构体、属性和方法。
- UHT解析
- UBT在编译时调用UHT,UHT解析C++头文件,识别反射宏,并生成
.generated.h文件。
- 代码生成
- UHT生成的
.generated.h文件包含了类型信息、属性和方法的元数据注册代码。
- 编译
- UBT将原始代码和生成的代码一起编译,生成最终的二进制文件。
- 运行时反射
- 在运行时,UE4使用生成的元数据和反射API来动态访问和操作对象的属性和方法。
1.2.2 示例
以下是一个完整的示例,展示了如何使用UE的反射机制动态访问属性和调用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #pragma once
#include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "MyActor.generated.h"
UCLASS() class MYGAME_API AMyActor : public AActor { GENERATED_BODY()
public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom") int32 MyProperty;
UFUNCTION(BlueprintCallable, Category = "Custom") void MyFunction(); };
#include "MyActor.h"
void AMyActor::MyFunction() { UE_LOG(LogTemp, Warning, TEXT("MyFunction called!")); }
void UseReflection() { UClass* MyActorClass = AMyActor::StaticClass();
AMyActor* MyActorInstance = NewObject<AMyActor>(MyActorClass);
UProperty* MyProperty = FindField<UProperty>(MyActorClass, TEXT("MyProperty")); if (MyProperty) { MyProperty->SetPropertyValue_InContainer(MyActorInstance, 42); int32 PropertyValue; MyProperty->GetValue_InContainer(MyActorInstance, &PropertyValue); UE_LOG(LogTemp, Warning, TEXT("MyProperty value: %d"), PropertyValue); }
UFunction* MyFunction = MyActorClass->FindFunctionByName(TEXT("MyFunction")); if (MyFunction) { MyActorInstance->ProcessEvent(MyFunction, nullptr); } }
|
2 使用UE反射机制
2.1 使用反射机制的前提
要使用反射机制有四个必要条件:①继承UObject类;②包含.generated.h头文件;③添加了GENERATED_BODY()宏;④给需要反射的类、属性、方法通过宏进行标记。
前三个条件都没必要强调,在UE中创建一个集成UObject的类自动就帮我们设置好了。所以讨论下第四点就行。
使用反射系统的总开关是类的反射,UCLASS(),如果这个类没有用这个宏进行标记,则内部的属性或方法标记了也不能反射。这一点需要注意。
同时反射宏还有需要可以配置的
2.2 使用反射API
Unreal Engine 4(UE4)反射API提供了一套强大且灵活的工具,用于在运行时动态地访问和操作对象的属性和方法。以下是对UE4反射API的详细介绍,包括常用类和函数的使用方法。
1. 核心类和函数
UE4的反射API主要涉及以下几个核心类和函数:
- UClass: 描述一个UObject类的元数据。
- UObject: 所有反射对象的基类。
- UProperty: 描述对象属性的元数据。
- UFunction: 描述对象方法的元数据。
- FField: 用于访问对象的属性和方法。
2. 查找类 == 查找UClass对象
使用StaticClass、FindObject或GetClass函数来查找类。这三种方法使用场景不一样。
StaticClass函数是静态函数,直接通过类调用静态函数即可。
FindObject函数直接在运行时通过类名来查找UClass对象。
GetClass函数是通过对象实例来查找UClass对象。
1 2 3 4 5 6 7 8 9
| UClass* MyActorClass = AMyActor::StaticClass();
UClass* MyActorClass = FindObject<UClass>(ANY_PACKAGE, TEXT("MyActor"));
UClass* ObjectClass = Object->GetClass();
|
3. 创建实例
使用NewObject函数来创建类的实例。
1
| AMyActor* MyActorInstance = NewObject<AMyActor>(MyActorClass);
|
4. 访问属性
使用FindField函数查找属性,然后使用属性的API来获取或设置属性值
1
| UProperty* MyProperty = FindField<UProperty>(MyActorClass, TEXT("MyProperty"));
|
获取和设置属性值
1 2 3 4 5 6 7 8 9
| if (MyProperty) { MyProperty->SetPropertyValue_InContainer(MyActorInstance, 42);
int32 PropertyValue; MyProperty->GetValue_InContainer(MyActorInstance, &PropertyValue); UE_LOG(LogTemp, Warning, TEXT("MyProperty value: %d"), PropertyValue); }
|
5. 调用方法
使用FindFunctionByName函数查找方法,然后使用ProcessEvent函数调用方法。
查找方法
1
| UFunction* MyFunction = MyActorClass->FindFunctionByName(TEXT("MyFunction"));
|
调用方法
1 2 3
| if (MyFunction) { MyActorInstance->ProcessEvent(MyFunction, nullptr); }
|
6. 实际应用示例
下面是一个完整的示例,展示如何使用UE4反射API在运行时访问和操作对象的属性和方法。
MyActor.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #pragma once
#include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "MyActor.generated.h"
UCLASS() class MYGAME_API AMyActor : public AActor { GENERATED_BODY()
public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom") int32 MyProperty;
UFUNCTION(BlueprintCallable, Category = "Custom") void MyFunction(); };
|
MyActor.cpp:
1 2 3 4 5
| #include "MyActor.h"
void AMyActor::MyFunction() { UE_LOG(LogTemp, Warning, TEXT("MyFunction called!")); }
|
UsageExample.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include "MyActor.h"
void UseReflection() { UClass* MyActorClass = AMyActor::StaticClass();
AMyActor* MyActorInstance = NewObject<AMyActor>(MyActorClass);
UProperty* MyProperty = FindField<UProperty>(MyActorClass, TEXT("MyProperty")); if (MyProperty) { MyProperty->SetPropertyValue_InContainer(MyActorInstance, 42); int32 PropertyValue; MyProperty->GetValue_InContainer(MyActorInstance, &PropertyValue); UE_LOG(LogTemp, Warning, TEXT("MyProperty value: %d"), PropertyValue); }
UFunction* MyFunction = MyActorClass->FindFunctionByName(TEXT("MyFunction")); if (MyFunction) { MyActorInstance->ProcessEvent(MyFunction, nullptr); } }
|
7. 高级用法
获取类的所有属性:可以使用TFieldIterator来遍历类的所有属性。
1 2 3 4
| for (TFieldIterator<UProperty> It(MyActorClass); It; ++It) { UProperty* Property = *It; UE_LOG(LogTemp, Warning, TEXT("Property: %s"), *Property->GetName()); }
|
获取类的所有方法:可以使用TFieldIterator来遍历类的所有方法。
1 2 3 4
| for (TFieldIterator<UFunction> It(MyActorClass); It; ++It) { UFunction* Function = *It; UE_LOG(LogTemp, Warning, TEXT("Function: %s"), *Function->GetName()); }
|
动态创建对象并设置属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| void DynamicCreateAndSetProperty() { UClass* MyActorClass = AMyActor::StaticClass(); AMyActor* MyActorInstance = NewObject<AMyActor>(MyActorClass);
UProperty* MyProperty = FindField<UProperty>(MyActorClass, TEXT("MyProperty")); if (MyProperty) { MyProperty->SetPropertyValue_InContainer(MyActorInstance, 100); int32 PropertyValue; MyProperty->GetValue_InContainer(MyActorInstance, &PropertyValue); UE_LOG(LogTemp, Warning, TEXT("Property value: %d"), PropertyValue); }
UFunction* MyFunction = MyActorClass->FindFunctionByName(TEXT("MyFunction")); if (MyFunction) { MyActorInstance->ProcessEvent(MyFunction, nullptr); } }
|
【UE 反射】反射的原理是什么?如何使用机制?-CSDN博客