原文链接:https://www.unrealengine.com/en-US/blog/unreal-property-system-reflection?sessionInvalidated=true

反射是程序的一种能力,借助于它可以在运行时查看自身。作为虚幻引擎中的基础技术,它相当有用,增强了众多的系统比如编辑器中的属性面板,对象序列化,垃圾回收,网络对象传输以及蓝图脚本和C++之间的通信等。不过C++语言本身并不提供任何形式的反射,因此虚幻引擎实现了一套自己的反射系统,通过它来收集,查询和修改C++中的类,结构,函数,成员变量和枚举的信息。在本文中我们提到反射通常是指属性系统,而不是图形学中的概念。

反射系统是可选的。如果你希望某些类型或者属性对反射系统可见,那么就必须给它们加上修饰宏,这样在编译工程时Unreal Header Tool (UHT) 才会去收集这些信息。

标示
为了标示一个头文件包含了反数据类型,我们需要在文件的头部包含一个特殊的文件。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()来指定该类具有反射特性。与UCLASS()对应的,我们还在类定义内部插入了GENERATED_UCLASS_BODY() 宏。对于想要加入反射的类和结构体中,GENERATED_UCLASS_BODY() / GENERATED_USTRUCT_BODY()是必须的。通过这两个宏,我们给类和结构体注入了实现反射所必须的额外函数和类型信息。

在代码中,第一个反射属性是ResourcesToGather。它被指定为EditAnywhere 和Category=Pawn。这意味着这个属性可以在任意属性面板里编辑,并且属于Pawn这个类别。此外好几个函数指定了BlueprintCallable和一个类别,这意味这些函数都可以在Blueprints里被调用。

如MyTeamNum声明所示,在同一个类中混杂反射成员和非反射成员是没有问题的,只是要注意的是非反射成员对于所有基于反射的系统来说都是不可见的(比如缓存一个非反射的UObject指针通常来说是危险的,因为垃圾回收器并不知道你引用了它)。

你可以在ObjectBase.h里找到每一个说明符关键字(比如EditAnywhere和BlueprintCallable)的一段简短注释和用法说明。如果你不知道某个关键字是起做什么用的,按快捷键Alt+G会跳转到ObjectBase.h中对应的定义上去了(这些不是真的C++关键字,但是智能提示和VAX看起来不关心也分不清它们间的区别)。

更多的信息可以参见官网上的Gameplay Programming Reference。

局限
UHT并不是一个真正的C++解析器。它可以识别语言的常见子集,并且在解析时尽可能多的跳过任何它认为不相关的代码;同时仅关注反射的类,函数和属性。尽管这样,某些情况下它还是会出错的,所以当往一个已有的头文件里加上反射类型时,你可能要重写一些代码,或者要把已有的代码包在#if CPP / #endif里。你应该尽量避免把反射宏修饰过的属性或者函数包在 #if/#ifdef (WITH_EDITOR 和WITH_EDITORONLY_DATA除外)里,这是因为在某些构建配置里这些宏定义有可能不是true的,那么在生成的代码里去引用这些属性或者函数时就会报编译错误了。

对于虚幻引擎的反射来说,C++中绝大多数的数据类型都是支持的,但是并不是所有(特别是只有少数模版类型是支持的,比如TArray和TSubclassOf,并且它们的模版参数不能是嵌套类型)。如果你使用反射宏来修饰无法在运行时表示的数据类型,UHT会给你报一个描述性质的错误信息。

使用反射信息
虽然大部分的游戏代码在运行时使用反射系统给予的便利的同时,可以忽略反射系统,但是当你编写工具或者玩法系统时会发现反射还是很有用的。

反射系统的类型层级如下所示:

UFieldUStructUClass (C++ class)UScriptStruct (C++ struct)UFunction (C++ function)UEnum (C++ enumeration)UProperty (C++ member variable or function parameter)

(更多不同类型的子类)

