Unity热更新基础

前言

热更新指的是将在不让用户重新下载游戏的前提下对游戏中的资源(比如贴图,预制体,代码等)进行更新,本文中主要使用到Addressable

原理

Addressable在打包时主要分成两种,Local与Remote,Local是基础本地包,不可被更新的基础包,而Remote包则是放在服务器上供玩家下载的远程包,在这里存放需要热更新的资源或者附加资源,Addressable的热更本质是资源替换,不是增量更新。每次Build会生成新的Catalog和bundle文件,客户端下载完整的替换旧文件,不是像Git那样只下载差异部分。

准备工作

用PackManager下载Addressable包,在Windows中找到如下面板

其中Groups是关键,打开后大概是这个面板

可以点击左上角的New创建新的分组,每一个分组可以在检查器中选择是Local组还是Remote组。还有设置Remote组的存放地址等。将资源放入对应组最简单的方式是将其拖入对应分组,还有一种方式是利用利用AssetDatabase类将指定文件夹下的指定文件打包到同一组。一般分组情况如下:

  1. Local_Base:基础资源,永不改变
  2. Remote_Level:关卡资源,DLC地图等
  3. Remote_Audio:音频资源和图片等需要大量修改的
  4. Remote_Others:不常更新的资源

注意观察该面板中的一些属性,比如第一栏AddressableName,即对应文件的字符串键,在使用该资源可以通过该字符串加载对应资源,还有最后一栏Labels,类似Unity中的Tag,将统一类的资源分到一组,用于统一下载,比如将第一关的敌人都加上同一个Label,在进入关卡前就下载对应资源即可

注意资源需要打包后才能使用,右上角Build即是打包按钮,可以选择Update(小规模改动)和New Build(整个重新打包),打包后一般可以查看每一个包的大小等属性

使用包中资源

一般使用资源,有两种常见用法,使用拖引用的方式,使用字符串加载

加载资源需要用到异步加载的方式,当使用Addressables.LoadAssetAsync时,该方法会返回一个句柄,当不需要用到该资源,除了卸载该资源,还应该调用Addressables.Release方法释放该句柄,否则会导致引用计数异常。而使用InstantiateAsync的方法创建预制体时,则它会帮助创建预制体,至于拖引用的方式,则是用AssetReference类的LoadAssetAsync方式加载,本质上还是需要释放句柄。

复制代码
 public class AddressableLoader : MonoBehaviour
 {
   
     public async Task LoadAsset<T>(string key) where T:Object
     {
         AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(key);
         await handle.Task;
         if(handle.Status==AsyncOperationStatus.Succeeded)
         {
             T asset= handle.Result;
             //用Load就要Release,不需要用到该资源时就Release,这里只是演示
             Addressables.Release(handle);
         }
     }
    
     public async Task LoadScene(string sceneKey)
     {
         var handle = Addressables.LoadSceneAsync(sceneKey,
             UnityEngine.SceneManagement.LoadSceneMode.Single);

         // 进度监控
         while (!handle.IsDone)
         {
             float progress = handle.PercentComplete;
             Debug.Log($"Loading: {progress:P}");
             await Task.Yield();
         }

         if (handle.Status == AsyncOperationStatus.Succeeded)
         {
             Debug.Log("Scene loaded!");
         }
     }
     public async Task InstantiatePrefab(string key, Vector3 position)
     {
         // 自动处理: 加载 + 实例化 + 依赖管理
         var handle = Addressables.InstantiateAsync(key, position, Quaternion.identity);
         GameObject instance = await handle.Task;

         // InstantiateAsync内部会引用计数+1,创建的GameObject销毁时引用计数-1,但handle本身需要手动Release。
     }
	//提前下载对应Label的资源,同样需要释放句柄
     public async Task PreloadDependencies(string label)
     {
         // 获取所有依赖,即标签
         var dependencies = Addressables.GetDownloadSizeAsync(label);
         long size = await dependencies.Task;

         if (size > 0)
         {
             Debug.Log($"Need download: {size / 1024 / 1024}MB");

             // 下载并加载到内存
             var downloadHandle = Addressables.DownloadDependenciesAsync(label);
             await downloadHandle.Task;

             Addressables.Release(downloadHandle);
         }
     }
 }

