AB包梗概

是什么?

类似与平台的资产压缩包,将资产如模型,贴图,预设体材质球等打包。

作用?

通常unity加载资源时,我们使用Resources文件夹,但缺点很多

  1. 打包后即定死,只读,不允许修改,也就不支持动态更新。
  2. 打包后文件较大,占用空间。

相对Resources,AB可以更好的管理资源

  • 存储位置可自定义
  • 压缩方式也可自定义,减少包体大小
  • 支持热更新(资源热更新和脚本热更新)

AB包热更新流程

  • 第一步:连接服务器获取资源服务器地址
  • 第二步:从资源服务器端(存储着各种AB包)下载资源对比文件
  • 第三步:客户端通过资源对比文件检测哪些要更新,哪些要下载,然后下载更新AB包。

AB包的窗口配置

Unity官方提供了打包工具

Asset Bundle Browser

高版本upm内提供了Addressables,其内部封装好了Asset Bundle Browser。也可以去Github下载AB Browser文件导入即可。

在Window内打开ABB,可以看到如图

AB包

我们看到了三个页签

  • Configure 配置页
  • Build 打包页
  • Inspect 检视页

(翻译了一遍。。。。)

Configure

直接pass!

Build

参数说明

  • Build Target:面对的打包平台

  • Output Path: 包体输出路径

  • Clear Folder : 每次打包时先清空,若勾选Copy to StreamingAssets,则复制包体也会清空

    好处:会清楚掉你忘记删的多余文件

    坏处:包体较大时,打包时间变长

  • Copy to StreamingAssets:打包时复制包体到StreamingAssets文件中。

  • Compression :压缩方式

    • No Compression

      不压缩,解压快但包体大,不推荐

    • Standard Compression(LZMA)

      极致压缩,包体压缩贼小,但all for one,想用某个资源就得解压所有资源

    • Chunk Based Compression(LZ4)

      压缩程度低于第二种,但用什么解压什么,内存占用小。推荐哦!

其他的,咱们把鼠标放上去就知道什么意思了。

打包后生成的文件说明

打包后每个资产都会生成了一对文件

  • AB包文件 二进制文件,记录者对应资产信息

  • manifest文件 记录着AB包文件信息,提供了资源信息,依赖关系,版本信息等

    简单看一下manifest文件信息

    Manifest文件

    1~10行:记录着版本,CRC码,哈希码等关键信息

    11~36行:记录着引用的脚本等脚本信息

    注意:C#代码无法被打包进去,这就是为什么我们需要热更新语言。

    37~39行:资源路径

    40:依赖项

生成的文件中有一对文件名和目录相同,称为主包文件,记录着所有AB包依赖关系。

Inspect

直接pass!

使用AB包

这里只说我用过的

和使用Resources API异曲同工,注意类名即可

  • 同步加载资源

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //加载AB包
    AssetBundle a = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/cube");
    //加载资源(泛型)
    GameObject g = a.LoadAsset<GameObject>("cube");
    Instantiate(g);
    //加载资源非泛型
    GameObject c = a.LoadAsset("cube", typeof(GameObject)) as GameObject;
    Instantiate(c);

    建议使用非泛型方法,因为为Lua并不支持泛型
    此外若重复加载AB包会报错
  • 异步加载(协程方式)

    1
    2
    3
    4
    5
    6
    7
    8
    IEnumerator LoadGameObjAsync(string ABpath,string name)
    {
    AssetBundleCreateRequest ab= AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/"+ABpath);
    yield return ab;
    AssetBundleRequest abc= ab.assetBundle.LoadAssetAsync(name, typeof(GameObject));
    yield return abc;
    Instantiate(abc.asset as GameObject);
    }
  • 异步加载(非协程)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/cube");
    AssetBundleRequest ff = null;
    request.completed += (f) =>
    {
    ff = request.assetBundle.LoadAssetAsync("cube");
    ff.completed += (f) =>
    {
    Instantiate(ff.asset as GameObject);
    };
    };

    异步加载用协程的方式更加明了,感觉。

  • 卸载资源

    1
    2
    3
    4
    5
    6
    //卸载所有AB包,若参数为True,则已经加载的资源也会被卸载,若False,则保留,(好像:保留的资源与原AB包的联系将会切断?)
    AssetBundle.UnloadAllAssetBundles(bool);

    //卸载单个AB包,参数同上
    AssetBundle a = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/cube");
    a.Unload(false);

