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即可完全理解流程,这里不再赘述

相关推荐
魔士于安17 小时前
Unity资源Toon City Pack 发电厂 工地 公园 地铁站口 银行 车 直升飞机 可动 URP
游戏·unity·游戏引擎·贴图·模型
SmartRadio20 小时前
NRF52833 + MPU6050 室内定位跟随无人机
游戏引擎·无人机·cocos2d
心前阳光20 小时前
Unity之运行时标准材质半透明无效果
unity·游戏引擎·材质
张老师带你学1 天前
Unity buildin 石头围墙 树木 树墩子 卡通风格 栅栏 小桥 低多边形
科技·游戏·unity·游戏引擎·模型
呆呆敲代码的小Y1 天前
【Unity工具篇】| 使用YooAsset接入自己的游戏项目,实现完整热更新流程
游戏·unity·游戏引擎·热更新·yooasset·资源热更新
张老师带你学1 天前
Unity 低多边形 赛博朋克城市 拼装 模型 道路 建筑 buildin
科技·游戏·unity·游戏引擎·模型
PassionY1 天前
Unity NGO 系列教程(四):多人抓取的权限争夺
unity·xr·network·ngo·multiplayer·ownership·多人竞态权
ฅ^•ﻌ•^ฅ11 天前
Unity mcp并使用claude code制作游戏
游戏·unity·游戏引擎
程序员正茂1 天前
Unity3d使用SRDebugger屏幕输出调试信息
unity·srdebugger
张老师带你学1 天前
unity资源 buildin 低多边形 小镇村
科技·游戏·unity·游戏引擎·模型