元某人也元某人也

热更新

Unity 热更新方案与资源更新流程笔记。

本文目录

热更新


热更新是什么

类型更新内容典型技术
资源热更贴图、模型、配置、UI 预制体等AssetBundle、Addressables
代码热更C# 业务逻辑(不通过商店整包审核)HybridCLR、ILRuntime、xlua 等

目的:修 Bug、发活动资源、降首包体积、分平台差异化内容。

限制:无法热更 Unity 引擎本身、原生插件(需走商店更新或单独下载 so/dll)。


常见方案对比

方案资源代码特点
AssetBundle✅❌(需配合热更框架)底层、可控,自建版本与 CDN
Addressables✅❌基于 AB,官方封装分组/远程/引用计数
HybridCLR可配合 AB✅原生解释执行 IL,性能较好,国内常用
ILRuntime可配合 AB✅解释执行 DLL,成熟
XLua / tolua可配合 AB✅(Lua)脚本语言热更,与 C# 互调
  flowchart LR
    A[首包安装] --> B{需要更新?}
    B -->|资源| C[下载 AB / Addressables]
    B -->|代码| D[下载 DLL / Lua]
    C --> E[加载进内存]
    D --> E
    E --> F[运行新逻辑/新资源]

AssetBundleManifest

当项目有多个 AB 包(如 texture.ab 依赖 material.ab)时,打主包会生成 AssetBundleManifest(常随主包名,如 platform 或自定义主包名),用于:

作用说明
包列表所有 AB 名称、Hash(版本对比、增量更新)
依赖关系加载某 AB 前自动加载其依赖包
灵活路径AB 可放 StreamingAssets、持久化目录、CDN
压缩减小包体,按需解压

有依赖时必须先加载 Manifest 所在的主包,再通过 AssetBundleManifest API 查依赖。


AssetBundle 打包选项

(Unity AssetBundle Browser 或自定义构建脚本中的常见项)

选项说明
Build Target目标平台;各平台需分别打包
Output Path输出目录
Clear Folders打包前是否清空输出文件夹
Copy To StreamingAssets是否复制到 StreamingAssets(首包内置资源)
Compression见下表
ETI(Exclude Type Information)包内不含类型信息(更小,加载略受限)
FR(Force Rebuild)强制全量重建;与 Clear 不同:不删已不存在的旧包文件
ITTC(Ignore Type Tree Changes)增量构建时忽略 Type Tree 变化
Append Hash哈希附加到 AB 文件名(利于缓存与热更对比)
Strict Mode出错则构建失败
Dry Run Build只模拟构建,不真正写文件

压缩方式

方式特点
No Compression不压缩,包大,加载快
LZMA体积最小;整包解压才好用单个资源,首包解压慢
LZ4可按块解压,边用边解压,内存友好;热更常用

打包产物说明

文件说明
主包名称常与输出目录/平台主包一致;内含 AssetBundleManifest,记录依赖与 Hash
各 AB 文件实际资源数据(.ab 或自定义后缀)
.manifest 文本单个 AB 的元信息(依赖、Hash、类型等),调试用;运行时主要靠二进制 Manifest

示例结构:

1
2
3
4
5
6
Output/
├── StandaloneWindows64          ← 主包(含 Manifest)
├── ui_panel.ab
├── ui_panel.ab.manifest
├── textures.ab
└── textures.ab.manifest

AB 加载流程与代码

流程

  sequenceDiagram
    participant App
    participant MainAB as 主包/Manifest
    participant Dep as 依赖AB
    participant Res as 目标AB
    App->>MainAB: LoadFromFile(主包路径)
    App->>MainAB: GetManifest()
    App->>MainAB: GetAllDependencies(目标包名)
    loop 每个依赖
        App->>Dep: LoadFromFile
    end
    App->>Res: LoadFromFile / LoadAsset
    App->>App: Instantiate / 使用资源

示例代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using System.IO;
using UnityEngine;

public class ABLoader : MonoBehaviour
{
    // 主包路径(热更后一般在 persistentDataPath)
    string abRoot = Path.Combine(Application.persistentDataPath, "AssetBundles");
    string mainAbName = "StandaloneWindows64"; // 与打包主包名一致