下载远程资源

每次打完远程包并添加到服务器上时,都会导致Catalog发生变化,而Addressable会检查配置是否发生变化,如果是,就可以尝试下载新的Remote包

复制代码
async Task<bool> CheckAndUpdate()
{
    try
    {
        // 1. 初始化
        var init = await Addressables.InitializeAsync().Task;

        // 2. 检查目录更新
        var checkHandle = Addressables.CheckForCatalogUpdates(false);
        var catalogs = await checkHandle.Task;

        if (catalogs != null && catalogs.Count > 0)
        {
            // 3. 更新目录
          
            await Addressables.UpdateCatalogs(catalogs).Task;

            // 4. 计算下载大小
            var sizeHandle = Addressables.GetDownloadSizeAsync(catalogs);
            long size = await sizeHandle.Task;
            Addressables.Release(sizeHandle);

            if (size > 0)
            {
                float mb = size / 1024f / 1024f;
                statusText.text = $"需要下载 {mb:F1}MB";

                // 5. 下载资源(带进度)
                var downloadHandle = Addressables.DownloadDependenciesAsync(catalogs);

                while (!downloadHandle.IsDone)
                {
                    progressBar.value = downloadHandle.PercentComplete;
                    statusText.text = $"下载中... {downloadHandle.PercentComplete:P0}";
                    await Task.Yield();
                }

                await downloadHandle.Task;
                Addressables.Release(downloadHandle);
            }
        }
        return true;
    }
    catch (Exception e)
    {
        Debug.LogError($"更新失败: {e}");
        return false;
    }
}

HybridCLR

Addressables本身并不支持将c#脚本打包,我们可以用到HybridCLR让C#代码能被当作资源下载执行。核心是将热更代码编译成DLL,运行时通过Assembly.Load加载

复制代码
// 下载DLL(Addressable)
var handle = Addressables.LoadAssetAsync<TextAsset>("HotUpdate.dll");
TextAsset dllAsset = await handle.Task;

// 加载执行(HybridCLR)
Assembly hotAssembly = Assembly.Load(dllAsset.bytes);
Type entryType = hotAssembly.GetType("HotUpdate.Main");
entryType.GetMethod("Start").Invoke(null, null);

详细内容跟随HybridCLR的Demo即可完全理解流程,这里不再赘述

相关推荐
爱搞虚幻的阿恺7 天前
Niagara粒子系统-超炫酷的闪电特效(加餐 纸牌螺旋上升效果)
游戏·游戏引擎
_Li.7 天前
Simulink - 6DOF (Euler Angles)
人工智能·算法·机器学习·游戏引擎·cocos2d
weixin_424294677 天前
Unity 调用Steamworks API 的 SteamUserStats.RequestCurrentStats()报错
unity·游戏引擎·steamwork
HoFunGames7 天前
Unity小地图,Easy Minimap System MT-GPS插件
unity·游戏引擎
wy3258643647 天前
Unity 新输入系统InputSystem(基本操作)
unity·c#·游戏引擎
WarPigs7 天前
着色器multi_compile笔记
unity·着色器
ECHO飞跃 0127 天前
Unity2019 本地推理 通义千问0.5-1.5B微调导入
人工智能·深度学习·unity·llama
Unity游戏资源学习屋7 天前
【Unity UI资源包】GUI Pro - Casual Game 专为休闲手游打造的专业级UI资源包
ui·unity
冰凌糕7 天前
Unity3D Shader 顶点法线外扩实现描边效果
unity
星和月7 天前
Untiy使用说明
c#·游戏引擎