虚幻4属性系统(反射)翻译

反射是程序在运行时进行自检的一种能力。它非常有用且在虚幻引擎中基础技术,支撑了诸如 编辑器中的细节面板、序列化、垃圾回收、网络复制、以及蓝图与C++交互等功能。然而,C++原生并不支持任意形式的反射,因此 虚幻引擎有它自己的系统用来 利用、查询以及操作关于C++类、结构体、函数 、成员变量以及枚举的信息。我们特意把反射叫做属性系统,因为反射也是一个图形术语。

反射系统是可以选择加入的。你需要给暴露给反射系统的类型或属性添加注解,这样Unreal Header Tool (UHT)就会在编译工程的时候利用那些信息生成特定的代码。

标记

为了标记一个头文件包含反射类型,需要在文件顶部添加一个特殊的include文件。这让UHT知道它需要考虑这个文件,并且在反射系统的实现里也是需要的。

#include "FileName.generated.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]
};

  

  

这个头文件声明了一个继承自ACharacter叫做AStrategyChar的类。它使用UCLASS()来表明它是需要反射的类型,在C++定义内部它还包含一个GENERATED_UCLASS_BODY()的宏。GENERATED_UCLASS_BODY() / GENERATED_USTRUCT_BODY()在需要反射的类或者结构体里面是必要的,因为它们会加入额外的函数和typedef到类的内部。

第一个显示的属性是ResourcesToGather,它包含了EditAnywhere和Category=Pawn的注解。这表示这个 属性可以在编辑中的任意细节面板中进行编辑,并且会显示在Pawn分类中。有几个以BlueprintCallable以及分类作为注解的函数,这表示它们可以在蓝图里面调用到这些C++函数。

由MyTeamNum的定义所示,在同一个类里面混合使用需要反射的属性以及不需要反射的属性是允许的,你只需要记住非反射的属性对所有依赖反射功能的系统都是不可见的(例如存储一个非反射的UObject裸指针是很危险的,因为垃圾回收系统不知道你在引用着它。)

每一个修饰符(例如EdityAnywhere、BlueprintCallabe)在ObjectBase.h中均有定义,并且都带有一个简短的注释来说明它的意义或者用途。如果你不清楚一个关键字是干什么的,Alt+G一般会把你带到ObjectBase.h的定义处。

查看游戏性编程参考来获取更多信息。

限制

UHT并不是一个真正的C++分析器,它只能理解这个语言的一个子集并且会尝试跳过那些它不需要的文本。只关注那些跟反射类型、函数以及属性。然而,一些用法仍然会迷惑它,因此当你需要添加 一个反射类型到一个头文件时,你可能 需要改写一些代码或者把它们用#if CPP / #endif。你也应该避免使用#if /#endif(除了WITH_EDITOR和WITH_EDITORONLY_DATA)把注解的属性或者函数包含起来,因为生成的代码会引用 它们并且会在任意define为假的工程配置中造成编译错误。

大多娄的通用类型会正常得工作,但是属性系统并不能表示所有可能的C++类型(只支持一些如TArray和TSubclassOf的模板类型,并且它们的模板类型不能是嵌套的类型)。如果你注解了一个不同表示的类型,那么UHT在运行时会给出一个比较详细的错误描述。

使用反射数据

大多数的游戏代码可以在运行时忽略属性系统,也可以享受它给你带来的好处,但是当你在写工具代码或者构建游戏性系统的时候,你就会觉得它很有用了。

属性系统的类型层次大约如下所示:

UStruct是所有 聚合结构体的基础类型(包含其它成员的类型,比如一个C++类、结构体、或者函数),不应该跟C++中的结构体(struct)混为一谈(那是UScriptStruct)。UClass可以包含函数、属性以及它们的孩子,而UFunction和UStriptStruct只能包含属性。

你可以通过使用UTypeName::StaticClass()或者FTypeName::StaticStruct()来获取反射类型对应的UClass以及UScriptStruct,你也通过 一个UObject的实例通过Instance->GetClass()来获取类型(不能通过一个结构体实例的获取类型,因为结构体没有一个通用的基类或者需要的存储空间)。

要想迭代一个UStruct的所有成员,可以使用TFieldIterator:

for (TFieldIterator<UProperty> PropIt(GetClass()); PropIt; ++PropIt)

