Addressable本地模拟热更新
引
上网查阅了一些文章,自己用的Unity版本是2022.3.20f1,Addressable(之后我会简称AA,偷懒一下方便写作)的版本比较新,一些选项也不一样,稍微绕了点圈子,所以打算简单写篇文章记录一下。我自己的手动更新方案很粗糙,而且最重要的预加载策略我并没有写出来,所以仅供参考,如果有错误希望各位路过的大佬指出。
有时候暂时没有服务器环境,或者为了测试方便,需要在本地额外搭建一个服务器用于热更。AA自身给我们提供了一个起本地服务器的选项,但我觉得不是太好用,因为Curl的问题卡了半天(当初不学好计网真是罪过),所以后面还是找了个很轻量的三方软件来做替代,这些都会在后面的内容中提到。
远端设定
AA自带的服务器设定
这里端口自选,应该没有什么太大的禁忌,我姑且是避开了我梯子正在占用的端口,挑了80段的随便一个,总之影响不会很大,这里随便填应该都行。上传速度可以参照自己网络环境的带宽/8,也就是带宽理论的最高上行,如果是真实环境运营商给你的上行流量肯定跑不满的,本地环境随便写了,写的大一点也无所谓。ping超时我选择了5000ms,这个也可以填的宽松一点,10000我觉得也没啥问题。之后勾选Enable应该就能启动服务器了。
HTTP FILE SERVER(HFS)
很轻量,只有一个exe,WindowsDefender会报建议不启用,允许就好了。
在配置了AA自带的服务器生成了内网IP之后,HFS会自动捕捉到内网IP,你只需要修改一下上方的Port改成一致的即可。
AA的Profile配置
下面几个窗口打开位置基本都和上面一致,之后不再重复
如果你用AA自带的服务器配置的话可以不用修改任何东西,直接用默认的就行。用HFS因为要关联整个远端文件夹,所以我在IP后加了输出的Build路径,方便之后直接在HFS里关联。
AA的Setting配置
主要关心最后三个配置项:
Build Remote Catalog:进行远端更新一定要勾,目录文件是非常重要的。
Build & LoadPaths:改成Remote,即使用远端路径加载。
Only update catalogs manual:只允许手动更新,也就是说开始运行时Unity不会帮你做更新目录这件事。
一般来说如果我们在游戏运行开头自己做手动更新和预加载,这个选项也是要勾的,因为Unity自动更新是hash全扫一遍,如果你想要分目录并且加一些自定义要素,自动更新就没办法满足需求了。
Group的配置
怎么分组是另外一件需要讨论的事了,这里我们假设已经制定好了分组策略,对于需要跟随游戏本地加载的资源我们还是保持本地打包即可,而对于放在远端同步的这里就需要改成Remote。
Windows Build下的几个路径
HFS需要关联下面的子文件StandaloneWindows64,这个文件夹里有Hash文件,json文件,以及打包好的AB包,也就是说所有需要的文件都在这个文件夹里,所以在HFS右键添加这个文件夹,之后查找的时候就能按照路径找到了。
为了测试我并没有特意切换到安卓打包,所以下午在找缓存路径方面迷茫了一会,Windows下默认都把热更的东西塞到Appdata里,也就是C盘路径,我猜可能是因为PC有Steam pipeline这类第三方的分发工具所以没啥热更的需求,所以才都塞在默认的数据文件夹里。
用于校验的hash和json文件的位置其实就在常用的持久化路径下:
C:\Users\peter\AppData\LocalLow\DefaultCompany\xLua_Demo\com.unity.addressables
翻一下设置里你的公司名项目名应该就能找到了。
AB包的缓存路径则是在一个很神奇的地方:
C:\Users\peter\AppData\LocalLow\Unity
底下会有一个以 公司名_项目名 命名的文件夹,里面都是16进制编码的文件命名。
手动更新目录
AA其实有一步到位的API,Addressables.UpdateCatalogs(),但你也可以获取目录列表再更新目录,这里我就不过多讲解,直接上我写的代码了。
using System; using System.Collections; using TMPro; using UnityEngine; using UnityEngine.AddressableAssets; using StarFramework.Runtime; public class HotUpdate : MonoBehaviour { //状态显示UI public TMP_Text statusText; //设定的超时 public float Timeout = 5000f; //状态变量 private bool isChecking = false; private float checkUpdateTime = 0f; void Start() { //一开始先检测网络状态 如果不联网则不推进 if(Application.internetReachability == NetworkReachability.NotReachable) { statusText.text = "没有网络连接,不进行推进"; Debug.Log("没有网络连接,不进行推进"); } else { statusText.text = "开始检测是否有资源更新"; Debug.Log("开始检测是否有资源更新"); StartCoroutine(CheckUpdate()); } } void Update() { //超时检测 if(isChecking) { checkUpdateTime += Time.deltaTime; if(checkUpdateTime > Timeout) { isChecking = false; StopAllCoroutines(); statusText.text = "检测超时"; } } } IEnumerator CheckUpdate() { isChecking = true; //计时用 用本地时间无所谓 因为只看加载区间 var startTime = DateTime.Now; Addressables.CheckForCatalogUpdates(true).Completed += (CheckHandle)=> { isChecking = false; var logCheckTime = string.Format($"检测所消耗时间{(DateTime.Now - startTime).Milliseconds}ms"); statusText.text = logCheckTime; Debug.Log(logCheckTime); //判断返回的目录更新数目是否大于0 如果是就要进行更新 if(CheckHandle.Result.Count > 0) { startTime = DateTime.Now; string logUpdateTime = string.Format($"更新花费了{(DateTime.Now - startTime).Milliseconds}ms"); statusText.text = logUpdateTime; Debug.Log(logUpdateTime); Addressables.UpdateCatalogs(CheckHandle.Result, true).Completed +=(UpdateHandle)=> { Debug.Log("更新成功,加载主场景"); SceneControl.Instance.LoadSceneAdditive("Main"); }; } else { Debug.Log("未检测到要更新的资源,直接加载主场景"); SceneControl.Instance.LoadSceneAdditive("Main"); } }; yield return true; } }
实际运行时的效果大概就像下面控制台这样:
假如我们给某个组里添加一个新资产,例如说这里我加了一张新图片进来:
补充:预加载
我之前会有一个误区,就是热更新后包只要同步了就完事大吉了,但实际上这里的操作只是确保了你缓存的AB包和远端是同步的,并没有加载到内存里,所以如果你想异步加载的时候不会太卡顿,一般的做法就是创建一个Loading界面把包预加载进来。所以判断同步并更新在游戏的加载过程中只是占了一部分而已,另一部分实际上是在做预加载,把资源先Load到内存里,这里的代码并没有给预加载的策略,因为我的测试项目体量很小,很难达到卡顿的瓶颈,但是实际项目里还是会有这一步的。