Unity 是使用很广泛的游戏引擎,值得深入研究。

其中的游戏热更新是重要的应用技术,但是Unity官方对这一技术的支持有限,并且受限于应用市场的政策,需要特别关注。

在这个页面里,能找到与 Unity 相关的热更新笔记。

游戏热更新

游戏热更新:游戏或者软件更新时,无需重新下载客户端进行安装,而是在应用程序启动的情况下,在内部进行的资源或者代码更新。用户重启客户端就能实现客户端更新的需求或者功能;或者也可以这样说:不需要通过在应用商店更新应用,就可以实现新的需求和功能。

为什么要用热更新?

热更新的优点:

  1. 迅速修复Bug – 避免重新下载安装包,游戏内部及时更新Bug;能够缩短用户取得新版客户端的流程,改善用户体验。
  2. 减小安装包的体积 – 非核心资源上传服务器,运行时动态加载剩余资源
  3. 迅速更换游戏"内核" – 狸猫换太子,绕过审核,迅速对游戏进行更新

就拿iOS来说,如果你需要更新应用,需要经过一系列的苹果提审流程,少则几天,多则一个月,审核周期比较难控制。试想下,如果你的上线游戏出现了一个严重的bug,此时如果没有热更新,那么只有两种选择:第一种:提交更新包到应用商店,等审查(时间不受自己控制);第二种:坐视不管。无论哪种方式,你都应该清楚,可以提前准备简历和打包行李了。

还有一个原因,对于大型游戏来说,玩家更新成本太大。App Store上,你更新一个应用,其实是重新下载一次,而不是增量的更新。

如果有了热更新机制会怎么样呢?你可以通过热更新推送给每一个用户,在较短的时间(自己控制)内解决这个bug。

热更新基本流程:

  1. 版本号的比较,如果版本号不同才继续以下流程(version.txt记录版本号,可选)
  2. 下载资源服务器上的对比文件(files.txt记录所有文件md5码,类似于目录)
  3. 确定下载列表,将最新下载的对比文件和本地旧对比文件对比,记录缺少或不同的文件。(利用files.txt中的md5码实现此步骤)
  4. 根据下载列表,下载所需的资源。(一般放在Application.persistentDataPath)
  5. 解压(如果文件压缩过需要先解压,可选)
  6. 保证下载成功后,用最新的对比文件覆盖本地的对比文件(更新目录)

Unity 热更新

Unity热更新是指能够在不重新安装app包的情况下,来完成更新新的功能和解决开发中遇到的bug。

Unity热更新主要是包含资源更新与代码更新两大板块:

  • 资源更新: 目前Unity 的Assetsbundle与Addreessable机制都能很好的做到资源更新。做资源更新时还要根据版本,比对前后版本的资源变化来做增量的资源更新。
  • 代码更新: 目前做热更新的解决方案有 Lua, C#, JavaScript/TypeScript等方案。

热更新一般可以分为美术资源热更新和代码热更新。美术资源热更新的方案一般就是采用AssetBundle方式。代码热更新方案一般可以分为以下两种方法:

第一种方案:

(1)项目开发中,可以将部分逻辑提取至一个单独的代码库工程中,打包为DLL;

(2)将DLL打包为AssetBundle;

(3)Unity程序动态加载AssetBundle中的DLL文件,使用Reflection机制来调用代码。

这个方案看起来很完美,能支持C#级别的代码热更新,但是它有限制:苹果官方禁止Ios下的程序热更新;JIT在Ios下无效。所以这个方案在Android下还是可以用用的。但是一般情况下,一个项目不可能为Android和Ios开发两套不同的热更新方案,所以这个方案实际上也就无法应用到项目之中了。

这里试验热更新用的是 Huatuo 方案,现在改名叫 hybridclr 方案。

hybridclr试验

我使用 hybridclr 在 Windows 上进行了一下小试验,可以对 Windows 的 Unity 游戏进行热更新,步骤很简单,简单记录一下:

  1. 下载HybridCLR 体验项目,并对对应的Unity版本打开。
  2. 根据安装HybridCLR在Unity里把HybridCLR package安装上。
  3. 通过Unity工程里的菜单HybridCLR ==> Build ==> Win64,构建 Win64 的 Unity 游戏。
  4. 如果游戏能正常运行,则把第1点里提到的体验项目的目录:hybridclr_trial\Release-Win64 拷贝出来,可以再单独运行一下HybridCLRTrial.exe看是否正常。
  5. 用Visual Studio 2019打开体验项目的 C# 代码,修改 Assets/HotUpdateMain.cs 文件,添加几行测试代码,比如:
