UE反射

1 UE反射的原理

1.1 UE是如何实现反射机制

Unreal Engine(UE)通过一套宏、元数据和自定义的对象系统实现了反射机制。这些机制允许在运行时获取类型信息、访问和修改对象的属性和方法。以下是UE反射机制的核心部分及其实现方式:

1.1.1 核心组件

  • UObject和UClass
  • 宏系统
  • 元数据系统
  • 反射API

1. UObject和UClass
UObject:所有支持反射的类都必须继承自UObjectUObject是UE的所有反射类的基类,提供了反射所需的基础设施。
UClassUClassUObject的元类,包含了反射信息,如类的名称、属性和方法。

2. 宏系统
UE使用一套宏来标记和生成反射信息,这些宏包括UCLASSUSTRUCTUPROPERTYUFUNCTION等。

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:用于声明一个类支持反射。
UCLASS()
class MYGAME_API AMyActor : public AActor {
GENERATED_BODY()
}

//USTRUCT:用于声明一个结构体支持反射。
USTRUCT()
struct FMyStruct {
GENERATED_BODY()
UPROPERTY()
int32 MyProperty;
};

//UPROPERTY:用于声明类或结构体中的属性支持反射。
UCLASS()
class MYGAME_API AMyActor : public AActor {
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Custom")
int32 MyProperty;
}

//UFUNCTION:用于声明类中的方法支持反射。
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
//查找类和创建实例: FindObject和StaticConstructObject用于查找类并创建其实例。
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++头文件,并生成反射信息和必要的代码。

工作流程:

  1. 解析C++头文件:
    • UHT读取和解析C++头文件,识别其中的反射宏(如UCLASSUSTRUCTUPROPERTYUFUNCTION等)。
  2. 生成代码
    • 根据解析到的元数据,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() // 这里插入由UHT生成的代码

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并处理生成的代码文件。

工作流程:

  1. 调用UHT
    • 在编译过程中,UBT会调用UHT来解析头文件,并生成.generated.h文件
  2. 编译生成的代码
    • UBT会将生成的.generated.h文件与原始代码一起编译,确保所有反射信息和元数据都被正确集成到最终的二进制文件中。
1.2.2.1 反射机制的工作流程
  1. 代码编写
    • 开发者在C++头文件中使用UCLASSUSTRUCTUPROPERTYUFUNCTION等宏标记类、结构体、属性和方法。
  2. UHT解析
    • UBT在编译时调用UHT,UHT解析C++头文件,识别反射宏,并生成.generated.h文件。
  3. 代码生成
    • UHT生成的.generated.h文件包含了类型信息、属性和方法的元数据注册代码。
  4. 编译
    • UBT将原始代码和生成的代码一起编译,生成最终的二进制文件。
  5. 运行时反射
    • 在运行时,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
// MyActor.h
#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
#include "MyActor.h"

void AMyActor::MyFunction() {
UE_LOG(LogTemp, Warning, TEXT("MyFunction called!"));
}

// UsageExample.cpp
void UseReflection() {
// 查找MyActor类
UClass* MyActorClass = AMyActor::StaticClass();

// 创建MyActor实例
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
// 通过StaticClass查找类(静态函数查找)
UClass* MyActorClass = AMyActor::StaticClass();

// 通过FindObject查找类(运行时根据对象名称查找类)
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() {
// 查找MyActor类
UClass* MyActorClass = AMyActor::StaticClass();

// 创建MyActor实例
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博客