UStruct是聚合类结构(任何包含了其他成员的类型,比如C++类,结构,或者函数)的基础类型,不要把它和C++的结构混淆起来(与它对应的是UScriptStruct)。UClass可以包含函数或者属性作为它的成员,但UFunction和UScriptStruct只能局限于属性。

通过UTypeName::StaticClass() 或者 FTypeName::StaticStruct(),你可以获取某个反射C++类型的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 + HasAnyClassFlags, etc…)和一个继承自UField的通用元数据存储系统。反射宏中的说明符关键字可以作为标志位存储也可以作为元数据存储,取决于它们是被用于游戏运行时,还是只是在编辑器里。这样就可以实现在游戏运行时去除仅在编辑器用的元数据来达到节省内存的目的,而作为标志位存储的则一直可用。

你可以通过应用反射数据来实现不同的功能(比如枚举属性,按数据驱动的方式获取/设置属性,调用反射方法,甚至创建新实例);与其深入了解每个反射特性,不如浏览一下UnrealType.h 和 Class.h,然后跟踪调试一个和你要做的功能相近的样例来得更容易一些。

反射的实现原理
如果你仅仅只是想使用反射系统而已,那么可以毫不犹豫的跳过这一部分;但是知道它是怎么工作的可以让你在使用时更好的做出决定并了解它的局限性。

Unreal Build Tool (UBT) 和 Unreal Header Tool (UHT)在实现运行时的反射功能中扮演着核心的角色。UBT的工作就是扫描头文件,如果一个头文件包含至少一个反射类型则记录该头文件所在的模块。如果这些头文件在编译之后发生改变,UHT就会被唤起收集并更新对应的反射数据。UHT解析头文件,创建反射数据集合,然后生成包含反射数据(包含在每个模块都有的.generated.inl里)以及各类辅助类和函数(包含在每个头文件对应的.generated.h里)的C++代码。

之所以通过生成的C++代码来保存反射信息,一个主要的好处是这样可以确保这些信息和最终的二进制文件保持同步。把反射信息和引擎代码一起编译,并在启动时通过C++表达式来计算成员的偏移等信息,而不是逆向工程某个特定的平台/编译器/优化选项的组合,这样你永远都不会加载到错误的反射信息。UHT作为一个独立的程序,它不会修改任何生成的头文件,这样就避免了在UE3脚本编译中经常被抱怨的先有蛋还是先有鸡这样的问题。

生成的方法包括像StaticClass() / StaticStruct(),方便你获得某个类型反射数据的类型,生成的代码段则方便你在Blueprints或者网络传输中调用。这些东西必须作为类或者结构体的一部分来声明,这就是为什么GENERATED_UCLASS_BODY() 或GENERATED_USTRUCT_BODY()宏要包含在反射类型里,以及头文件中需加入包含定义这些宏的代码#include “TypeName.generated.h” 的原因。

虚幻引擎中的反射(译)相关推荐

  1. 元宇宙开发:你在虚幻引擎中的第一个虚拟现实游戏

    了解如何开发零编程背景的Oculus Quest游戏 你会学到什么 为Oculus Quest构建应用程序 设计和开发虚拟现实游戏 在虚幻引擎中工作 使用材料和纹理 优化内容,实现移动和虚拟现实游戏的 ...

  2. 教你学会虚幻引擎中的光线追踪功能使用,此教程必看

    在本教程中将展示虚幻引擎中的光线追踪功能.为了比较光线追踪引入的变化,我从头开始构建了一个独特的环境,其中包括各种材料和模块化资产.光线追踪是一个很棒的工具,可以让你更好地控制场景的视觉方面. 启用光 ...

  3. 在虚幻引擎中使用Python批处理3:批量修改属性

    Unreal Python API 文档: - https://docs.unrealengine.com/4.26/en-US/PythonAPI/ 在上一篇文章在虚幻引擎中使用Python批处理2 ...

  4. 虚幻4 python_技术前瞻:虚幻引擎中的 Python

    原标题:技术前瞻:虚幻引擎中的 Python 在Autodesk University上,我们简单展示了最新的研究,它将解决CAD数据导入虚幻引擎这一复杂问题.这是Datasmith研发过程中的重大突 ...

  5. 《从C语言过渡到C++和虚幻引擎中的C++编程》教程①

    本系列文字教程的受众对象是刚刚经历完大一上的C语言期末考试的.仅具有一定C语言基础的新生. 如果您是上述对象,在看完本系列教程之后,您将收获C++编程入门和虚幻引擎中的C++编程入门知识以及一些游戏开 ...

  6. 终极Git课程——在虚幻引擎中的应用 The Ultimate Git Course – with Applications in Unreal Engine

    了解Git和GitHub.虚幻引擎C++项目的版本控制等等! 你会学到: Git是如何工作的 如何从命令行使用Git 使用GitHub.GitHub桌面应用程序.Visual Studio中的Git工 ...

  7. 如何改善虚幻引擎中的游戏线程CPU性能表现

    您游戏中的帧频率是不是太低? 您了解为什么会发生这种现象吗? 这是不是由于您同时生成了太多敌人?还是由于某个特定敌人过于消耗系统资源? 是由于您设置了过多的视觉特效,还是由于您所设计的战斗系统所造成的 ...

  8. 虚幻引擎中导出模型,并导入到Unity

    1.UE5 安装glTF 插件 在虚幻商城中添加glTF 免费插件 打开虚幻引擎插件面板,启用glTF 插件,并重启引擎 2.导出模型 选择要导出的模型资源,右键选择导出,选择格式为 gltf. 3. ...

  9. 在虚幻引擎中使用蓝图实现简单的对话

    在虚幻引擎的小组项目中,我们实现了简单的对话功能. 对话功能要求: 在靠近可对话的物体时,出现提示显示按下E对话 按下E可以进行对话 对话内容可以随着任务流程推进而更改 离开可对话物体时,对话中断:再 ...

最新文章

  1. 西农韦革宏组揭示甘草根系微生物群落分布及其与根内次级代谢产物之间的联系...
  2. 给char*一个名份
  3. 03-JDBC连接MySQL数据库【插入数据】
  4. java排队买票_【排队买票】 (Java代码)
  5. 数码管显示1到8c语言,单片机控制八只数码管滚动显示1~8 附PROTEUS软件仿真图
  6. 给大家一个网站程序,fastmai网站源码
  7. OpenCV-数字图像处理之直方图均衡化
  8. Hibernate 添加数据 一 (一对多)
  9. RabbitMq--AMQP高级消息队列协议--简单了解
  10. 计算机网络知识点回顾
  11. c15语言中不支持十六进制的数据,从十六进制字符串中提取数据
  12. 如何在百度地图、腾讯地图标注公司地址信息?
  13. android按钮显示注册成功,Android中给按钮注册事件的四种方法
  14. 基于C++实现一个支持简单交互绘图小程序
  15. 【SpringBoot】SpringBoot2.x 配置 笔记
  16. Win10离线安装.NET Framework 3.5的方法技巧(附离线安装包下载)
  17. 多线程并发在电商系统下的追本溯源-电商实战
  18. 就是策划和服务器维护,DNF官方策划针对跨区服务器的回应
  19. 怎样解决计算机管理compmgmt.msc打不开
  20. 测试开发工作者日记【终焉】:再见~ 小猪

热门文章

  1. 可动平行平板电容建模策略
  2. 一半径为R的球沉入水中,球面顶部正好与水面相切,球的密度为1,求将球从水中取出所做的功
  3. 分享99个ASP电子商务源码,总有一款适合您
  4. VR系列——Oculus Audio sdk文档:三、Oculus对于Unity天然的声场定位技术(3)——空间定位的应用
  5. LCD1602的解读(详细步骤分析)
  6. hbase数据库 数据存入_七个星期中的七个数据库– Hbase第二天
  7. W13电力线载波通信技术
  8. MySQL-学生表以及课程、老师、成绩表的创建
  9. Acwing1123. 铲雪车(欧拉回路)
  10. Cuckoo Filter(布谷过滤器)