首页 > 编程学习 > Unreal Property System (Reflection) 虚幻属性系统(反射)

Unreal Property System (Reflection)

https://www.unrealengine.com/en-US/blog/unreal-property-system-reflection

三月 28, 2014

虚幻属性系统(反射)

特征

学习

编程

教程

作者:迈克尔·诺兰德


反射是程序在运行时检查自身的能力。这非常有用,是虚幻引擎的基础技术,为编辑器中的细节面板、序列化、垃圾回收、网络复制和蓝图/C++通信等许多系统提供支持。但是,C++本身并不支持任何形式的反射,因此虚幻引擎有自己的系统来收集、查询和操作有关C++类、结构、函数、成员变量和枚举的信息。我们通常将反射称为属性系统,因为反射也是一个图形术语。

反射系统是可选的。你需要注释任何你想要对反射系统可见的类型或属性,虚幻标题工具(UHT)将在你编译项目时收集这些信息。

标记

若要将标头标记为包含反射类型,请在文件顶部添加一个特殊的包含。这让UHT知道他们应该考虑这个文件,并且它也是系统实施所必需的(有关更多信息,请参阅“幕后花絮”部分)。

#include “文件名生成.h”

您现在可以使用 UENUM()、UCLASS()、USTRUCT()、UFUNCTION() 和 UPROPERTY() 来注释标头中的不同类型和成员变量。其中每个宏都位于类型或成员声明之前,并且可以包含其他说明符关键字。让我们看一个真实世界的例子(来自StrategyGame):

//
// Base class for mobile units (soldiers)

#include "StrategyTypes.h"
#include "StrategyChar.generated.h"

UCLASS(Abstract)

class AStrategyChar : public ACharacter, public IStrategyTeamInterface
{

	GENERATED_UCLASS_BODY()

	/** How many resources this pawn is worth when it dies. */
	UPROPERTY(EditAnywhere, Category=Pawn)
	int32 ResourcesToGather;

	/** set attachment for weapon slot */
	UFUNCTION(BlueprintCallable, Category=Attachment)
	void SetWeaponAttachment(class UStrategyAttachment* Weapon);

	UFUNCTION(BlueprintCallable, Category=Attachment)
	bool IsWeaponAttached();

	protected:

	/** melee anim */
	UPROPERTY(EditDefaultsOnly, Category=Pawn)
	UAnimMontage* MeleeAnim;

	/** Armor attachment slot */
	UPROPERTY()
	UStrategyAttachment* ArmorSlot;

	/** team number */
	uint8 MyTeamNum;

	[more code omitted]
};

此标头声明了一个名为 AStrategyChar 的新类,该类派生自 ACharacter。它使用 UCLASS() 来指示它被反射,这也与C++定义中的宏 GENERATED_UCLASS_BODY() 配对。GENERATED_UCLASS_BODY() / GENERATED_USTRUCT_BODY() 宏在反射类或结构中是必需的,因为它们将附加函数和 typedef 注入到类体中。

显示的第一个属性是 ResourcesToGather,它使用 EditAnywhere 和 Category=Pawn 进行批注。这意味着可以在编辑器的任何详细信息面板中编辑属性,并将显示在“Pawn”类别中。有几个带注释的函数标有蓝图可调用和一个类别,这意味着可以从蓝图调用它们。

正如 MyTeamNum 声明所示,在同一类中混合反射和非反射属性是可以的,只需注意非反射属性对所有依赖反射的系统都是不可见的(例如,存储原始未反射的 UObject 指针通常是危险的,因为垃圾回收器看不到您的引用)。

每个说明符关键字(如 EditAnywhere 或 BlueprintCallable)都会镜像到 ObjectBase.h 中,并附有关于含义或用法的简短注释。如果你不确定关键字的作用,Alt+G 通常会把你带到 ObjectBase.h 中的定义(它们不是真正的C++关键字,但 Intellisense 或 VAX 似乎并不介意/理解其中的区别)。

Check out the Gameplay Programming Reference for more information.

Limitations

UHT不是一个真正的C++解析器。它理解该语言的一个不错的子集,并积极尝试跳过任何可以跳过的文本;只关注反射的类型、函数和属性。但是,有些事情仍然会混淆它,因此在向现有标头添加反射类型时,您可能必须改写某些内容或将其包装在 #if CPP / #endif 对中。还应避免在任何带批注的属性或函数周围使用 #if/#ifdef(WITH_EDITOR 和 WITH_EDITORONLY_DATA 除外),因为生成的代码引用它们,并且会导致定义不为 true 的任何配置中的编译错误。