{

UProperty* Property = *PropIt;

// Do something with the property

}

  

TFieldIterator的模板参数作为一个过滤器(因此你可以通过使用UField查看所有属性和函数,或者仅查看其中的一个)。迭代器构造函数 的第二个参数是用来 表示 你是否只需要这个指定的类或结构体引入的项或者包括它父类/结构体(默认值 )。它对函数没有任何效果。

每一个类型都有一系列标记(EClassFlags + HasAnyClassFlag等),并且包含一个继承自UField的元数据(metadata)存储系统 。关键字修饰符存储为标记或者元数据,取决于它们是否游戏在运行时需要,或者只是 作为编辑器的功能。这允许只对编辑器有用的元数据可以去掉用来 节省内存,而运行时的标记却一直有效。

你可以利用反射数据来做很多不同的事情 (枚举属性,以数据驱动的方式来获取、设置值,调用反射函数,甚至是创建新的对象)与其深入这里说的某个事例,倒不如看看UnrealType.h和Class.h中的代码,并且研究其中的一个与你想要完成功能相似的代码示例。

反射实现机制简要说明

如果你仅仅是想用反射系统,那么你可以路过这个章节,但是了解它是如何工作的能帮助你激发一些决策和在包含反射系统的头文件中限制。

Unreal Build Tool(UBT)和Unreal Header Tool (UHT)两个协同工作来生成运行时反射需要的数据。UBT属性通过扫描头文件,记录任何至少有一个反射类型的头文件的模块。如果其中任意一个头文件从上一次编译起发生了变化,那么 UHT就会被调用来利用和更新反射数据。UHT分析头文件,创建一系列反射数据,并且生成包含反射数据的C++代码(放到每一个模块的moulde.generated.inl中。注:最新版会生成到moudle.generated.cpp中),还有各种帮助函数以及thunk函数(每一个 头文件 .generated.h)

用生成的C++代码来存储反射数据的一个最大好处就是,它可以保证跟二进制做到同步。你永远也不会加载陈旧或者过时的反射数据,因为它是跟引擎的其它代码同时编译的,并且它会在程序启动的时候使用C++表达式来计算成员偏移等,而不是通过针对特定平台/编译器/优化的组合中进行逆向工程。UHT作为一个单独的不使用任何生成头文件的程序来构建,因此它也避免了鸡生蛋、蛋生鸡的问题,这个在虚幻3的脚本编译器中一直被诟病。

生成的诸如StaticClass()、StaticStruct()函数是为了让当前类型更好的获取反射数据,以及那此转换函数(thunks)用来在蓝图或者网络复制中调用C++函数。这些必须声明为类或者结构体的一部分,这也就解释了为什么GENERATED_UCLASS_BODY() or GENERATED_USTRUCT_BODY()宏会包含在你的反射系统的类型中,而#include "TypeName.generated.h"的头文件中定义了这些宏。

时间: 2024-11-10 14:56:23

虚幻4属性系统(反射)翻译的相关文章

WPF - 属性系统 (4 of 4)

依赖项属性的重写 在基于C#的编程中,对属性的重写常常是一种行之有效的解决方案:在基类所提供的属性访问符实现不能满足当前要求的时候,我们就需要重新定义属性的访问符. 但对于依赖项属性而言,属性执行逻辑的重新定义并不能存在于CLR属性包装中:WPF内部对依赖项属性的实现要求依赖项属性的CLR包装实现仅仅调用GetValue()以及SetValue()属性,而不能提供其它的自定义逻辑.相反地,我们需要通过更改创建时所传入的元数据来指定自定义属性执行逻辑,甚至在某些更苛刻的要求下,如更改依赖项属性的类

WPF - 属性系统 (3 of 4)

依赖项属性元数据 在前面的章节中,我们已经介绍了WPF依赖项属性元数据中的两个组成:CoerceValueCallback回调以及PropertyChangedCallback.而在本节中,我们将对其它元数据属性进行讲解. 首先让我们来看看元数据对默认值的支持.在元数据的构造函数中,软件开发人员可以通过它的defaultValue参数指定该依赖项属性的默认值.如果在元数据中并没有指定依赖项属性的默认值,那么WPF属性系统会自动根据依赖项属性的类型为该依赖项属性指定一个默认值: private s

WPF - 属性系统 (1 of 4)

