UE 热更新:资产管理与审计工具

在前面的文章中,介绍了基础包的拆分规则和实现,在基础的打包规则稳定之后,日常开发中的关注重点就转向侧重于项目的资产管理和包体资源审计、分析项目中的资产大小和冗余情况等。

本篇文章介绍 UE 中的资源打包配置、常用的资产管理方式以及资产审计工具等工程实践,热更新系列文章的资源管理篇,也是对上一篇文章 UE4 热更新:拆分基础包 的内容补充。

打包配置

UE 中的资源打包选项可以在Project Settings-Packaging 中进行设置,可以在这里设置是否生成 Pak 以及资源进包的规则。

关于 UE4 基础包中包含的具体内容,我在 Unreal Open Day 的热更新方案中有介绍,有兴趣的可以去看这个视频:Unreal Open Day 2020 虚幻引擎 4 全平台热更新方案 | 查利鹏

  • UsePakFile:是否使用 Pak 文件,如果启用,会把游戏用到的资源都打包到 pak 文件中,如果不启用,则会把 cook 之后的 uasset/config/shaderbytecode/assetregistry/uplugin/uproject 等文件按照项目内的路径规则每个文件单独存放。

  • Generate Chunks:是否打包时生成 Chunk,如果项目内具有划分 Chunk 的规则,勾选此选项会生成多个 pak 文件。
  • Generate No Chunks:不生成 Chunk
  • Chunk Hard Reference Only:只把 Hard 引用的资源打包到 Chunk 中,Soft 的引用不会进包。
  • PakFile Compression Formats:指定 Pak 的压缩算法,该参数会传递给 UnrealPak,详见:ModularFeature:为 UE4 集成 ZSTD 压缩算法
  • ShareMaterialShaderCode:是否共享 ShaderCode,会生成 ushaderbytecode 到包内,可以减小包体的大小。

下面这些参数就是具体的控制把资源打到包内的规则:

Directories to never cook的目录不会被打到包内。

Additional Non-Asset Directories to Package用来把一些 non-uasset 文件打到包内,比如 Lua 或者其他的一些表格文件等。

Chunk 划分

UE 具有划分 Chunk 的功能,每个 Chunk 在打包时会生成一个单独的 pak 文件,可以通过创建一个 PrimaryAssetLable 来进行设置。

UE 关于划分 Chunk 的文档:

创建 PrimaryAssetLable 资源:

注意,在默认情况下,引擎中的资源和在项目设置中配置的 Non-Asset 的资源是强制打包到 Chunk0 中的,如果包含了引擎的资源也不会把它们打包到当前 Chunk 中(设置 Priority 也无用),用 PrimaryAssetLable 只能控制 uasset 的划分。

这部分代码在:

Runtime/Engine/Private/AssetManager.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
bool UAssetManager::GetPackageChunkIds(FName PackageName, const ITargetPlatform* TargetPlatform, const TArray<int32>& ExistingChunkList, TArray<int32>& OutChunkList, TArray<int32>* OutOverrideChunkList) const
{
// Include preset chunks
OutChunkList.Append(ExistingChunkList);
if (OutOverrideChunkList)
{
OutOverrideChunkList->Append(ExistingChunkList);
}

if (PackageName.ToString().StartsWith(TEXT("/Engine/"), ESearchCase::CaseSensitive))
{
// Some engine content is only referenced by string, make sure it's all in chunk 0 to avoid issues
OutChunkList.AddUnique(0);

if (OutOverrideChunkList)
{
OutOverrideChunkList->AddUnique(0);
}
}

// Add all chunk ids from the asset rules of managers. By default priority will not override other chunks
TSet<FPrimaryAssetId> Managers;
Managers.Reserve(128);

GetPackageManagers(PackageName, true, Managers);
return GetPrimaryAssetSetChunkIds(Managers, TargetPlatform, ExistingChunkList, OutChunkList);
}

通过编辑创建的 PrimaryAssetLable 资源的参数来控制它所管理的 Chunk 的资源:

  • Priority:指定当前 Chunk 的优先级,因为可能有几个 Chunk 中都会包含同一个资源,如何决定这个资源打包到哪个 chunk 中去?通过指定优先级可以控制资产的冗余,如果优先级相同则每个 Chunk 中都会包含一份。

  • ChunkID:指定生成 Chunk 的 ID 值,生成的 pak 文件命名规则为:pakchunk0-WindowsNoEditor.pak,pakchunk后的数字就是 ChunkID 值。

  • ApplyRecursively:对所管理的资源进行递归依赖分析,但不包含优先级更高的 Chunk 管理的资产。

  • CookRule:当前 Chunk 的 Cook 规则,可以实现控制该 PrimaryAssetLable 管理的资源都不打包(NeverCook)

    • UnKnown:如果有引用则进行 Cook
    • NeverCook:从不进行 Cook,该部分资源不会被打包
    • DevelopmentCook:如果有引用则在 Development 模式中 Cook,Production 时不 Cook
    • DevelopmentAlwaysCook:在 Development 模式中均进行 Cook,Production 不 Cook
    • AlwayCook:在任何情况下都执行 Cook
  • Lable Assets in My Directory:当前 PrimaryAssetLable 所在目录(以及子目录)下的资源都归它管理。

  • IsRuntimeLabel:设置 PrimaryAssetLable 资源本身在运行时可用,并且会打到包内,该选项不会对它标记的资产有影响。

  • Explicit Assets:指定所管理的资源文件,与 HotPatcher 中的 IncludeSpecifyAsset 类似。

  • ExplicitBlueprints:指定管理某种类型的蓝图资产。

  • AssetCollection:指定 Collection 名字。