AB包的依赖关系

如果一个或者多个 UnityEngine.Objects 引用了其他 AssetBundle 中的 UnityEngine.Object,那么 AssetBundle 之间就产生的依赖关系。相反,如果 UnityEngine.ObjectA 所引用的UnityEngine.ObjectB 不是其他 AssetBundle 中的,那么依赖就不会产生。

所以当我们加载一个资源却没有加载其依赖项的AB包时,加载的资源将会缺失其依赖资源,在unity内试试就知道啦

所以就得加载依赖项的AB包,但如果一个资源有多个AB包依赖,一个一个加载很繁琐,这时候就可以用到主包了,它记录着所有依赖关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
AssetBundle a = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/cube");
//加载主包文件
AssetBundle main = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/StandaloneWindows");
//加载主包manifest文件,固定名字为"AssetBundleManifest"
AssetBundleManifest manifest = main.LoadAsset("AssetBundleManifest", typeof(AssetBundleManifest)) as AssetBundleManifest;
//获取对应包文件的依赖包名字
string[] strs = manifest.GetAllDependencies(a.name);
foreach(var k in strs)
{
AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/"+k);
}

GameObject g = a.LoadAsset<GameObject>("cube");
Instantiate(g);

这样写有个缺点:

一个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
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
using System.Collections.Generic;
using UnityEngine;
using Cysharp.Threading.Tasks;
using UnityEngine.Events;

