您当前的位置:首页 >> 综合 >> 
[UE5]使用C++借助反射批量获取/修改蓝图参数

时间:2023-08-27 19:18:45    来源:哔哩哔哩

说说前情

上周美术大哥提了一个需求,希望能修改Ultra Dynamic Sky里纯蓝图类Dynamic Sky的一些参数,以实现在基础的时间变化上增添“亿”些细节,他已经把参数名称(41组*3种预设)总结到Excel了。对这么多参数,我本想着性能差一点直接向后期框一样直接暴力拷贝参数结构体就好,但这个插件的作者着实是比较猛,参数没有外层包裹、需要一个一个调整,同时又没有C++基类,是纯蓝图做的。


(资料图片)

如果用蓝图做还容易错,正好最近在学UEC++,不如查查看有没有什么用C++批量改参数的方法,在论坛一顿搜索之后发现了这篇(/t/reading-struct-attributes-in-c/471834/2),又看到了大钊老师在知乎上InsideUnreal中对反射的讲解(/p/61042237),那不如用C++整个活。

美术大哥提的需求里主要需要获取浮点数(中默认双精度,其实对应的是double)、布尔值(bool)、颜色值(FLinearColor)三种类型。考虑到后续升级和复用,我创建了一个C++的ActorComponent挂载到Dynamic Sky Actor上。使用这个Component作为作为数据控制器。接下来就是如何把数据从三种已经调好的流送关卡中读取出来、写到DataTable,然后在切换预设的时候把参数再写到主场景的Dynamic Sky Actor中。简单来说就是:

读取蓝图数据——写入DataTable——读取DataTable——写入蓝图数据

利用反射读取蓝图参数

通过一些资料的查阅,读取方面分为以下三步:

首先需要通过给定的名称找到蓝图中反射参数FProperty,主要函数是PropertyAccessUtil::FindPropertyByName()或FindFProperty<FProperty>()

转换成需要的类型, CastField<FTargetTypeProperty>(FProperty)

然后通过FProperty的内置函数把参数读出来FTargetTypeProperty->GetPropertyValue_InContainer(Target)

这边需要注意FProperty是以后版本中对外暴露的项取代了之前的UProperty,早些的文章中可能会使用UProperty,本文以为演示平台,使用时注意版本对应。

获得double值(蓝图中的float)参考代码:

特别注意下面这行中不再传入Target->GetClass()(或Target->StaticClass())而是直接传入需要获取的对象指针,在有些文章里有编码错误,导致无法获取实例中的值。

为了便于开发Debug,下面写了一个Debug编辑器函数进行尝试,获取名称为MoveAmplitude的蓝图变量,在可以在编辑器中修改为其他的float值名称。

能够成功获取该变量

那么根据上边的方式稍作修改就可以获取其他类型变量的值,可以根据每一个需要修改的变量类型创建对应的函数。

但应当注意FVector、FLinearColor类型的变量是以结构体形式储存的,没有直接的F【XXX】Property对应,需要获取为FStructProperty然后使用FProperty::ContainerPtrToValuePtr直接返回容器内值的指针,参考代码如下:

运行结果如下

简化获取步骤

这个步骤中对变量进行了严格的限定,虽然达成了目的但是适用度太窄,如果后续修改其他类型的变量又要创建新的函数,而且其中的重复步骤太多,可以考虑简化一下然后变成函数模板缩减一下工作量。参考了这篇文章(/t/c-introspection-and-containerptrtovalueptr/1208130/3)于是就有了下面的内容:

由于返回值类型不同,转换到输出中打印。

修改Deug函数尝试调用一下:

点击运行后各变量输出正常。

写入DataTable

完成数据读取之后下一步就是要把读出来的数据写入蓝图,根据[中文直播] 第21期 | UE4数据驱动开发 | Epic 大钊中大钊老师讲的内容,使用DataTable或DataAsset均可,两者在编辑上也类似,差异在于DataTable前置条件是结构体,而DataAsset前置条件是类。在实际应用的配置方面,DataTable只需要指定一个变量,DataAsset需要创建一个Map,作为纯数据存储两者差异也不大。最终考虑到csv导入导出还是选择了DataTable,但DataTable不支持蓝图写入,所以需要把DataTable写入逻辑写在C++。