可以看到通过上述的参数能够实现以下的资产管理功能:

  1. 指定目录
  2. 指定资源和其依赖
  3. 反向控制上述参数不打到包内(Never Cook)
  4. chunk 优先级控制资产冗余

在工程的资产管理中可以组合这几种模式来实现项目的需求:

如果地图副本较多,可以把副本及其资源作为一个 Chunk 管理,如果有一些资源被其他 chunk 中重复引用,可以把这部分资源单独作为 chunk 管理并设置成较高优先级。

可以在资源预览(Reference Viewer)中看到 Chunk直接 引用的资源,需要勾选Show Management References,被 Chunk 引用的类型是EAssetRegistryDependencyType::Manage,并不是普通的 Hard/Soft 资源引用关系。

但是 PrimaryAssetLable 也有个缺点,不能指定 non-uasset 文件,如果我们有更新 lua 或者 db、表格之类的需求,无法通过 PrimaryAssetLable 机制来进行打包和划分,这个需求可以通过 HotPatcher 实现,HotPatcher 也实现了类似 PrimaryAssetLable 的 Chunk 的功能用于拆分 Patch,详情见之前的文章:UE4 资源热更打包工具 HotPatcher

当我们使用了 PrimaryAssetLable 并且在 Project Settings-Packaging 中开启了Generate Chunks,在打包时就会按照配置的规则生成多个 pak 文件了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
D:\PCVersion\WindowsNoEditor>tree /a /f
| ClientGame.exe
| Manifest_DebugFiles_Win64.txt
| Manifest_NonUFSFiles_Win64.txt
|
+---Engine
\---ClientGame
+---Binaries
| \---Win64
| ClientGame.exe
| ClientGame.pdb
|
+---Content
| \---Paks
| pakchunk0-WindowsNoEditor.pak
| pakchunk1-WindowsNoEditor.pak
| pakchunk2-WindowsNoEditor.pak
|
\---Plugins

这里生成的多个 chunks 的 pak 文件可以设置在上一篇拆分基础包的规则文件中,实现基础包拆分的功能。

资产审计

Asset Audit

在上面的两节中分别介绍了 UE 中打包的项目配置和为资产划分 Chunk 的方式,本节会介绍在日常开发中进行资产审计的方式和工具。

通过Chunk 划分,这一节的介绍,我们可以标记一些资源打包到 pak 中去,但是我们无法很直观地知道被这个 chunk 所管理的资源有多少,在每个平台的大小,因为 UE 打包时需要执行 cook,每个资源的最终大小都是平台相关的,所以需要一种方法能够直观地看到基础包内 Chunk 的资源划分。

UE 提供了资产审计工具:Asset Audit,可以在 Editor 中通过 Windows-Developer Tools-Asset Audit 打开。

可以看到资源路径、大小、位于哪些 Chunk 中等一系列的信息,便于排查资源大小和 Chunk 中的资源冗余。

前面已经提到了 Editor 里的看到的资源大小和最终打到包内的大小是不一样的,在 Asset Audit 窗口的右上角会列出已经打包的平台(Saved/Cooked)下的平台。

Asset Audit是需要读取 DevelopmentAssetRegistry.bin 文件来得到某个平台的资源信息的,当打包某个平台之后,存储在以下路径中:

1
Client\Saved\Cooked\WindowsNoEditor\Client\Metadata\DevelopmentAssetRegistry.bin

这个文件记录着某个平台执行完 Cook 之后资源的大小以及引用关系等信息,注意 Cook 之后的资源如 Texture2D 等设置的压缩均以执行,但是打包成 pak 时也会执行压缩,这里列出来的大小是没有经过打包 pak 压缩的 Cook 资源之后的原始大小。

可以在打包时自动提取 Cooked 目录下的 Metadata 目录,在 AssetAudit 窗口的右上角选择 Custom,选择 DevelopmentAssetRegistry.bin 文件即可。

UnrealPakViewer

UnrealPakViewer是一个可以查看 Pak 文件的工具,可以方便地查看 Pak 中的文件信息以及从 Pak 中解压文件。

作者还支持了指定加载 AssetRegistry 文件,因为默认情况下 AssetRistry.bin 只在 Chunk0 中包含,打开不包含 AssetRegistry.bin 的 pak 是无法预览出资源类型的,可以通过指定外部文件的 AssetRegistry.bin 来使用,UnrealPakViewer 可以与 AssetAudit 结合起来用作为日常工程的资产审计工具。