    AssetBundle mainBundle;
    AssetBundleManifest manifest;

    void LoadManifest()
    {
        string mainPath = Path.Combine(abRoot, mainAbName);
        mainBundle = AssetBundle.LoadFromFile(mainPath);
        manifest = mainBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
    }

    public GameObject LoadPrefab(string bundleName, string assetName)
    {
        if (manifest == null) LoadManifest();

        // 1. 先加载所有依赖
        string[] deps = manifest.GetAllDependencies(bundleName);
        foreach (var dep in deps)
        {
            string depPath = Path.Combine(abRoot, dep);
            if (!IsLoaded(dep))
                AssetBundle.LoadFromFile(depPath);
        }

        // 2. 加载目标包
        string bundlePath = Path.Combine(abRoot, bundleName);
        AssetBundle bundle = AssetBundle.LoadFromFile(bundlePath);

        // 3. 加载资源
        GameObject prefab = bundle.LoadAsset<GameObject>(assetName);
        return Instantiate(prefab);
    }

    bool IsLoaded(string bundleName) { /* 维护已加载字典 */ return false; }

    void OnDestroy()
    {
        mainBundle?.Unload(true);
        // 其他 bundle 按引用计数 Unload(false/true)
    }
}

异步加载(推荐)

1
2
3
4
5
6
7
var req = AssetBundle.LoadFromFileAsync(path);
yield return req;
AssetBundle bundle = req.assetBundle;

var assetReq = bundle.LoadAssetAsync<GameObject>("MyPrefab");
yield return assetReq;
GameObject go = Instantiate(assetReq.asset as GameObject);

Unload 注意

调用效果
Unload(false)卸载 AB 文件,已实例化对象仍可用(材质可能丢引用)
Unload(true)连已加载出的资源一并卸载

有依赖时按加载逆序卸载,或使用统一 AB 管理器做引用计数。


版本与更新流程

典型热更流程

  1. 首包:内置少量 AB 或 StreamingAssets + 版本号 version.json
  2. 启动:请求 CDN 上 version.json / 资源清单(文件名 + Hash + 大小)
  3. 对比:本地 Hash vs 远程 Hash,得到需下载列表
  4. 下载:差量或全量 AB 到 persistentDataPath
  5. 加载:从持久化路径 LoadFromFile,不走 StreamingAssets
  6. 校验:MD5/CRC 防篡改(可选)

version.json 示例

1
2
3
4
5
6
7
{
  "appVersion": "1.0.0",
  "resVersion": "2026051901",
  "bundles": [
    { "name": "ui_panel.ab", "hash": "a1b2c3...", "size": 1024000 }
  ]
}
目录用途
StreamingAssets首包只读,安装后不可写
persistentDataPath热更下载目录,可读写

Addressables 简述

在 AssetBundle 之上的资源管理系统:

概念说明
Group资源分组,决定打哪些 AB
Address用地址字符串加载,不必记 AB 名
Remote / Local内置与远程 CDN 路径
Catalog资源目录,类似 Manifest + 版本
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

async void Load()
{
    AsyncOperationHandle<GameObject> handle =
        Addressables.LoadAssetAsync<GameObject>("MyPrefab");
    await handle.Task;
    Instantiate(handle.Result);
}

适合新项目;老项目大量 AB 可逐步迁移。


常见坑与注意点

问题原因 / 处理
材质变粉AB 里 Shader 丢失;Shader 放 Always Included 或进 AB
重复加载依赖未先 GetAllDependencies 或未缓存已加载 AB
内存涨未 Unload;重复 LoadFromFile 同一包
平台加载失败打错 Build Target;路径大小写
LZMA 卡顿首包解压慢;热更资源改 LZ4

要点速查

主题要点
Manifest主包、依赖、Hash
压缩LZMA 小但整包解压;LZ4 热更常用
加载顺序主包 → Manifest → 依赖 AB → 目标 AB → LoadAsset
路径热更后用 persistentDataPath
卸载Unload(false/true) 区别
代码热更HybridCLR / ILRuntime / Lua;与 AB 配合
仅用于交流学习,记录学习心得