HotPatcher项目开源这一年多以来,经过了不少的更新和优化,也被越来越多的开发者选择作为自己项目的热更新方案,期间有不少人陆陆续续询问UE4热更新相关遇到的问题,很多问题比较常见,重复询问的频率也比较多,所以我准备把一些常见的问题进行整理,方便初步上手UE4热更新方案的人能够尽快地排查问题。
本篇文章会持续更新UE4热更新和HotPatcher相关的Q&A内容,有疑问的地方也可以直接在本篇文章中评论,我会定期统一回答和整理,也可以加入我的UE4热更新群讨论遇到的问题(QQ群958363331)。
HotPatcher相关问题
- 是否可以用在商业项目中?
本软件的开源协议:允许在商业项目中免费使用功能,但不允许任何第三方基于该插件进行任何形式的二次收费,包括但不限于录制收费课程、对插件及代码的二次分发等。
是否可以热更C++?
不能,只能用来更新uasset和Non-Asset(lua/db/json等等)。支持移动端热更吗?
支持,本身HotPatcher是没有平台限制的,可以打包和管理UE支持的任意平台。
注意:使用HotPatcher打包时,需要避免一个目录既包含uasset又包含non-asset的情况,不然会导致未被cook的uasset打包。
热更新系列文章
我写的UE4热更新的系列文章,可以作为工程实践的参考:
- UE热更新:需求分析与方案设计
- UE资源热更打包工具HotPatcher
- UE热更新:基于HotPatcher的自动化流程
- 2020 Unreal Open Day
- UE热更新:拆分基础包
- UE热更新:资产管理与审计工具
- UE热更新:Create Shader Patch
- UE热更新:Questions & Answers
- UE热更新:资源的二进制补丁方案
- UE热更新:Shader 更新策略
pak的自动挂载目录
以下三个路径中的Pak会在引擎启动时自动挂载:
- Engine/Content/Paks
- GAME_DIR/Content/Paks
- GAME_DIR/Saved/Paks
1 | void FPakPlatformFile::GetPakFolders(const TCHAR* CmdLine, TArray<FString>& OutPakFolders) |
这三个路径下的pak的默认优先级不同(除非通过_1_P.pak这种形式命名):
1 | int32 FPakPlatformFile::GetPakOrderFromPakFilePath(const FString& PakFilePath) |
Mount Point的作用
在Mount Pak的时候,有一个参数可以指定MountPoint:
1 | /** |
那么它是干什么的呢?
首先从Mount函数开始:
1 | if (InPath != NULL) |
如果在调用Mount时传递了InPath
,则通过加载Pak的FPakFile实例调用SetMountPoint
,把InPath设置给它。
其实在FPakFile中,MountPath是有默认值的(从Pak文件中读取),在FPakFile的构造函数中调用了Initialize(Reader, bLoadIndex);
,Initialize中又调用了LoadIndex
,在LoadIndex
中从Pak中读取Pak的Mount Point的逻辑:
1 | // Runtime/PakFile/Private/IPlatformFilePak.cpp |
简单的可以理解为:如果Mount时不传递Mount Point就会从Pak文件中读取,如果有传入就设置为传入的值(Pak文件中的MountPoint是Pak中所有文件的公共路径)。
那么,给Pak设置MountPoint的作用是什么呢?
真实目的是,检测要加载的文件是否存在于当前Pak中!因为Pak的Mount Point的默认含义是当前Pak中所有文件的公共路径,所以只需要检测要读取的文件是否以这个路径开头,就可以首先排除掉基础路径不对的文件(基础路径都不对,意味着这个文件在Pak中也不存在)。
具体逻辑可以看这个函数的实现:
1 | // Runtime/PakFile/Public/IPlatformFilePak.h |
当我们从Pak中读取文件时,通过对游戏中所有Mount的Pak调用Find
函数,而FPakFile::Find
的函数就实现了上述我说的逻辑:
1 | // Runtime/PakFile/Private/IPlatformFilePak.cpp |
所以,MountPoint的作用就是在从Pak中查找文件时,首先判断文件的路径是否与Pak中所有文件的基础路径相匹配(StartWith),如果不存在也就不会进入后续的流程了。
Pak无法被挂载
在本体包中开启signature后,打包出来的Pak无法被挂载
同样是pak的signature的错误,是因为没有为pak生成对应的.sig文件。
Log中的内容如下:
1 | LogPakFile: Warning: Couldn't find pak signature file '../../../Pak/Content/Paks/1.0.3_WindowsNoEditor_P.pak' |
这是因为打出本体包时Project Setting
-Crypto
中的bEnablePakSigning
被设置成了true,这样对打出来的包里的所有pak
都会执行校验,目的就是为了确保只有自己打包的pak才可以被加载。
相关的代码处理在:
1 | // Runtime/PakFile/Private/SignedArchiveReader.cpp |
所以,如果在用HotPatcher打包pak时没有与项目指定相同的加密参数,则导致放入包内的pak会加载失败(因为验证失败了)。
解决的办法就是,在使用HotPatcher时指定与项目相同的加密信息,当直接使用UE打出本体包时,会默认在下列路径中生成一个Crypto.json
文件:
1 | PROJECT_DIRECTORY\Saved\Cooked\WindowsNoEditor\PROJECT_NAME\Metadata\Crypto.json |
它里面的内容是根据Project Setting
-Crypto
中的选项生产的。
使用方法为:
在HotPatcher的UnrealPak
参数项添加参数:-cryptokeys="Crypto.json"
(在UE4.23+中还需要添加-sign
参数):
重新生成Pak就会在Pak的目录里生成与Pak同名的.sig
文件了,把pak
和sig
文件一同拷贝到挂载目录里就可以了。
UnrealPak的参数可以看我之前的一篇文章:UE4工具链配置与开发技巧#UnrealPak的参数
Pak master signature table check failed for pak
- 使用HotPatcher打包出来的pak在挂载时Crash并具有Pak master signature table check failed for pak提示
这是由于打出本体包的时候在项目设置中设置了Signing
加密,需要在HotPatcher中的UnrealPak参数中添加相同的加密参数。
在IPlatformFilePak.cpp
中的RegisterPakFile
中,同样做了判断:
1 | // Runtime/PakFile/Private/ |
iOS热更metallib问题
在4.25存在不会重新加载shaderbytecode的问题,而且引擎内部对加载metallib是单独处理的流程,无法复用usahderbytecode的流程,所以出iOS包尽量使用远程打包的方式,会生成ushaderbytecode
,在4.25里LoadLibrary没有问题,但是如果去加载metallib就有问题。
UE热更Shader相关的内容可以看之前的文章:UE热更新:Create Shader Patch
UE4.25+ ShaderPatch Crash
这是因为在4.25+引擎内部的bug导致的,UE热更新:Create Shader Patch#4.25+ ShaderPatch Crash](https://imzlp.com/posts/5867/)这篇文章中提供了修改方案。
热更一个不存在的插件中的资源
打包之后引擎是会从upluginmanifest中读取当前工程中具有有哪些插件的,加载插件中的资源先判断插件是否存在,从而实现一个粒度较粗的过滤效果。
所以,当需要把一个在基础包中不存在的插件打包至pak中,需要在打包资源的同时需要把项目的upluginmanifest
文件同步打包,挂载点为:
1 | ../../../PROJECT_NAME/Plugins/PROJECT_NAME.upluginmanifest |
关于upluginmanifest
的介绍,可以看我之前的笔记:UE4#upluginmanifest。
热更的资源没有效果
如果热更蓝图,逻辑没有变化,需要检查资源是否被Cook,可以手动在Content Browser中通过HotPatcher中提供的功能对选中资源执行Cook,也可以在打包Patch时勾选bCookAsset
选项。
热更的资源材质丢失
如果时热更了新的资源/材质,没有效果,需要检查是否把Shaderbytecode打包,如果新增材质没有打包shaderbytecode是会导致Shader获取失败使用默认材质的。
Log中的错误:
如果在打包时使用了bSharedShaderLibrary
,在运行时手动Mount包含新shaderbytecode的pak,则需要在mount之后手动重新加载一遍shaderbytecode,这样引擎在加载材质时才能够读取到最新的Shader:
1 |
|
在mount之后,根据ShaderLib的实际路径和命名调用即可。
如果没有开bSharedShaderLibrary
,则无需处理,引擎会默认使用资源内的Inline Shader Code。
AssetRegistry是否必须热更
看需求,如果Runtime的代码中有通过AssetRegistry模块获取资源的引用关系、检测资源是否存在,需要热更。但是AssetRegistry并不是引擎必要的,如果肯定不会在运行时用到,可以去掉它,会节省一点内存。
具体介绍可以看我之前的笔记:UE4#控制AssetRegistry的序列化
Android提示not found uproject
UE中有一个BUG,在4.25.1引擎版本中可以复现,步骤如下:
- 安装apk,第一次启动游戏
- 打开UE的沙盒数据目录
UE4Game/PROJECTNAME
,在这个目录下创建Content/Paks
目录 - 重新启动游戏
Log中也有Project file not found: ../../../FGame/FGame.uproject
提示。
在Android上自动挂载的Pak文件可以放到Saved/Paks
下,有时间具体分析一下这个问题。
控制资源不打到基础包中
拆分基础包的实践可看我的这两篇文章:
分析某个平台的包中的资源
可以使用UE提供的Asset Audit工具,需要在每次打包时备份好Cooked/PLATFORM/PROJECT_NAME/Metadata目录中的DevelopmentAssetRegistry.bin文件。
也可以使用UnrealPakViewer来直接加载Pak文件。
具体可以看这篇文章的资产审计小节:UE热更新:资产管理与审计工具#资产审计
UMG子控件热更不生效
如果Instanced的形式引用的UMG,子UMG的变动需要递归包含所有以子控件形式引用的UMG资源。我之前在笔记中记录过这个问题:UE4#UMG的子控件引用热更问题。
解决方案:HotPatcher中具有一个递归分析UMG父控件的的选项(bRecursiveWidgetTree),开启即可。
这个问题的具体分析在我2020 UOD的演讲中有详细介绍,感兴趣的可以去这里查看视频和PPT:
注意,Instanced的UMG只对控件的序列化有影响,对于子控件中的逻辑变动无关的。
如下这种情况,只打包子控件的UMG是生效的:
- UMG_Child中有一个Button,并绑定了Button事件,输出了TEST01
- UMG_Main中嵌入UMG_Child
- 打出版本
- 修改UMG_Child中Button事件的输出,改为TEST02
- 只打包
UMG_Child
- 创建出UMG_Main,点击它里的UMG_Child的Button,会正确输出TEST02
Pak是否可以跨引擎版本使用
不行,要保持打包和使用的引擎版本一致。
从A项目打包给B项目使用的Pak
HotPatcher中做了替换pakcommand的功能,可以通过以下参数指定:
注意:From和To都必须要包含../../../
前缀,不然会把文件的绝对路径替换了。
plugin HotPatcher faild
打包之后又如下提示:
应该是纯蓝图项目打包导致的,在项目中新建一个C++类,变成一个C++工程重新打包即可。
打包原始uasset资源
目前插件并没有能直接选择打包原始uasset资源的功能,但是可以使用一个取巧的方法实现。
可以设置ReplacePakCommandTexts
把Cooked的目录替换为项目的Content目录
:
虽然*pakcommand.txt
里依然会有uexp
等文件的记录,但是在项目的Content
下没有,也并不会打包到pak中去,会忽略不存在的文件,并有以下log输出:
1 | LogPakFile: Warning: Missing file "D:/Client/Content/Assets/Scene/Map/LookDev/DemoAssets/Mesh/FFXV/000.uexp" will not be added to PAK file. |
算是取巧的一种实现吧,但是可行。
导出跨机器的通用配置文件
Q:HotPatcher中导出的配置,有些是依赖于本地绝对路径的文件,如BaseVersion、Non-Asset文件、SavePath等,不同的机器这些绝对路径并不能保证一致,能否基于相对路径进行配置?
A:可以。HotPatcher所有能够指定路径的配置项,都支持标记符替换,可以使用以下标记符来替代绝对路径。
1 | [ENGINEDIR] |
在打包时会自动替换为当前机器的绝对路径,基于相对路径则是完全通用的配置文件。
依赖分析耗时
当项目资源非常多,插件提供的依赖分析功能的耗时十分可观,其主要目的是分析被依赖的资源,防止依赖了但是没有被打包的情况。
如果依赖了引擎、插件中的资源,不进行依赖分析是管理不到的(或者手动指定),而且如果想要剔除没有引用的资源不分析也做不到。不需要的话也是可以关掉的,设置bAnalysisFilterDependencies就可以了。
这样只会对配置中所指定的目录下的所有资源、单独指定的资源,与基础版本中的进行Diff分析,能够减少依赖分析的耗时(如果资源量非常大,并且能保证所有的资源依赖都在/Game)下的可以不开启依赖分析。
Memory mapped file has the wrong alignment
注意:最新的插件中已经做了适配支持,无需再手动指定。
在IOS包中出现以下Crash:
1 | IsAligned(MappedRegion->GetMappedPtr(), FPlatformProperties::GetMemoryMappingAlignment()), TEXT("Memory mapped file has the wrong alignment!") |
这是因为打包IOS时没有设置alignformemorymapping
的值导致的,默认是0,但是IOS要设置为16384
:
1 | static FORCEINLINE int64 GetMemoryMappingAlignment() |
打包Pak时传递参数即可:-alignformemorymapping=16384
。
在UE默认打包时也添加了该参数:
1 | private static void CreatePaks(ProjectParams Params, DeploymentContext SC, List<CreatePakParams> PakParamsList, EncryptionAndSigning.CryptoSettings CryptoSettings, FileReference CryptoKeysCacheFilename) |
它读取的是Config/PLATFORM/*Engine.ini
中MemoryMappedFiles
Section下的Alignment
值,如IOS:
1 | [MemoryMappedFiles] |
正好对应了代码里的值,并且只有IOS的平台指定了该项的值:MemoryMappedFiles。
导入Release漏掉部分插件资源
请检查插件路径名字是否与插件名完全一致。如:
1 | Plugins\HDiffPatchUE4\HDiffPatch.uplugin |
这种情况是不允许的,路径必须要与插件名一致。
Editor中资源打包和真机加载流程
随着游戏内资源越来越多,包体越来越大,但移动端的包大小是有限制的,不能无限度地把所有的资源都打到基础包中,Android上限是2G(可以开启Allow Large Obb files
以支持4G),IOS提交到AppStore的上限也是4G,通常会保持IOS/Android基础包内的资源统一,其余的资源用热更或动态下载的形式更新到设备上。
所以,实际上在开发阶段打进基础包内的资源,只是工程中的一部分。经常无法满足策划、美术、测试同学的验证需要。
- 新的地图、资源想要在真机查看
- 包内资源有问题,提交后等待整包构建时间太久
- 基础包的资源量超标,可能无法添加新的资源
基于这种需求,在开发阶段,可以使用基础包+补丁的形式实现。
资源的打包
在我们当前的工程中,在编辑器中,可以直接在资源、目录上右键选择Cook And Pak Actions,选择对应的平台,以及选择AnalysisDependencies(分析依赖)。
不同平台的包需要选择的不一样(根据自己项目实际情况选择):
- 安卓:Android_ASTC
- IOS:IOS
- PCVersion:WindowsNoEditor
执行之后右下角会有执行提示:
需要等待执行完毕,这个过程不会卡住编辑器,可以做其他的编辑操作。
执行的耗时取决于引用的资源量,以及所选择的资源之前有没有执行过打包(DDC)。
执行完毕之后,会在项目的Saved/HotPatcher/Paks/{EXECUTE_TIME}/Android_ASTC
下面创建出一个对应的 .pak 文件,这个 pak 文件就是打包资源的补丁包,文件名中包含了平台名,不同平台的 pak 文件不能混用。
放入真机中
PCVersion
PCVersion需要将这个Pak文件放到以下目录中:
1 | WindowsNoEditor\PROJECT_NAME\Content\Paks |
若不存在Paks目录,可手动创建。
Android
在Android中,需要将这个 pak 文件放到下面目录中:
1 | UE4Game/PROJECT_NAME/PROJECT_NAME/Saved/Paks |
如果修改了数据目录到沙盒路径,则是这个路径:
1 | Android/data/com.xxxx.yyyy/files/UE4Game/PROJECT_NAME/PROJECT_NAME/Saved/Paks |
如果没有Paks目录,可以手动创建一个。
IOS
IOS则要复杂一些,需要用爱思助手或iMaZing等IOS管理工具传递文件。
放入 文稿 - PROJECT_NAME - Saved - Paks下,若不存在Paks目录,可手动创建。
检查挂载情况
当将pak文件放入上述目录之后,就可以启动游戏了。
如果为了确认pak文件是否生效,可以从Log中查看:
1 | LogPakFile: Display: Found Pak file ../../../PROJECT_NAME/Saved/Paks/2022.10.18-13.02.53_IOS_001_P.pak attempting to mount. |
有这样的Log,就代表资源包被引擎读取成功,可以直接使用,就像它被打进了基础包中一样。
注意事项
手动打包Pak的优先级高于基础包内的资源,意味着一个资源如果补丁包和基础包中同时存在,就会替换基础包内的,就像热更新逻辑。
- 不同平台的pak不能混用!
- 测试完毕之后要把设备上的pak文件删除,不然后面新打的包可能会出现资源表现错误。
UE5+的问题排查
注意:从v82.0开始,已兼容UE5.2、5.3。
在UE5中如果发现打包的Pak出现异常:
- Pak无法挂载:排查是否项目开启了IoStore,在项目设置中关闭后,重新打基础包。