资源管理:重塑UE的包拆分方案

Resource Management: Reshaping UnrealEngine Subcontracting Solution

之前我写了一篇文章,介绍UE默认的拆包方式(UE 热更新:拆分基础包),但UE默认的拆包方式在大规模资源项目中不够灵活,无法满足对超大规模资源项目的精细化管理需求。

为了解决UE默认打包和资源拆分的痛点,我基于HotPatcherCore开发了一个扩展,能够能够解决默认拆包流程的缺点,足够地灵活与强大。博客中有篇文章,介绍它的整体实现机制: 一种灵活与非侵入式的基础包拆分方案

HotChunker能够非侵入式地集成到UE默认的打包流程中,无需手动做任何的处理,只要UE默认打包,就会自动拉起HotChunker的拆包流程,实现资源的Cook、打包,以及自动Copy到StagedBuilds目录中。

本篇文章会着重介绍,使用HotChunker在进行资源拆分的配置方式和自动化构建、最大限度地降低资源冗余、以及进行并行打包的实现。

UE默认打包的问题

UE默认使用的CookerOnTheFly-UnrealPak的模式本身存在一些问题:

  1. 不能够精确地打包和灵活控制自定义拆包的行为
  2. 没有全平台通用的pak进包过滤机制(只有安卓的ObbFilter
  3. 强依赖Cook流程,无法单独打包某一个Chunk
  4. 无法方便地进行程序化的拆包
  5. 无法配置Chunk包含Not-uasset文件
  6. 所有Not-uasset文件均在0号Chunk
  7. 无法基于chunk拆分Metadatas

HotChunker的实现能够比较好的解决上面的问题,每个Chunk都是自包含的:

最终效果,UE打包的资源配置,只有那些最基础的资源由UE自己管理打包,其余的都可以通过HotChunker的配置来拆分。

文章的后续部分会介绍如何配置做到这一点。

Chunk配置

Chunk的配置,是进行包拆分的最小单元,在UE中,每个Chunk会打包为一个独立的pak文件。

对于HotChunker的实现,单个Chunk的配置可以包含以下这些属性:

Chunk的资源的配置提供了两种模式:Rule/Getter,用于指定打包的资源路径从代码中通过程序化的方式获取

每个Chunk都可以添加的外部文件,以及控制Shader的序列化方式、AssetRegistry的序列化等等。并且Chunk的metadta是自包含的,最显著的就是降低安装包内ShaderLirary的大小。

默认情况下,UE打包项目的Shader都被序列化到了同一个文件内,并且这个文件是被打包到pakchunk0之内的,也就是整个项目完整的Shader都在安装包之内。资源规模上来之后ShaderLibrary大小非常恐怖,可能会达到数百M。
让每个Chunk的Shader自包含,能够比较好地解决这个问题。并且配合上我之前的一种高效的 ZSTD Shader 字典训练方案,组合起来能够最大限度地降低ShaderLibrary的大小,实现Shader的按需下载。

Rule

Rule模式的配置和HotPatcher的配置类似,可以指定包含路径、资源、忽略的资源。

这种模式适合配置比较固定的资源,如打包某个目录、某个地图等。

Getter

除了直接指定资源之外,我还提供了一种Getter的配置模式,可以指定一个继承自UHPLGetter的类,从代码中动态地获取Chunk管理的资源。

之所以会有这样一种模式,是期望与策划的配置建立起直接的对应关系。基于策划的配置来控制打包。

比如,当前版本包含了哪几个地图、哪些职业的角色等等,这些配置是会随着开发进程不断变化的,而数据源头在策划,在UE默认的方式中,还需要在工程中进行修改打包配置。
但,HotChunker通过Getter这种配置方式建立起打包策划配置的对应关系,就能够策划来直接控制打包哪些资源。

Chunk管理

HPL资源

与PrimaryLabel类似,我给Chunk的配置增加了一个等价物,我将其称之为HPL(HotPatcherPrimaryLabel)。

可以创建一个HPL资源用于编辑要打包的资源,每个HPL是一个独立的Chunk。

在打包中,需要在Project Settings-Plugins-HotChunker中添加扫描路径:

在HotChunker的打包中,会自动扫描所有配置的目录下的HPL资源,进行打包。

DataTable

如果想要创建多个Chunk,可以创建多个HPL的资源,但是为了方便管理,我还支持了DataTable配置。
选择创建DataTable,RoleStructure选择HPLChunkSetting

打开编辑,每个元素就是一个独立的HPL的Chunk配置:

并且我为HPL的DataTable添加了一个专门的打包按钮,可以直接在表格界面中选择打包哪个Chunk:

它会拉起一个单独的进程执行,不会阻塞当前编辑器。

同样的,当创建了一个HPL的DataTable,想要将其参与打包,需要在Project Settings-Plugins-HotChunker中添加ImportRuleTables配置:

动态注册

为了实现程序化自动进行分包的需要,我为拆分配置增加了动态注册的机制,只需要绑定该Delegate:

1
FHotChunkerDelegates::OnNotyfyChunkRegister.AddStatic(&FGameEditorModule::OnNotyfyChunkRegister);

在该函数中将Chunk注册进去:

通过动态注册或者前面提到的Getter机制,可以把策划配置与打包建立起一种对应关系,可以完全按照策划的配置,来自动控制要打包哪些资源,解放程序的配置任务。

编辑器支持

我为Chunk的DT配置提供了编辑器选项,能够比较方便地选择打包某个Chunk,在开发过程中,可以让美术同学自己方便地打包自己所需要的资源,避免出包的等待。

  1. 打包单个Chunk
  2. 打包DT中所有的Chunk
  3. 导入/导出Chunk的配置
  4. 不阻塞编辑器

全局配置与流程扩展

Project Settings-Plugins-HotChunker中可以进行全局配置,和逻辑替换(ChunkerManager)。

  1. HPL扫描路径
  2. Chunk规则DT配置
  3. Pak Setting
  4. 功能开关
  5. 替换ChunkerManager
  6. 全局重载

介入Chunker的各个阶段

为了实现足够的灵活性,在实现过程中,我为Chunk的打包留了很多可以介入到各个阶段的口子。

  1. 按需对资源资源分析、处理
  2. 获取打包状态、结果
  3. 获取完整的Manifest信息
  4. 进行包体审计

在框架之内,可以非侵入式地实现项目的各种分析需求。

冗余控制

我在UE热更新:拆分基础包#Priority 打包时失效中介绍了通过UE默认的拆包形式,优先级不生效的情况,并提供了解决方案。

HotChunker在实现时,就已经规避了这些问题,Chunk的配置也支持Priority

如果Chunk的Priority是平级的,则它们之间就没有依赖关系,统计资源会冗余一些。

打进安装包内的Chunk之间是没有资源冗余的,在安装包之外的Chunk,可以优先级来进行冗余控制。

Commandlet支持

我为HotChunker添加了多种Commandlet支持,能够比较方便地使用命令行打包。

  1. 打包工程里所有配置的Chunk
  2. 打包一组Chunks
  3. 打包单个Chunk
  4. 打包某个命名的Chunk
1
2
3
4
-run=HotChunker -TargetPlatform=IOS
-run=HotMultiChunker -config=G:/HPL/HPL_ChunkerSettings.json -TargetPlatform=IOS
-run=HotSingleChunker -config=G:/HPL/ChunkerSetting.json -TargetPlatform=IOS
-run=HotNamedChunk -ChunkName=XXXX -TargetPlatform=IOS

打包控制与动态开关

Chunk的打包控制,分为三种类型:

  1. bUseHPL:是否启用HotChunker的配置打包chunk
  2. ALL:所有的chunk
  3. BASEBUILDS:安装包内的Chunk
  4. EXTERNAL:安装包之外的Chunk

在项目设置中可以控制默认打包的类型

除了固定配置之外,还可以在commandlet传递命令来覆盖引擎中的配置,能够比较方便地在CI/CD上进行控制。

目前支持了两个动态开关:

1
2
3
4
# true/false
-bUseHPL=
# ALL/BASEBUILDS/EXTERNAL
-GenerateType=

在拉起UE的CookCommandlet时传递,就会自动地控制Chunker的打包开关和生成的Chunk类型。

如果是自己在CI/CD中调用HotChunkerCommandlet,也可以传递这两个动态开关。
如,只打包非安装包内的Chunk:

1
-run=HotChunker -TargetPlatform=IOS -GenerateType=EXTERNAL

能够比较方便地集成到CI/CD系统中:

通过这种机制,可以实现打包时的精确控制。在一些测试场景中,可以实现快速出包,避免每次打包都要处理完整资源。

Chunk的加密

使用HotChunker方式管理并打包的Chunk,默认复用项目配置中里DefaultCrypto.ini的配置。

Project Settings-Plugins-HotChunker-HPLPakSettings-EnceyptSettings中配置:

bUseDefaultCryptoIni:使用项目设置中的配置。如果不复用,可以通过CryptoKeys来指定crypto.json进行加密。

通用的包过滤方案

UE 热更新:拆分基础包这篇文章里,我介绍了Android和IOS包过滤的方法。
引擎默认只为Android提供了ObbFilter,但没有为IOS提供等价物,需要自己实现。

在HotChunker的实现中,我对包过滤的机制做了补充,可以在创建每个Chunk时,直接指定它是否进安装包。

并且,可以针对某个平台特殊处理:如,默认情况下进基础包,但在WindowsNoEditor平台不进。

如果不想要对每个Chunk都单独配置,可以在项目设置中配置全局重载:

这个配置意味着,在打包WindowsNoEditor平台时,HotChunker所有管理的chunk都会归档到安装包之内。

流程优化

默认情况下,UE使用CookerOnTheFly的过程,是要把当前工程中所有参与打包的资源都执行Cook之后,才进入到Pak打包过程。

本质上是一个串行的过程:

但是当资源规模庞大之后,这个过程非常之久。

但实际上,我们并不一定需要把所有的资源处理完,才能打安装包。因为在手游中,安装包内的资源只是一少部分,其余的资源都是热更动态下载下来的。

在HotChunker的实现中,可以把安装包内Chunk的打包与动态下载的Chunk进行并行执行:

这样就能够节省大量的时间,用最快的速度打出安装包,再用一个独立的打包任务并行执行动态下载的Chunk的打包。

结语

基于HotChunker的包管理方案,是一种强大和精确地包管理机制。能够在精确拆分资源的同时,保证配置的灵活性,并且支持强大的Commandlet能力。能够方便地非侵入式扩展流程和进行资源审计。

而且,该方案是非侵入式集成,无需对默认的打包过程做任何处理也不需要修改引擎,整个实现都是插件化的,只要启用插件后拉起UE默认的打包就会自动执行。

全文完,若有不足之处请评论指正。

微信扫描二维码,关注我的公众号。

本文标题:资源管理:重塑UE的包拆分方案
文章作者:查利鹏
发布时间:2023年03月15日 11时24分
本文字数:本文一共有4.2k字
原始链接:https://imzlp.com/posts/37036/
许可协议: CC BY-NC-SA 4.0
文章禁止全文转载,摘要转发请保留原文链接及作者信息,谢谢!
您的捐赠将鼓励我继续创作!