大多数常见类型按预期工作,但属性系统无法表示所有可能的C++类型(特别是仅支持少数模板类型,如 TArray 和 TSubclassOf,并且它们的模板参数不能是嵌套类型)。如果批注无法在运行时表示的类型,UHT 将给你一个描述性错误消息。

使用反射数据

大多数游戏代码可以在运行时忽略属性系统,享受它所支持的系统的好处,但在编写工具代码或构建游戏系统时,您可能会发现它很有用。

属性系统的类型层次结构如下所示:

UField
	UStruct
		UClass (C++ class)
		UScriptStruct (C++ struct)
		UFunction (C++ function)

	UEnum (C++ enumeration)

	UProperty (C++ member variable or function parameter)

		(Many subclasses for different types)

UStruct 是聚合结构的基本类型(包含其他成员的任何内容,例如 C++ 类、结构或函数),不应与C++结构(即 UScriptStruct)混淆。UClass可以包含函数或属性作为其子属性,而UFunction和UScriptStruct仅限于属性。

您可以通过编写UTypeName::StaticClass()或FTypeName::StaticStruct()来获取反射C++类型的UClass或UScriptStruct,并且您可以使用Instance->GetClass()获取UObject实例的类型(由于结构没有公共基类或所需的存储,因此无法获取结构实例的类型)。

要遍历 UStruct 的所有成员,请使用 TFieldIterator:

for (TFieldIterator<UProperty> PropIt(GetClass()); PropIt; ++PropIt)
{
	UProperty* Property = *PropIt;
	// Do something with the property
}

TFieldIterator 的模板参数用作筛选器(因此您可以使用 UField 查看属性和函数,或者只查看其中一个)。迭代器构造函数的第二个参数指示您是只希望在指定的类/结构中引入字段,还是也希望在父类/结构中引入字段(默认值);它对函数没有任何影响。

每种类型都有一组唯一的标志(EClassFlags + HasAnyClassFlags等),以及从UField继承的通用元数据存储系统。关键字说明符通常存储为标志或元数据,具体取决于运行时游戏中是否需要它们,还是仅用于编辑器功能。这允许剥离仅编辑器元数据以节省内存,而运行时标志始终可用。

您可以使用反射数据做很多不同的事情(枚举属性、以数据驱动的方式获取或设置值、调用反射函数,甚至构造新对象);与其在这里深入探讨任何一个案例,不如通过UnrealType.h和Class.h查看,并找到一个与你想要完成的事情类似的代码示例。

幕后花絮

如果您只想使用属性系统,则可以安全地跳过此部分,但了解其工作原理有助于激发包含反射类型的标头中的一些决策和限制。

虚幻构建工具(UBT)和虚幻标头工具(UHT)协同工作,生成支持运行时反射所需的数据。UBT 必须扫描标头才能完成其工作,并且它会记住包含至少一个反射类型的标头的任何模块。如果自上次编译以来这些标头中的任何一个发生了更改,则会调用 UHT 来收集和更新反射数据。UHT 解析标头,构建一组反射数据,然后生成包含反射数据(有助于每个模块 .generated.inl)以及各种帮助程序和 thunk 函数(每个标头 .generated.h)的C++代码。

将反射数据存储为生成的C++代码的主要好处之一是保证它与二进制文件同步。您永远无法加载过时或过时的反射数据,因为它是与引擎代码的其余部分一起编译的,并且它会计算成员偏移量/等......启动时使用C++表达式,而不是尝试对特定平台/编译器/优化组合的打包行为进行逆向工程。UHT也是作为一个独立的程序构建的,它不使用任何生成的标头,因此它避免了UE3中脚本编译器常见的鸡和蛋的问题。

生成的函数包括StaticClass() / StaticStruct()之类的函数,它们可以轻松获取类型的反射数据,以及用于从蓝图或网络复制调用C++函数的thunk。这些必须声明为类或结构的一部分,这解释了为什么GENERATED_UCLASS_BODY()或GENERATED_USTRUCT_BODY()宏包含在反射类型中,以及定义这些宏的#include“TypeName.generated.h”。

Copyright © 2010-2022 dgrt.cn 版权所有 |关于我们| 联系方式