Addressable本地模拟热更新

上网查阅了一些文章,自己用的Unity版本是2022.3.20f1,Addressable(之后我会简称AA,偷懒一下方便写作)的版本比较新,一些选项也不一样,稍微绕了点圈子,所以打算简单写篇文章记录一下。我自己的手动更新方案很粗糙,而且最重要的预加载策略我并没有写出来,所以仅供参考,如果有错误希望各位路过的大佬指出。

有时候暂时没有服务器环境,或者为了测试方便,需要在本地额外搭建一个服务器用于热更。AA自身给我们提供了一个起本地服务器的选项,但我觉得不是太好用,因为Curl的问题卡了半天(当初不学好计网真是罪过),所以后面还是找了个很轻量的三方软件来做替代,这些都会在后面的内容中提到。

远端设定

AA自带的服务器设定

从Hosting打开服务器设置窗口
创建一个新的本地服务器,内网IP会自动生成

这里端口自选,应该没有什么太大的禁忌,我姑且是避开了我梯子正在占用的端口,挑了80段的随便一个,总之影响不会很大,这里随便填应该都行。上传速度可以参照自己网络环境的带宽/8,也就是带宽理论的最高上行,如果是真实环境运营商给你的上行流量肯定跑不满的,本地环境随便写了,写的大一点也无所谓。ping超时我选择了5000ms,这个也可以填的宽松一点,10000我觉得也没啥问题。之后勾选Enable应该就能启动服务器了。

HTTP FILE SERVER(HFS)

很轻量,只有一个exe,WindowsDefender会报建议不启用,允许就好了。

在配置了AA自带的服务器生成了内网IP之后,HFS会自动捕捉到内网IP,你只需要修改一下上方的Port改成一致的即可。

后面只要把文件夹关联了 就和自带的测试服务器一样了

AA的Profile配置

下面几个窗口打开位置基本都和上面一致,之后不再重复

默认应该是这个样子的
我这里因为三方所以用的Custom改了LoadPath

如果你用AA自带的服务器配置的话可以不用修改任何东西,直接用默认的就行。用HFS因为要关联整个远端文件夹,所以我在IP后加了输出的Build路径,方便之后直接在HFS里关联。

AA的Setting配置

选项和老版本相比发生了点变化

主要关心最后三个配置项:

Build Remote Catalog:进行远端更新一定要勾,目录文件是非常重要的。

Build & LoadPaths:改成Remote,即使用远端路径加载。

Only update catalogs manual:只允许手动更新,也就是说开始运行时Unity不会帮你做更新目录这件事。

一般来说如果我们在游戏运行开头自己做手动更新和预加载,这个选项也是要勾的,因为Unity自动更新是hash全扫一遍,如果你想要分目录并且加一些自定义要素,自动更新就没办法满足需求了。

Group的配置

通过点击单个Group可以快速跳转到它的设置
还是主要关心地址就行

怎么分组是另外一件需要讨论的事了,这里我们假设已经制定好了分组策略,对于需要跟随游戏本地加载的资源我们还是保持本地打包即可,而对于放在远端同步的这里就需要改成Remote。

Windows Build下的几个路径

远端服务器的输出路径就在工程文件夹下与Assets同级的位置 ServerData就是输出路径了

HFS需要关联下面的子文件StandaloneWindows64,这个文件夹里有Hash文件,json文件,以及打包好的AB包,也就是说所有需要的文件都在这个文件夹里,所以在HFS右键添加这个文件夹,之后查找的时候就能按照路径找到了。

这里选Real就可以了,没必要Virtual

为了测试我并没有特意切换到安卓打包,所以下午在找缓存路径方面迷茫了一会,Windows下默认都把热更的东西塞到Appdata里,也就是C盘路径,我猜可能是因为PC有Steam pipeline这类第三方的分发工具所以没啥热更的需求,所以才都塞在默认的数据文件夹里。

用于校验的hash和json文件的位置其实就在常用的持久化路径下:

C:\Users\peter\AppData\LocalLow\DefaultCompany\xLua_Demo\com.unity.addressables

翻一下设置里你的公司名项目名应该就能找到了。

Project Setting

AB包的缓存路径则是在一个很神奇的地方:

C:\Users\peter\AppData\LocalLow\Unity

底下会有一个以 公司名_项目名 命名的文件夹,里面都是16进制编码的文件命名。

貌似是GUID 没仔细对过

手动更新目录

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;
    }

}

实际运行时的效果大概就像下面控制台这样:

没有发生变化的情况

假如我们给某个组里添加一个新资产,例如说这里我加了一张新图片进来:

发生变化的情况,至于为啥是0ms我也很好奇

补充:预加载

我之前会有一个误区,就是热更新后包只要同步了就完事大吉了,但实际上这里的操作只是确保了你缓存的AB包和远端是同步的,并没有加载到内存里,所以如果你想异步加载的时候不会太卡顿,一般的做法就是创建一个Loading界面把包预加载进来。所以判断同步并更新在游戏的加载过程中只是占了一部分而已,另一部分实际上是在做预加载,把资源先Load到内存里,这里的代码并没有给预加载的策略,因为我的测试项目体量很小,很难达到卡顿的瓶颈,但是实际项目里还是会有这一步的。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注