UE 热更新:基于 HotPatcher 的自动化流程

HotPatcher是我之前开源的一个 UE4 热更新版本管理和资源打包工具,可以方便地进行版本间的差异分析和 pak 打包。之前的文章为了直观地介绍都是基于手动地在编辑器中进行配置和打包的,在真正的工程实践中,可以自动化的重复操作就要避免手动的参与,较早之前我就在插件中添加了 commandlet 的支持,近期修复了一些问题以及增加了很多针对 commandlet 的优化,本篇文章是基于 HotPatcher 的自动化热更新流程的工程实践。

在之前的文章中,介绍了 HotPatcher 的工作机制,可以从以下几篇文章中获取更详细的信息:

从流程上简单的划分为以下几个步骤:

  1. 使用 UE 的方式打基础包;
  2. 提取基础包的 paklist 文件,导入 HotPatcher 中进行分析;
  3. 生成基础包的 Release 信息(多平台使用一份 Release);
  4. 热更版本基于 Release 信息进行比对;
  5. 生成 Patch;

所以本篇文章的目的就是实现这几个关键步骤的可配置自动化流程,并且这个流程和所有的配置文件不应该受到引擎、项目路径在不同机器的差异等非关键因素的影响,实现真正通用的配置和流程。

自动化出基础包

当需要集成到 ci/cd 进行自动化构建出包,可以使用 UE 的 BuildGraph 来指导 UBT 和 UAT 工作。

可以直接使用下面的命令(引擎、项目、平台根据自己实际情况修改):

1
E:\UnrealEngine\Launcher\UE_4.25\Engine\Build\BatchFiles\RunUAT.bat BuildCookRun -nocompileeditor -installed -nop4 -project="E:/Examples/Blank425/Blank425.uproject" -cook -stage -archive -archivedirectory="E:/Examples/Blank425/Package" -package -pak -prereqs -nodebuginfo -targetplatform=Android -cookflavor=ASTC -build -target=Blank425 -clientconfig=Development -utf8output

支持的平台名字为:

1
Win32,Win64,HoloLens,Mac,XboxOne,PS4,IOS,Android,HTML5,Linux,LinuxAArch64,AllDesktop,TVOS,Switch,Lumin

也可以通过自己写 BuildGraph 的脚本来更精准地控制打包的阶段,这里不再赘述,推荐看这篇文章:UE4 基础:一键出包脚本

通过上面的命令可以通过命令行的方式将 UE 的项目打包,可以自己封装一层脚本集成到 ci/cd 平台上。

打包不是本节的重点,重点是在打包后的第一时间提取当前打包时 UE 产生的 paklist*.txt 文件。

UE 打基础包时会把项目中配置的资源打包成 pak 文件,UE 的打包流程中也是通过创建了一个 paklist*.txt 文件来记录哪些资源需要被打成 pak,所以,只要提取了 paklist*.txt 文件就能知道当前基础包中包含了哪些 文件(注意不仅仅是 uasset)。

HotPatcher提供了两种方式来生成基础包的 Release 信息(用于记录基础包中的所有资源信息):

  1. 通过导入打基础包时生成的 paklist 文件
  2. 通过自己指定打包的目录、添加外部文件

我建议的方式是使用第一种,因为更精准,可以精确地分析出基础包的任何一个文件(uasset/non-uasset),而且可以分析出不同平台的基础包中的差异文件,实现完全精确地基础包信息导出。

UE 打基础包时默认会把 paklist 文件存放在以下路径中(源码版与安装版的路径不同):

安装版引擎

1
%AppData%\Unreal Engine\AutomationTool\Logs\ 引擎路径拼接 \PakList_*.txt

其中 引擎路径拼接 的规则为:

1
2
3
4
# 引擎路径
E:\UnrealEngine\Launcher\UE_4.25
# 拼接之后
E+UnrealEngine+Launcher+UE_4.25

源码版引擎

1
Engine\Programs\AutomationTool\Saved\Logs\BuildCookRun\PakList_*.txt

所以,当打完基础包之后就按照上面的路径规则提取 paklist_*.txt 文件即可,以安装版为例,Win 上可以使用以下文件拷贝命令:

1
echo f|xcopy /y/i/s/e "%AppData%\Unreal Engine\AutomationTool\Logs\E+UnrealEngine+Launcher+UE_4.25\PakList_*.txt" "E:\ClientVersion\0.0.1.0"

就会把当前该目录下所有符合 paklist_*.txt 规则的文件拷贝到指定目录了。

为了统一获取源码版和安装版 paklist 的路径差异,可以使用 Python 写个脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def GetPakListFileRules(target_platform,engine_root_dir):
b_installed_engine = False
installedbuild_txt_path = "%s\\Engine\\Build\\InstalledBuild.txt" % engine_root_dir
if os.path.exists(InstalledBuild_txt_path):
b_installed_engine = True

ue_paklist_dir = ""
if b_installed_engine:
sys_appdata_dir = os.getenv("APPDATA")
ue_aut_log_dir = "Unreal Engine\\AutomationTool\\Logs"
engine_log_dir = engine_root_dir
engine_log_dir = engine_log_dir.replace(':','')
engine_log_dir = engine_log_dir.replace('\\','+')
engine_log_dir = engine_log_dir.replace('/','+')
ue_paklist_dir = "%s\\%s\\%s\\BuildCookRun" % (sys_appdata_dir,ue_aut_log_dir,engine_log_dir)
else:
ue_paklist_dir = "%s\\Engine\\Programs\\AutomationTool\\Saved\\Logs\\BuildCookRun" % engine_root_dir
print(ue_paklist_dir)
ue_pak_list_rule = "%s\\PakList_pakchunk*-%s.txt" % (ue_paklist_dir,GetRealPlatformName(target_platform))
return ue_pak_list_rule

调用方法:

1
ue_paklist_rules = GetPakListFileRules("Android_ASTC","C:\\Program Files\\Epic Games\\UE_4.26")

根据自己项目的需要简单修改即可。

注意:每打包一个平台都 必须要立即提取 该平台基础包的 paklist 文件,因为当执行下一个打包任务的时候,该目录下的文件会被清理。

自动化提取基础包信息

在上一步自动化出基础包中,实现了两个关键点的自动化:

  1. UE 的打包
  2. 提取当前平台的基础包的 paklist

这一节的重点,则是当我们打包了 0.0.1.0 版本数个平台的基础包和提取了相应的 paklist 文件之后,如何生成多平台的 release 的流程。

提取到的几个平台的 Paklist 目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
E:\ClientVersion\0.0.1.0>tree /a /f
卷 Document 的文件夹 PATH 列表
卷序列号为 0003-AEBB
E:.
+---Android_ASTC
| PakList_pakchunk0-Android_ASTC.txt
| PakList_pakchunk1-Android_ASTC.txt
| PakList_pakchunk2-Android_ASTC.txt
|
+---IOS
| PakList_pakchunk0-ios.txt
| PakList_pakchunk1-ios.txt
| PakList_pakchunk2-ios.txt
|
\---WindowsNoEditor
PakList_pakchunk0-WindowsNoEditor.txt
PakList_pakchunk1-WindowsNoEditor.txt
PakList_pakchunk2-WindowsNoEditor.txt

几个平台的 paklist 的存放结构和文件名是有相同规则的,只要有规则就可以自动化处理。

HotPatcher提供了 HotRelease 的 Commandlet,用于命令行的方式来导出 Release,可以指定配置文件:

1
UE4Editor.exe PROJECT.uproject -run=HotRelease -config="release-config.json"

但是,简单地指定配置文件其实对命令行是不那么友好的,因为首先需要构造出 config 文件才能指定,所以我近期对 HotRelease 的 Commandlet 做了增强,可以完全通过命令行来控制 config 的参数,并且可以不指定 config 文件。

因为前面已经提到 HotPather 支持两种模式导出 Release 的信息,所以如果当使用导入 paklist 时,只需要指定以下几个关键参数即可:

  1. VersionId 当前 Release 的版本号
  2. ByPakList 开启 Paklist 模式
  3. PlatformsPakListFiles 指定各个平台的对应的 paklist*.txt 文件
  4. SavePath 生成 Release 的存储路径

我使用 UE 的反射机制实现了可以在命令行指定参数结构中定义的名字来构造出真实的配置信息(暂不支持指定数组),可以在执行 commandlet 时直接指定上面的参数。

所以,需要使用的 HotRelease 的 Commandlet 命令为(方便观看我对参数换行拆分):

1
2
3
4
5
6
7
E:\UnrealEngine\Launcher\UE_4.25\Engine\Binaries\UE4Editor-cmd.exe
E:\Examples\Blank425\Blank425.uproject
-run=HotRelease
-versionid="0.0.1.0"
-ByPakList=true
-AddPlatformPakList=WindowsNoEditor+E:\ClientVersion\0.0.1.0\WindowsNoEditor\PakList_pakchunk0-WindowsNoEditor.txt+E:\ClientVersion\0.0.1.0\WindowsNoEditor\PakList_pakchunk1-WindowsNoEditor.txt+E:\ClientVersion\0.0.1.0\WindowsNoEditor\PakList_pakchunk2-WindowsNoEditor.txt
savepath.path="E:\ClientVersion\"

AddPlatformPakList是通过命令行指定 Platform-PaklistFiles 的参数,要求为:

1
PLATFORM_NAME+Paklist_0.txt+PakList_1.txt,PLATFORM_NAME+Paklist_0.txt+PakList_1.txt

可以指定多个平台,以逗号 (,) 分割,每个平台的节第一个为平台名通过 + 号分割,后面可以指定任意数量的 Paklist 文件。

以指定 WindowsNoEditor/Android_ASTC/IOS 三个平台为例:

1
-AddPlatformPakList=WindowsNoEditor+E:\Paklist_chunk01_WindowsNoEditor.txt+E:\Paklist_chunk02_WindowsNoEditor.txt,Android_ASTC+E:\Paklist_chunk01_Android_ASTC.txt+E:\Paklist_chunk02_Android_ASTC.txt,IOS+E:\Paklist_chunk01_IOS.txt+E:\Paklist_chunk02_IOS.txt

指定平台和文件路径都是有规则的,可以使用 Python 等脚本来针对项目的管理风格进行封装,最后直接使用 os.system 来执行 commandlet 即可,就会与在 Editor 中在 HotPatcher 编辑器中手动执行 ByRelease 一样的行为。

注意:虽然 Release.json 中记录了 Non-uasset 文件的绝对路径,但是实际上进行版本比对时,是不检查文件的绝对路径的,所以 Release.json 文件是跨机器通用的信息(以资源路径、mount 路径进行比对)。

自动化生成 Patch

上一节也已经介绍了如何自动化地生成 Release 信息,生成 Release 需要配置的选项不多,但是 Patch 就有非常多的配置了,所以完全地抛弃 config 文件是更麻烦的行为,所以我想了一个折衷方案——指定通用配置文件和命令行指定特殊的参数。

那么,哪些是通用的配置文件呢?

  • Patch 要扫描的资源目录
  • 要添加的文件(ini/shaderbytecode/assetregistry)
  • Non-uasset 的文件、目录
  • Chunk 的划分

哪些是可以通过命令行指定的呢?

  • VersionID
  • 基础版本的 Release 文件
  • 是否 Cook 当前 Patch 中的资源
  • 要生成 Patch 的平台
  • Pak 文件的命名规则
  • 保存目录

在老版本的 HotPatcher 中,配置文件是依赖各种参数的绝对路径的(添加的 Non-uasset)文件,近期我做了优化,可以支持相对于工程目录的相对路径。

以添加 Content/Script 目录为例,在之前的版本中只能通过选择绝对路径的文件夹:

现在可以通过 [PROJECT_CONTENT_DIR] 来替代:

在执行 Path 的过程中会扫描替换为当前工程的绝对路径。

支持 [] 标记的相对路径有以下几个:

1
2
3
4
5
6
[ENGINEDIR]
[ENGINE_CONTENT_DIR]
[PROJECTDIR]
[PROJECT_CONTENT_DIR]
[PROJECT_SAVED_DIR]
[PROJECT_CONFIG_DIR]

可以根据自己的需要使用,使用这种方式,就可以导出一份通用的配置。

PatchTemplate.json
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
{
"bByBaseVersion": true,
"baseVersion":
{
"filePath": ""
},
"versionId": "",
"assetIncludeFilters": [
{
"path": "/Game"
}
],
"assetIgnoreFilters": [],
"bForceSkipContent": true,
"forceSkipContentRules": [
{
"path": "/Engine/Editor"
},
{
"path": "/Engine/VREditor"
}
],
"forceSkipAssets": [],
"bIncludeHasRefAssetsOnly": false,
"bAnalysisFilterDependencies": true,
"bRecursiveWidgetTree": true,
"assetRegistryDependencyTypes": [
"Packages"
],
"includeSpecifyAssets": [],
"bIncludeAssetRegistry": true,
"bIncludeGlobalShaderCache": false,
"bIncludeShaderBytecode": false,
"bIncludeEngineIni": false,
"bIncludePluginIni": false,
"bIncludeProjectIni": false,
"bEnableExternFilesDiff": true,
"ignoreDeletionModulesAsset": [],
"addExternAssetsToPlatform": [
{
"targetPlatform": "AllPlatforms",
"addExternFileToPak": [],
"addExternDirectoryToPak": [
{
"directoryPath":
{
"path": "[PROJECT_CONTENT_DIR]/Script"
},
"mountPoint": "../../../Blank425/Content/Script"
}
]
}
],
"bIncludePakVersionFile": false,
"pakVersionFileMountPoint": "../../../Blank425/Versions/version.json",
"bEnableChunk": false,
"chunkInfos": [],
"bCookPatchAssets": true,
"pakCommandOptions": [],
"replacePakCommandTexts": [],
"unrealPakOptions": [
"-compress",
"-compressionformats=Zlib"
],
"pakTargetPlatforms": [],
"bCustomPakNameRegular": false,
"pakNameRegular": "{VERSION}_{CHUNKNAME}_{PLATFORM}_001_P",
"bIgnoreDeleatedAssetsInfo": false,
"bSaveDeletedAssetsToNewReleaseJson": true,
"bSavePakList": true,
"bSaveDiffAnalysis": true,
"bSaveAssetRelatedInfo": false,
"bSavePatchConfig": true,
"savePath":
{
"path": ""
}
}

这个配置就可以作为生成 Path 时 资源相关 的通用配置文件,当我们想要修改打包的配置时只需要修改这个文件即可。

其他的版本相关的参数都可以通过 HotPatcher 的 commandlet 命令行来指定了:

1
2
3
4
5
6
7
8
E:\UnrealEngine\Launcher\UE_4.25\Engine\Binaries\UE4Editor-cmd.exe
E:\Examples\Blank425\Blank425.uproject
-run=HotPatcher
-config="E:\ClientVersion\PatchTemplate.json"
-versionid="0.0.1.1"
-baseVersion.filePath="E:\ClientVersion\0.0.1.0\0.0.1.0_Release.json"
-AddPatchPlatforms="WindowsNoEditor,Android_ASTC,IOS"
-savePath.path="E:\ClientVersion\"

同样,这个命令的参数也是有规律可循的,当目录结构组织比较好的情况下,只要指定当前 Patch 的基础版本和当前的版本号就可以完全自动化的出包了。

同样,生成 Path 的流程也可以通过 Python 等脚本进行封装,可以在 ci/cd 平台上指定引擎目录、项目目录、基线版本、当前版本、以及生成哪些平台的 patch 等参数,方便控制。

结语

本篇文章介绍了使用 HotPatcher 来自动化执行热更新的流程,包括 UE 自动化出基础包、paklsit 的提取,Release 的生成、通用的 Patch 生成规则等,可以方便地集成至 ci/cd 平台,避免手动参与的过程。

后续有时间我会写一份用于 HotPatcher 自动化导出 Release/Patch 的 Python 脚本,方便直接使用。