在先前的文章中,我曾介绍了在UE中利用ResScannerUE进行资源的多阶段与自动化检查的实现和执行效率优化。
已经能够实现非常灵活地配置资源检查和及时提醒,具有非常强大的规则表达能力。但它们还都仅限于发现问题予以提醒的阶段,虽然有强制约束不能提交,但仍需人工地去处理。尤其是具有大量存量资源的情况下,手动处理的人力耗时非常大。
基于这种情况下,我设计了一种自动化修正资源的机制,能够在规范制定之后自动修复资源,确保被扫描出来资源的都被设置为正确的状态。
补齐了我这套资源检查方案中的最后一块拼图,并且可以应用在各个阶段的扫描过程,按需执行。本篇文章会介绍它的设计思路与实现机制,以及一些在项目中实际的应用场景。
具体要做什么?
我希望,ResScanner支持我的下列需求:
- 当资源的规则足够确定时,能够让不合规的资源自动化地修复,降低人工参与
- 尽可能地使功能通用化,大多数情况下不需要针对资源单独编写特殊逻辑
- 配置简单,无需代码
- 对于复杂的资源处理需求,支持自定义扩展
- 自动化批量处理
- 能够在编辑器中实时修正,建立强制约束
而资源扫描本身,能够作为筛选器使用。在之前的文章里已经充分介绍了ResScanner对复杂规则的表达能力,所以,当规则确定时,就能够清楚地知道一个资源是否需要自动处理。
以一个根据命名规则检查贴图SRGB的规则为例:
规则的语言描述:如果命名不为
*_DA
或*_D
的贴图其sRGB属性为True
,可自动将其修改为False
。
本文中的后续内容会以该规则作为例子,介绍资源修正的设计思路与实现逻辑。
为什么不用Property Maxtrix?
UE中其实有一套批量修改资源属性的方法。需要在ContentBrowser里多选资源,右键找到Asset Actions
-Bulk Edit via Property Matirx
:
即可批量编辑所选中资源的属性:
它对于属性的修改也不够直观,以AnimSequence类型资源的BoneCompression配置为例,在Bulk Edit via Property Matrix
里就是以字符串的形式展示,而不是资源,不够直观。
它的设计初衷应该是为了方便在引擎内的当前目录下的资源进行批量修改,无法选择多个目录下的资源,而且也只能在ContentBrowser里手动选去资源,无法按照一定的规则进行精确控制和自动化。
如以下规则:检查命名以_WRD
结尾的贴图,LOD Group
是否设置为World
,若不为World
则自动处理。就无法通过Property Matrix实现,它只能编辑,不能按照条件编辑。
实现原理
筛选流程和处理逻辑如下:
资源筛选
从资源扫描的检查流程中筛选出匹配的资源,然后传递给后处理器。
以前面的规则为例,首先需要筛选出当前资源是否为Texture,以及它的名字是否符合下列逻辑表达式:
1 | bIsTexture2D && !(endwith(_DA) || endwith(_D)) |
使用ResScanner的规则实现该筛选逻辑:
然后就能够正确地检查到资源是否匹配:
资源后处理
当检查到资源匹配后,就能够进入自动修正的逻辑。在我的实现中,每条规则的后处理配置是一个能够指定多个Class的数组:
需要指定继承于UScannerPostProcesser
的子类,它有一个Processer
的接口,可以用来获取规则信息、以及触发规则的资源。可以获取资源进行修改,而且还可以自己写自定义的逻辑,用于处理复杂的需求。
1 | bool Processor(FScannerPostProcesserContext& Context); |
以最简单的方法,针对Texture2D单独写一个修改贴图SRGB属性的后处理规则:
1 | bool UPP_TextureSRGB::Processor_Implementation(FScannerPostProcesserContext& Context) |
然后将其配置到规则的后处理数组中执行即可:
测试一下运行效果:
可以看到在保存时能够检查到资源匹配了规则,并且自动把SRGB属性改成False了。
通用的属性替换实现
基于前面的介绍,现在我们可以筛选出需要处理的资源了。但仍需要针对每种资源单独处理,但我厌恶一切需要特殊处理的流程。
所以,能否实现一种方法,把“修改资源的某些属性”这个需求抽象起来,作为一个通用的方式呢?
当然有!在不考虑某些极端实现的情况下(如TypeCustomization
),编辑器中能够编辑的属性都是标记了反射的,能够编辑的属性就属于所有反射属性的子集。
所以,我们只要建立起一种基于通用类型的,反射属性替换机制就能够实现。
获取类型的反射属性
在文章UE反射实现分析:基础概念曾介绍过,可以通过 TFieldIterator
来遍历 UClass 中的反射属性:
1 | for(TFieldIterator<FProperty> PropertyIter(GetClass());PropertyIter;++PropertyIter) |
只需要目标类型的UClass即可。这在我们的需求中很简单,因为在配置扫描规则时,就会指定要检查的资源类型,可以直接获取UClass。
然后可以写一个TypeCustomization的实现,自动从该UClass中获取所有反射属性,并创建出一个ComboBox
的控件用于选择属性。
设计流程如下:
对于每个属性而言,存储方式是一个Name-Value的结构:
1 | struct FSCNPostPropertyFixer |
用于存储当前UClass中属性的名字,以及要设置该属性的值。
之所以Name-Value都使用FString,因为它是一种通用的描述属性信息的方法,把值信息都转换为字符串,再从字符串转换为实际的值。
UE的FProperty
中具有ExportTextItem
的函数,可以把反射属性的值导出为字符串形式:
1 | Property->ExportTextItem(Result,Property->ContainerPtrToValuePtr<void*>(Obj),TEXT(""),NULL,0); |
如:
1 | (Name="SRGB",Value="True") |
这样就实现了通用类型值存储的问题。
而且基于TypeCustomization,也可以实现在编辑器内创建出实际类型的控件进行编辑,并能够从字符串中导入。
关于它的实现方式在文章虚幻引擎中的属性面板定制化中有详细介绍,本篇文章不再赘述。
导入覆写属性
基于前一节的内容,我们可以实现指定某个UClass类型中的属性,以及该属性的值。
那么如何把Name-Value
形式的属性描述,替换到实际的资源中呢?如Texture2D
的(Name="SRGB",Value="True")
。
同样地,UE中的FProperty
也有一个从字符串中导入值的函数,与ExportTextItem
相对应:
1 | Property->ImportText(*Text,Property->ContainerPtrToValuePtr<uint8>(Object),0,Object); |
在编辑器Details里对属性进行Copy
/Paste
,实际上也是把属性值导出为字符串,并且Paste
时也会走到该函数,实现属性值导入。
只需要在导入时,获取要修改资源的属性值地址,就能完成值的修改。
最终实现
前面实现原理已经写的差不多了,可以看一看实际的效果了。
对于需要执行资源检查后处理的规则,可以启用资源扫描后处理
。如果是要修改资源的属性值,可以在后处理数组中配置一个PP_GeneralPropertyFixer
:
然后可以创建规则参数,会有一个Properties
,继续添加一个Properties
的元素,会自动列出当前规则所指定类型的属性:
选择对应的属性,在下方就会自动创建出来对应属性值的控件,能够进行方便地修改:
就像真的在编辑该属性一样。
同样地,以前面的规则为例,修改贴图的SRGB值。在规则配置中就如下:
执行效果与前面自己用C++代码写的一样,而它是一种通用的方法。
而且,在触发提示中,还会展示出修改前后的值,提醒修改人该属性的变化:
这样,对于任何类型资源属性的检查和自动修正,就能够以纯配置的方式实现,而无需写任何代码。
案例
约束AnimSeq压缩配置
以动画序列的压缩配置为例,通常工程中会集成ACL等库来用于Bone或Curve的压缩,能够提升压缩率降低包体大小。
虽然引擎中有修改默认动画压缩配置的功能,可以在DefaultEngine.ini
中添加下列配置,并修改为自己想要使用的资源路径。如修改为ACL:
1 | [Animation.DefaultObjectSettings] |
但是,这个配置对于已经存在且修改过的资源,不会使默认动画压缩配置生效,如:
所以也需要修正存量资源,并建立编辑时的强制约束。
命名约束sRGB值
这也是本篇文章贯穿始终的规则了,基于命名的规则,约束贴图sRGB的值,这里做个总结展示。
命名约束TextureGroup类型
通过Texture资源的命名,约束其TextureGroup的类型:
执行效果:
自动化
因为整个资源修正的方案,都是基于ResScanner的资源扫描,所以直接能够复用ResScanner在Cmdlet中的支持。
这一点与UE 中多阶段的自动化资源检查方案中的描述相同,区别就是需要在执行扫描配置时打开允许后处理即可。
结语
本篇文章介绍了基于ResScanner检查规则自动修复资源的设计思路与实现方案,并介绍了一种通用地方式配置、修改资源内属性的实现原理。
对于多数情况下而言,对资源属性的自动化修改就能解决很多需求了,复杂的资源处理也可以自己写扩展逻辑。这样能最大限度地降低人工参与的耗时,但无法完全避免。因为也有类似于引用关系异常等需要人为参与判断,才能正确处理的情况,因为规则可能没法细化到那么详细的地步,但从工具的实现架构上也能够做到。
只要规则足够明确,就能基于该方案实现自动化修复任何资源!