public class ABMgr : Singleton<ABMgr>
{
private AssetBundle main;
private AssetBundleManifest ABmanifest;
public Dictionary<string, AssetBundle> ABBundles ;
public ABMgr()
{
ABBundles = new Dictionary<string, AssetBundle>();
main = AssetBundle.LoadFromFile(PATH + MAiN_AB_NAME);
ABmanifest=main.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}

//主包路径
public string MAiN_AB_NAME
{
get { return "StandaloneWindows"; }
}
//加载路径
public string PATH
{
get { return Application.streamingAssetsPath + "/"; }
}
public AssetBundle Main
{
get { return main; }
}
public AssetBundleManifest ABManifest
{
get { return ABmanifest; }
}


/// <summary>
/// 卸载AB包
/// </summary>
/// <param name="name"></param>
/// <param name="a"></param>
/// <exception cref="System.Exception"></exception>
public void UnloadAB(string name, bool a)
{
if (!ABBundles.ContainsKey(name)) throw new System.Exception("无指定包");
ABBundles[name].Unload(a);
ABBundles.Remove(name);
}

/// <summary>
/// 卸载所有包
/// </summary>
/// <param name="a"></param>
public void UnloadAllAB(bool a)
{
AssetBundle.UnloadAllAssetBundles(a);
ABBundles.Clear();
}

/// <summary>
/// 重新加载主包和依赖包
/// </summary>
public void ReloadNecessaryBundle()
{
if(main==null)
main = AssetBundle.LoadFromFile(PATH + MAiN_AB_NAME);
if(ABmanifest==null)
ABmanifest = Main.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}

/// <summary>
/// 加载AB包
/// </summary>
/// <param name="ABName">包名</param>
/// <returns></returns>
/// <exception cref="System.Exception">加载失败</exception>
public AssetBundle LoadAB(string ABName)
{
AssetBundle bundle = null;
if(!ABBundles.ContainsKey(ABName))
{
bundle = AssetBundle.LoadFromFile(PATH + ABName);
ABBundles.Add(ABName, bundle);
}
else
bundle = ABBundles[ABName];
string[] strs=ABManifest.GetAllDependencies(bundle.name);
foreach(var a in strs)
{
if(!ABBundles.ContainsKey(a))
{
AssetBundle b = AssetBundle.LoadFromFile(PATH + ABName);
ABBundles.Add(a, b);
}
}
if (bundle == null)
throw new System.Exception("AB包加载失败");
return bundle;

}

/// <summary>
/// 指定类型同步加载资源
/// </summary>
/// <param name="ABName">包名</param>
/// <param name="assetName">资源名</param>
/// <param name="type">类型</param>
/// <returns>返回Object</returns>
public Object loadAsset(string ABName,string assetName,System.Type type)
{
LoadAB(ABName);
return ABBundles[ABName].LoadAsset(assetName,type);
}

/// <summary>
/// 泛型加载资源
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="ABName">包名</param>
/// <param name="assetName">资源名</param>
/// <returns></returns>
public T loadAsset<T>(string ABName, string assetName) where T : Object
{
LoadAB(ABName);
return ABBundles[ABName].LoadAsset<T>(assetName);
}

/// <summary>
/// 异步指定类型加载资源,加载完成后执行委托
/// </summary>
/// <param name="ABName">包名</param>
/// <param name="assetName">资源名</param>
/// <param name="type">类型</param>
/// <param name="action">加载完成后要执行的委托</param>
/// <returns></returns>
public Object LoadAssetAsync(string ABName,string assetName,System.Type type,System.Action<AsyncOperation> action = null)
{
LoadAB(ABName);
AssetBundleRequest assetBundleRequest= ABBundles[ABName].LoadAssetAsync(assetName,type);
assetBundleRequest.completed += action;
return assetBundleRequest.asset;
}

/// <summary>
/// 异步泛型加载资源,加载完成后执行委托
/// </summary>
/// <typeparam name="T">泛型参数</typeparam>
/// <param name="ABName">包名</param>
/// <param name="assetName">资源名</param>
/// <param name="action">加载完成后要执行的委托</param>
/// <returns></returns>
public T LoadAssetAsync<T>(string ABName, string assetName,System.Action<AsyncOperation> action=null) where T : Object
{
LoadAB(ABName);
AssetBundleRequest assetBundleRequest = ABBundles[ABName].LoadAssetAsync<T>(assetName);
assetBundleRequest.completed += action;
return assetBundleRequest.asset as T;
}
/// <summary>
/// 基于Unitask,指定类型加载资源,加载完成后执行委托
/// </summary>
/// <param name="ABName">包名</param>
/// <param name="assetName">资源名</param>
/// <param name="type">类型</param>
/// <param name="action">委托函数</param>
/// <returns></returns>
public async UniTask<Object> LoadAssetAsyncByUnitask(string ABName, string assetName, System.Type type, UnityAction<Object> action = null)
{
LoadAB(ABName);
Object obj= await ABBundles[ABName].LoadAssetAsync(assetName, type);
action(obj);
return obj ;
}

/// <summary>
/// 基于Unitask,泛型加载资源,加载完成后执行委托
/// </summary>
/// <typeparam name="T">泛型参数</typeparam>
/// <param name="ABName">包名</param>
/// <param name="assetName">资源名</param>
/// <param name="action">委托</param>
/// <returns></returns>
public async UniTask<T> LoadAssetAsyncByUnitask<T>(string ABName, string assetName, UnityAction<T> action=null) where T:Object
{
LoadAB(ABName);
T obj = await ABBundles[ABName].LoadAssetAsync<T>(assetName) as T ;
action(obj);
return obj ;

}
}

简单测试如下:

1
2
3
4
5
6
7
8
async void Start()
{
await ABMgr.Instance.LoadAssetAsyncByUnitask<GameObject>("cube", "cube", (a) =>
{
Instantiate(a);
});
GameObject A = ABMgr.Instance.loadAsset("cube", "cube", typeof(GameObject)) as GameObject;
Instantiate(A);

abbundle

正常

vs算法 a性 项目

完。。。。。QQ截图20201118223557