在大型项目中,资源的规模非常庞大,涉及的制作团队也非常广泛,场景、角色、UI、动画、特效、蓝图、数据表格等等,随之而来就是资源量和资源规范的管理难以把控。
对于制定的资源规格,美术制作人员难以覆盖100%的情况,会存在不经意间漏掉,大多数情况下都是包内发现问题后再处理,而且对于存量的资源,需要耗费大量的人力处理,难以审查和修复。
基于这种痛点,我之前开发了一个资源扫描工具,可以方便地编辑规则对项目内的资源进行扫描。
- UE 中利用反射为资产建立属性缓存
- UE 中资源自修正的设计与实现方案
- UE 中多阶段的自动化资源检查方案
- 基于ResScannerUE的资源检查自动化实践
- UE资源合规检查工具ResScannerUE
近期,我对插件做了全方面地升级,增强了编辑时和自动化检查的能力,本篇文章会介绍如何利用ResScannerUE实现编辑时、提交时、CI定时或Hook任务、Cook时等各个阶段的资源扫描,把错误地资源尽可能地在制作时暴露出来并提示解决,避免包内资源异常。
在插件的具体实现上,也针对扫描速度做了很多优化,尽可能把检查变成一个无感知的行为,文章内会具体介绍。
ResScanner简介
前面已经有两篇文章介绍了它的功能,这里不再详细赘述,简单地介绍一下功能。
ResScannerUE是一个UE的资源扫描工具,可以极为方便地编辑规则,支持纯配置和蓝图脚本规则,99%的情况下不需要编写任何C++代码。对于路径、命名的检查不需要实际加载资源。
支持路径规则、命名规则、属性规则、自定义规则四种类型,可以使用蓝图或者C++扩展规则。并且单个规则支持多个组合条件,支持逻辑操作(与或),对于复杂规则的支持非常好。
对于属性检查规则,基于UE的反射和Detail Customization机制,可以根据所选择的资源类型,列出其所有的属性,根据所选择的属性,构造出该属性类型的控件,对于属性的检测,鼠标勾选即可完成:
深度集成Git,可以基于Git的版本管理扫描,可以支持待提交文件、提交记录间的差异扫描。
具有完善的Commandlet支持,支持配置化、命令行参数的形式集成至ci/cd系统。
支持编辑时的规则扫描,把错误扼杀在摇篮中。
编辑时
大多数的资源问题,一般是以下几种情况造成的:
- 正式资产目录中创建了临时资源
- 选项忘记勾选、设置
- 资源规格不对
- 提交了不应该修改的资源
这种情况随着团队规模的扩大,几乎是无法避免的事情。而且对于第5点,资源是二进制形式,无法进行diff,就会存在覆盖的情况,如果出现问题,被覆盖的资源就要回滚或者重复制作,非常地浪费人力。
基于这种痛点,我对ResScannerUE增加了编辑时实时提醒的机制,在保存资源的同时,进行扫描,如果具有不合规的地方,及时提醒:
并且可以检查当前编辑的人对资源是否有修改权限:
插件并不会阻止保存,而是给予提示修改。
在实现上,是监听了UPackage::PackageSavedEvent
,当保存资源时会执行该Delegate,从而获取到哪些资源被修改了:
1 | UPackage::PackageSavedEvent.AddRaw(this,&FResScannerEditorModule::PackageSaved); |
在回调中进行检查:
1 | void FResScannerEditorModule::PackageSaved(const FString& PacStr,UObject* PackageSaved) |
因为每次只检查一个资源,而且保存的资源已经被引擎加载,所以扫描速度非常快,几乎没有感知。
启用方式
打开Project Settings
-Game
-Res Scanner Settings
面板,开启bEnableEditorCheck
,新建一个FScannerMatchRule
的DataTable
,并在规则数据表中指定。
这个DataTable中的每一项就是一个单独的规则。
可以根据自己项目中的情况编辑扫描的规则,比如只扫描IMPORTANT
类型的规则、并且支持黑白名单的方式指定规则。
提交时
在资源的提交阶段的检查,主要利用了版本控制软件提供的HOOK机制,如Git提供的Pre-Commit hook
,Ugit提供了钩子等等。本质上就是可以在提交时执行一个脚本,拉起来我们的资源扫描功能,并在触发规则时,禁止提交。
这部分实现,我在文章基于 ResScannerUE 的资源检查自动化实践中有详细介绍。
CI/CD
可以把资源扫描集成到CI/CD系统中执行定时或Hook触发的扫描:
利用插件的Commandlet机制,可以通过导出的配置文件、命令行参数替换等方式实现。
1 | UE4Editor.exe Project.uproject -run="ResScanner" -config="Full_Scan_Config.json" -gitChecker.bGitCheck=true -gitchecker.beginCommitHash=HEAD~ -gitchecker.endCommitHash=HEAD |
扫描完毕之后,可以通过企业微信机器人等方式,发送群通知,并@相关提交人处理。
我提供了一个完整的实现脚本,详情可见:企业微信提醒
打包时
在引擎中的资源,并不是都会完全进包的,如果只想要分析当前进包的资源中的规则检查,插件也提供了支持。
只要启用,在Cook阶段会自动执行,并在Cook完毕之后生成检测报告,默认存储在Saved/ResScanner/Cooking.txt
中。
整个实现是非侵入式的,只需要集成插件,无需变动引擎。
启用方式
在Project Settings
-Game
-ResScanner Settings
中启用bEnableCookingCheck
,并配置Cook时扫描的规则即可:
扫描优化
资源扫描,本质上就是拉起来UE的UE4Editor-cmd进程,去执行Commandlet,耗时方面还是有些敏感,因为需要完整地拉起引擎。
所以要针对Commandlet的启动做些优化,避免引擎启动太久导致的流程卡顿。
首先要分析启动引擎时,各个模块的启动耗时,也就是每个模块的StartupModule
的耗时。引擎中默认没有添加能够分析全部Modules的Profiling tag,可以在FModuleDescriptor::LoadModulesForPhase
以及FModuleManager::LoadModule
中添加Profiling Tag进行检查:
1 | void FModuleDescriptor::LoadModulesForPhase(ELoadingPhase::Type LoadingPhase, const TArray<FModuleDescriptor>& Modules, TMap<FName, EModuleLoadResult>& ModuleLoadErrors) |
FModuleManager::LoadModule
:
1 | IModuleInterface* FModuleManager::LoadModule( const FName InModuleName ) |
AkAudio
如果项目继承了WWise音频功能,它的AkAudio模块启动会非常耗时(取决于项目的资源量),经过分析发现,它会在StartupModule
中去扫描整个工程Content下的文件,并扫描AssetRegistry数据,这部分非常耗时,只扫描文件就会有几十秒的耗时:
1 |
|
这两部操作异常耗时,但对于Commandlet来说这两步并不需要,可以检测::IsRunningCommandlet
屏蔽。
需注意:在EBP模式下的Wwise中,必须要检查
InitBank
在AssetRegistry是否存在,否则会再创建一个出来,会弹窗提示。
解决这个问题的办法就是在忽略整体扫描的同时,只为InitBank
资源生成Registry数据。
1 | FString PackageFilename; |
LiveCoding
如果项目开启了LiveCoding,它的启动耗时比较可观:
但Commandlet完全不需要LiveCoding相关的支持,如果直接关掉,又会对日常的开发造成影响。
所以,我实现了一个方法,可以在Commandlet运行时,动态地关闭LiveCoding,这样就算EditorSetting中LiveCoding始终开启,也可以在执行Commandlet是关闭它。
方法就是在优先级较高的Module(Default
)的StartupModule
中把GEditorPreProjectIni
中的配置改了:
1 | if(IsRunningCommandlet()) |
这样等LiveCoding模块启动时,就会认为是关闭的,不会再执行那些耗时操作了。
Asset Registry
在引擎启动时,会通过AssetManager
扫描PrimaryAsset
,如果没有Cache过,从资源中重建AssetRegistry数据的过程也非常耗时。
对于资源扫描而言,其实大多数情况下(尤其是本地执行)并不需要完整的AssetRegistry数据,可以关闭SearchAllAssets
和ScanPrimaryAssetTypesFromConfig
的扫描,在资源检查时,按需地生成对应资源的AssetRegistry数据即可。
无需修改引擎,只需要继承一个UAssetManager
的类,重写ScanPrimaryAssetTypesFromConfig
接口:
1 | // .h |
在执行Commandlet时就可通过-NoScanPrimaryAsset
参数关闭AssetRegistry的扫描了。并且,尽量不要在Commandlet代码中执行SearchAllAssets
操作,也比较耗时。
但在运行中,需要访问AssetRegistry数据时,还是需要针对性地去扫描所需要的Package的AssetRegistry数据,插件中已经支持。
效果
经过优化之后,在引擎、Client、Content都放到SSD的情况下,引擎整体的启动耗时可以降低到20s左右,资源扫描过程可以降低到20s以内,提交时感知不明显。
在HDD中,引擎启动和扫描耗时都要慢一倍以上,就是IO瓶颈了,尽量把所有资源都放到固态中。
更新日志
2023.03.30 Update
- 支持渐进式扫描
- 优化Cook时的扫描实现,每个资源在Cook时的单独扫描,降低加载耗时
- 优化引用关系扫描效率,增加依赖关系、派生类缓存
- 优化扫描过程,避免执行不必要的规则
- 优化扫描结果的序列化阶段,支持非CookOnTheFly的模式
- 优化Cook时Warnning/Error级别Log的追踪方式,并去重
结语
本篇文章介绍了利用ResScannerUE实现,在资源各个提交阶段进行检查的实现和优化方式,能够覆盖实际开发中的绝大部分情况,尽可能地让错误提前暴露,提前解决。