本来我希望这一系列文章能够深入讲解WPF属性系统的实现以及XAML编译器是如何使用这些依赖项属性的,并在最后分析WPF属性系统的实际实现代码.但是在编写的过程中发现对WPF属性系统代码的讲解要求之前的介绍能触及到属性系统的方方面面.而且其内部实现代码涉及到了众多的内部算法,对它们进行讲解反而可能导致读者产生更多迷惑.因此我最终改变了初衷,将这一系列文章重新定义为介绍WPF属性系统所提供的各种功能,并伴随各个功能讲解WPF属性系统的实际实现方式. 本系列文章将从最基础的有关依赖项属性的知识讲起,并

Qt属性系统

The Property System Qt提供一个类似于其他编译器供应商提供的精致的属性系统.然而,作为一个编译器和平台独立的库,Qt并不依赖于非标准编译器特性,如__property 或 [property].Qt解决方案能在支持Qt的平台上与任何标准C++编译器一起工作.它依赖于 Meta-Object System . Requirements for DeclaringProperties 要声明一个属性,在继承Qobject的类中用 Q_PROPERTY()宏. Q_PROPERTY

[Qt入门篇]5 Qt的属性系统——声明属性

[Qt入门篇]5 Qt的属性系统--声明属性 Qt提供了灵活的属性系统,它基于Qt的元对象系统,不依赖于编译器,这保证了Qt独立于编译其和平台的特点.这篇文章主要看看如何声明属性. 属性系统比较复杂,先看一个简单的例子.在QWidget中,有很多属性的声明,找一个简单学习: Q_PROPERTY(boolmodalREADisModal) 这里出现了5个元素:Q_PROPERTY.bool.modal.READ.isModal.这五个元素都是啥作用呢? Q_PROPERTY:用于声明属性的宏:

WPF - 属性系统 - APaas(AttachedProperty as a service)

是的,文章的题目看起来很牛,我承认. 附加属性是WPF中的一个非常重要的功能.例如在设置布局的过程中,软件开发人员就常常通过DockPanel的Dock附加属性来设置其各个子元素所处的布局位置.同样地,在为程序添加一个新的功能时,我们也常常需要创建自定义的附加属性来完成该功能. 附加属性简介 首先,我们要对附加属性有一个简单的认识:什么是附加属性,而为什么WPF提供了附加属性呢? 在WPF中,附加属性用来表示定义在一个类型上,却可以在其它特定类型实例上被使用的属性.由于该属性并非定义在这些实例所

WPF - 属性系统 (2 of 4)

属性更改回调 前一章的示例中,对各个参数的设置都非常容易理解.如果我们仅仅需要创建一个独立的依赖项属性,那么上面所提到的创建依赖项属性的基础知识足以满足需求.但是事情往往并非如此完美.在一个系统中,很少有属性是独立存在的,在WPF这种描述界面组成的类库中更是如此.例如一个属性的取值可能受其它众多属性的限制,或者一个属性值的更改可能导致其它依赖项属性值发生更改. 在WPF的属性系统中,这一切关联关系的维护都是通过元数据以及创建属性时所传入的回调来完成的.在创建一个关联属性的时候,我们可以传入一个属

Qt属性系统(Qt Property System)

Qt提供了巧妙的属性系统,它与某些编译器支持的属性系统相似.然而,作为平台和编译器无关的库,Qt不能够依赖于那些非标准的编译器特性,比如__property 或者 [property].Qt的解决方案能够被任何Qt支持的平台下的标准C++编译器支持.它依赖于元对象系统(Meta_Object Sytstem),元对象系统通过信号和槽提供了对象间通讯的机制. 怎样声明属性 QObject的子类的私有域中使用Q_PROPERTY宏来声明一个属性 Q_PROPERTY(type name (READ 

Swift学习(4懒加载、计算型属性、反射机制)

懒加载.计算型属性.反射机制 1.懒加载: 目的:1.延迟创建,需要时加载,节省内存空间 2.避免开发中处理解包的问题(重要!!!) 知识:1.所有的UIView 及子类在开发是,一旦重写了够着函数,必须要实现initwithcoder函数以保证提供两个通道,目前Xcode会有提示. 2.在swift中懒加载的简单写法 lazy var label:UILabel = UILabel() 3.懒加载本质上是一个闭包,完整写法如下: {}包装代码  ()执行代码 lazy var labe = {