1
2
3
4
5
        Debug.Log("=======添加测试代码,测试热更新是否成功=======");

        Debug.Log("=======再测试一下=======");

        Debug.Log("=======再测试一下2222=======");
  1. 通过Unity工程里的菜单HybridCLR ==> Build ==>BuildAssetsAndCopyToStreamingAssets,重新构建 Win64 的 Unity 游戏。
  2. 拷贝 hybridclr_trial\HybridCLRData\HotUpdateDlls\StandaloneWindows64 目录下由第6步生成的文件,全部拷贝到第4步里的HybridCLRTrial.exe目录,替代掉相同文件,此时 HybridCLRTrial.exe 还在运行,内容没有改变,但是替代相同文件并不会提示占用问题。
  3. 关闭HybridCLRTrial.exe,再重新运行程序,即能看到新更新的日志能正常显示出来。

Android hybridlr 实验

在 Android 下操作 hybridlr,流程基本上和 Windows 上一样,不过默认情况下,AssetBundles 是针对 Windows 来编译的,在为 Android 生成 apk 包的时候,需要重新针对 Android 来编译 AssetBundles,即在菜单 HybridCLR ==> Buil ==> BuildAssetsAndCopyToStreamingAssets 针对 Android 目标平台生成 AssetBundles,如果不这样做,会提示类似正面的错误:build target error.

步骤基本上和[[20220720180750#hybridclr试验]]里一样,不过,第7步的路径需要修改为:hybridclr_trial/Assets/StreamingAssets,把里面的文件全部拷贝(即热更新)到 Android 手机指定的目录。

原demo中,默认是从asset目录读取,不方便我们修改,将其替换成cache目录,在代码 LoadDll.cs 里,把正面这行代码改为 cache 目录:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    private string GetWebRequestPath(string asset)
    {
        //var path = $"{Application.streamingAssetsPath}/{asset}";
        var path = $"{Application.temporaryCachePath}/{asset}";
        if (!path.Contains("://"))
        {
            path = "file://" + path;
        }
        if (path.EndsWith(".dll"))
        {
            path += ".bytes";
        }
        return path;
    }

这样,你 build and run 这个 Unity Demo 的时候,就会看到下面的界面:

HybridCLR Demo load failed

这个是一个错误界面,是因为没有正确加载应的 dll 库。修正方法很简单,把代码目录下的 hybridclr_trial/Assets/StreamingAssets 所有的内容,一股脑拷贝到手机目录

1
内部存储/Android/data/com.Dev.HybridCLRTrial/cache

目录下,拷贝完后大致是这样一个情形:

Unity Android Hotupdate Path

现在杀掉手机进程,重新跑起来这个app,就能看到正常显示的界面,如下图所示:

HybridCLR Demo Load Succceed

现在到了在 Android 上测试热更新的时候了,把 HotUpdateMain.cs 修改为如下测试代码,并且在 Unity 编辑器里再次编译(通过菜单 HybridCLR ==> Buil ==> BuildAssetsAndCopyToStreamingAssets):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    void Start()
    {
        Debug.Log("这个热更新脚本挂载在prefab上,打包成ab。通过从ab中实例化prefab成功还原");
        Debug.LogFormat("hello, {0}.", text);

        gameObject.AddComponent<CreateByCode>();

        Debug.Log("=======看到此条日志代表你成功运行了示例项目的热更新代码=======");

        Debug.Log("=======看到此条日志代表你成功运行了示例项目的热更新代码,手动更新=======");

        //Debug.Log("=======看到此条日志代表你成功运行了示例项目的热更新代码,手动更新22222=======");

        Debug.Log("=======看到此条日志代表你成功运行了示例项目的热更新代码,手动更新333333333=======");

        Debug.Log("=======看到此条日志代表你成功运行了示例项目的热更新代码,手动更新444444444=======");

        Debug.Log("=======看到此条日志代表你成功运行了示例项目的热更新代码,手动更新555555555=======");
    }

再次杀掉进程重启,则显示如下运行界面,表明成功热更新:

HybridCLR Demo Hot Update Succceed

参考资料

  1. https://bbs.huaweicloud.com/blogs/370395,这个资料有点老了,但是算是简单快速入门的资料,可以一读,操作上略有不同。

  2. https://github.com/focus-creative-games/hybridclr_trial,把这个 readme 先读懂,基本上就地 HybridCLR 有了基本的认识。

  3. Android 这一节重点参考资料:Unity-huatuo热更新调研