首先创建结构体,由于之前美术同时已经把变量名字抄到了Excel中,那么就需要批量创建对应的结构体变量,这方面使用Excel公式拼接配合Word查找替换就可以很轻松的完成。在示例(即上边的截图,文末附链接)中我创建了如下变量:

bool bShowWidget;FVector WidgetOffset;FText WidgetText;FLinearColor TextColor;

/*FTexture2D WidgetTexture*/;double MoveAmplitude;int32 Index;

对应的创建结构体,继承自FTableRowBase,在替换时特别注意Category的引号容易被替换为全角引号,可以指替换前不加换行,到IDE中使用列编辑模式统一修改。

创建DataTable后在C++中添加DataTable对象指针,然后使用函数设置DataTable值,(参考文章:/shiroe/p/)

在尝试过程中发现,用户定义结构体UScriptStruct在继承关系上比较复杂,无法直接将上面的UObject替换为UScriptStruct,使用上边创建的结构体类型进行处理:

这边对应的使用Target->StaticStruct()作为FProperty作为查找参数,直接使用找到的指针改变参数值。

同样创建EditorCallable函数在编辑器中调用:

指定后点击编辑器中生成的Button将蓝图实例的变量以指定的RowName写入DataTable写入结果:

如果当前DataTable处于打开状态,需要关闭并重新打开查看写入结果。

写入完成后就可以给不同子关卡中的目标对象分别挂载Component,指定不同的RowName将内容写到DataTable进行数据保存,需求完成了一半,剩下的就是把DataTable里的值在些回到蓝图对象中。

从DataTable读取到蓝图

从DataTable中读取数据和读取蓝图中变量的逻辑基本一致,结合上面对结构体的修改内容如下:

设置蓝图变量:

创建编辑器可调用函数并进行测试

至此实现了将对象A的参数写入DataTable然后再将DataTable中保存的变量写入对象B。

其他易用性优化

对于需求中需要覆盖double(float)、bool、LinearColor类型的多个变量,可以将同种变量进行归类,使用特殊分割符号(比如【&】),然后使用FString::的ParseIntoArray转换成字符串数组,然后For循环依次设置同类型的值。FName构造可以直接接收FString类型,直接输入即可,但是注意最好不要带空格,否则在解析的时候可能会报错(因此我把插件里的变量都去了空格)。

三种预设中可以创建枚举量,然后覆写PostEditChangeProperty函数(参考文章/p/63195899),当枚举量修改时调用从DataTable中读取变量的逻辑,实现直观预览和效果确认。但是DynamiSky的需要调用构造函数才能预览变化,在尝试中反射没能成功调用蓝图的构造脚本,因此还是需要一步手动操作。这个函数在使用中还需要注意在声明和实现中该函数均需要使用#if WITH_EDITOR #endif包裹,否则打包时会报错。

增删变量需要修改内容,在本文中变量查找使用名称驱动,因此在少用一个变量时只需要在输入变量群字符串中去除该变量即可,读取时会跳过该变量;对增加的内容一方面需要在输入变量群字符串中加入该变量,另一方面需要在结构体中对应添加、更新DataTable内容。

后续潜在的改进方向

Texture2D读取与写入,在上边的内容中Texture2D数据的读取和写入都被注释掉了,目前还没有想到读取和写入的方法,待后续尝试中进一步完善。

DataTable写入过程通用性增强,上文在读写DataTable时传入的时特定的结构体,两个文件形成了强绑定关系,在对接其他需要修改的对象的时候需要重复工作。目前已经有文章描述自动创建Struct的方法(参考文章/zhangxiaofan666/article/details/112879891),后续会进一步扩展。

读取写入方法函数模板统一,这个项目是我第一次使用反射,UE内各种继承关系还没有完全搞懂,很多地方有重复的代码,比如在BP和DataTable读写两者差别很少但没能统一,不同类型的变量需要分别调用。

性能优化,在调用FindPropertyByName的过程中进行了很多次域的查找,根据网上大佬的做法似乎可以对这方面调用更加内部的代码,实现仅查找一次完成所有变量的读写操作,提升性能。

希望对初学者朋友们有一点点帮助,也欢迎大家提建议,希望和大家共同进步。

示例工程:/jiadevr/UECppLearning

标签: