[toc]

# 热更新总流程

首先先把需求拆出来

  1. 热更新时我们需要什么?
    • 第一个就是大版本包
      • 大版本包里面需要当前版本内所有的 Assetbundle (如果不需要压包), 当用户下载这个软件的时候,就会自动包含这些包
      • 大版本包也称基础包
    • 第二个就是更新包
      • 如果我们在后期对包内某个资源进行了更新,我们就需要对这个包进行更新
      • 即我们需要将这个包打出来,然后放到文件服务器上
      • 再通过文件服务器的更新资源和本地更新包目录内的的 crc 校验码进行对比来确定需要更新哪些东西
    • 为了让我们调用 ab 包内资源更贴近 Resource.Load
      • 所以我们需要记录每个资源在项目里的相对路径,哪怕他实际路径不在那里,也要映射起来

# 资源热更流程示意图

如上图所示,我们在最初的时候会打一个完整的基础包,我们记为 Assetbundle_Base 如果后面出现了更新包,(比如某个资源出错了,需要更新一下), 这时候我们就需要打更新包,记为 AssetBundle_Update 其中更新包里只会包含 当前需要更新的文件 ,配置文件里也只会有需要更新的 ab 列表。这就是资源热更的流程


# 资源拆分粒度

在猫老师的课程中,他把资源按照文件夹进行了拆分,总体大概如下: 即 GAsset 是资源存放的根目录,对于一个游戏而言,可能存在很多不同的模块,每个模块有自己的资源,这时候就需要把模块拆分开,所以就有了上面那个图 GAsset 作为资源总目录,它下面的每个子文件夹都是一个模块,我们对每个模块生成 这个模块的资源配置文件 然后对于模块内的 每个子文件夹都打包成一个ab包 ,按照路径进行 前缀式的组合 ,比如: GAsset/Launch/sprite/ , 就组合成 gasset_launch_sprite.assetbundle 这个 ab 包里包含 这个文件夹内所有的资源 ,但 不包含这个文件夹的子文件里的资源


# 资源配置文件结构

知道了上面的资源粒度拆分,我们就需要得到一个模块中 有哪些ab包 ,以及 每个资源对应的ab包和其依赖了哪些其他ab包以及它本身的路径 举个例子,如下所示:

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
50
51
52
53
{
"BundleArray": {
"gassets_launch": {
"bundle_name": "gassets_launch",
"crc": "96e20479",
"size": 3042,
"assets": [
"Assets/GAssets/Launch/PizzaCat.prefab",
"Assets/GAssets/Launch/Sphere.prefab"
]
},
"gassets_launch_mat": {
"bundle_name": "gassets_launch_mat",
"crc": "795e1328",
"size": 24607,
"assets": [
"Assets/GAssets/Launch/Mat/TestMaterial.mat"
]
},
"gassets_launch_sprite": {
"bundle_name": "gassets_launch_sprite",
"crc": "604364b8",
"size": 74946,
"assets": [
"Assets/GAssets/Launch/Sprite/header.jpg"
]
}
},
"AssetArray": [
{
"asset_path": "Assets/GAssets/Launch/PizzaCat.prefab",
"bundle_name": "gassets_launch",
"dependencies": []
},
{
"asset_path": "Assets/GAssets/Launch/Sphere.prefab",
"bundle_name": "gassets_launch",
"dependencies": [
"gassets_launch_mat"
]
},
{
"asset_path": "Assets/GAssets/Launch/Mat/TestMaterial.mat",
"bundle_name": "gassets_launch_mat",
"dependencies": []
},
{
"asset_path": "Assets/GAssets/Launch/Sprite/header.jpg",
"bundle_name": "gassets_launch_sprite",
"dependencies": []
}
]
}

其中分为 Bundle区域Asset区域 其中 Bundle区域 中记录每个 AssetBundle 里: - 包含哪些资源 - crc 校验码 - bundle 名字 - bundle 大小 Asset区域 记录每个资源: - 对应哪个父 Bundle - 依赖哪些 Bundle - 资源在原项目里的路径


# 运行时

# AB 包的加载 (包含在资源的加载中)

先把 Bundle 和 Asset 的信息加载到内存里: - 加载 Update_BundleInfo - 加载 Base_BundleInfo - 加载资源配置信息 然后运行时加载资源的时候,如下图所示

# AB 包的卸载

他这里暂时实现的方式是:在 Update 中检查 如果资源没有依赖的 GameObject 了,就从对应的 bundle 和依赖的 bundle 中移除这个资源,如果这个 bundle 的资源列表也为空,就卸载这个 bundle 但是一般来说要么用引用计数,LRU 之类的


# 实际实现 (慢慢填)

# Bundle 区域 BundleRef

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 选择 原始只读路径 还是 可读可写路径
/// </summary>
public enum BaseOrUpdate
{
/// <summary>
/// App安装时,生成的原始只读路径
/// </summary>
Base,
/// <summary>
/// App提供的 可读可写路径
/// </summary>
Update
}

/// <summary>
/// 一个Bundle数据【用于序列化为json文件】
/// </summary>
public class BundleInfo
{
/// <summary>
/// 这个bundle的名字
/// </summary>
public string bundle_name;

/// <summary>
/// 这个bundle资源的crc散列码
/// 用于对比资源
/// </summary>
public string crc;

/// <summary>
/// 这个bundle资源的大小 单位是字节
/// </summary>
public int size;

/// <summary>
/// 这个bundle所包含的资源的路径列表
/// </summary>
public List<string> assets;

}

/// <summary>
/// 内存中的一个Bundle对象
/// </summary>
public class BundleRef
{
/// <summary>
/// 这个bundle的静态配置信息
/// </summary>
public BundleInfo bundleInfo;

/// <summary>
/// 加载到内存中的bundle对象
/// </summary>
public AssetBundle bundle;

/// <summary>
/// 这些BundleRef对象被哪些AssetRef对象依赖
/// </summary>
public List<AssetRef> children;

/// <summary>
/// 记录这个BundleRef对应的AB文件需要从哪里加载
/// </summary>
public BaseOrUpdate witch;

/// <summary>
/// BundleRef的构造函数
/// </summary>
/// <param name="bundleInfo"></param>
public BundleRef(BundleInfo bundleInfo,BaseOrUpdate wicth_)
{
this.bundleInfo = bundleInfo;

this.witch = wicth_;
}
}

主要就是按照上面的需求中那样,包含了静态配置信息,以及运行时管理这个 Bundle 所需要的 Bundle引用信息 BaseOrUpdate 表示的是:当前这个 bundle 是更新的还是基本包

# Asset 区域 AssetRef

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 一个Asset数据【用于序列化为json文件】
/// </summary>
public class AssetInfo
{
/// <summary>
/// 这个资源的相对路径
/// </summary>
public string asset_path;

/// <summary>
/// 这个资源所属的AssetBundle的名字
/// </summary>
public string bundle_name;

/// <summary>
/// 这个资源所依赖的AssetBundle列表的名字
/// </summary>
public List<string> dependencies;
}

/// <summary>
/// 内存中单个资源对象
/// </summary>
public class AssetRef
{
/// <summary>
/// 这个资源的配置信息
/// </summary>
public AssetInfo assetInfo;

/// <summary>
/// 这个资源所属的BundleRef对象
/// </summary>
public BundleRef bundleRef;

/// <summary>
/// 这个资源所依赖的BundleRef对象列表
/// </summary>
public BundleRef[] dependencies;

/// <summary>
/// 从bundle文件中提取出来的资源对象
/// </summary>
public Object asset;

/// <summary>
/// 这个资源是否是prefab
/// </summary>
public bool isGameObject;

/// <summary>
/// 这个AssetRef对象被哪些GameObject所依赖
/// </summary>
public List<GameObject> children;

/// <summary>
/// AssetRef对象的构造函数
/// </summary>
/// <param name="assetInfo"></param>
public AssetRef(AssetInfo assetInfo)
{
this.assetInfo = assetInfo;
}
}
更新于

请我喝[茶]~( ̄▽ ̄)~*

Solvarg 微信支付

微信支付

Solvarg 支付宝

支付宝

Solvarg 贝宝

贝宝