什么是AB包,它的主要作用是什么?

AB包全名AssetBundle(资源包)。是一种Unity提供的用于存放资源的包。通过将资源分布在不同的AB包中可以最大程度地减少运行时的内存压力,并且可以有选择地加载内容。
(直白点讲就是对内存有优化)。ab包主要用来热更新(你问什么是热更?这个我们一会再说,往下看)。相比于Resources加载,ab包加载对于内存的负担更小,而且更加快速。

unity资源加载的俩种方式

在Unity中,一般来说,资源加载方式主要分为Resources加载和AssetBundle加载。
Unity有个特殊文件夹Resources,放在这个文件夹下的资源可以通过Resources.Load()来直接加载。即Resources加载资源方式。
当获得AssetBundle之后,也可以调用AssetBundle对应的API来加载资源。

热更新

游戏或者软件更新时,无需重新下周客户端进行安装,而是在应用程序启动的情况下,在内部进行的资源或者代码更新。说白点你甚至可以在玩家游玩的时候修改一些代码或者美术资源。
等玩家下次更新以后就会完全变成另外一款游戏。

ab包的安装

这个就不细说了。

ab包的应用

重点!!!! c#代码是无法被打入ab包的,所以我们要用lua语言。

ab包打包常用的选项

Build Target:构建的平台目标(IOS,Android,windows)
Output Path:输出路径
Clear Floders:是否清空文件夹,一般会选择,即在多次打包时,将原文件夹清空(即原来所有的包清空重新构建),但是资源很大时会很耗时
Copy to StreamingAssets:将打包的AB包从Output Path复制到特数的StreamingAssets文件夹(与备份不同,在某些平台是只读文件夹,在PC是可读可写)
Compression:压缩方式
{
No Compression:不压缩,解压快,但包很大,不推荐使用
LZMA:压缩最小,但解压很慢,缺点是如果只需要AB包中的一个资源,会将包中的所有资源解压
LZ4:压缩率没有LZMA大,但是可以单独解压一个资源,内存占用低(建议使用)
}
打包之后:

分两个文件:.manifest

(1)关键AB包(和目录名一样的包):主包,存储着包与包之间的依赖关键关系
(2)(没有后缀名的文件):资源文件
(3)
.manifest:AB包文件信息,对应资源文件相关的关键配置信息。当加载时提供了资源信息,依赖关系,版本信息等关键信息。
其他:(了解即可)

ETI:在资源包中,不包含资源的类型信息

FR:重新打包时需要重新构建包,和ClearFolder类似,不同的是FR不会删除不再存在的包(即在打包的时候删除了某个包,但是原文件夹中的包不会被清除,浪费存储空间)

ITC:增量构建检查时,忽略类型数的更改

Append Hash:将文件哈希值附加到资源包名上(几乎不用)

SM:严格模式,如果打包时报错了,打包直接失败无法成功

DRB:运行时构建

Inspector界面:主要用于观测包的相关信息(大小,路径等)

如何使用ab包加载资源和ab包常用的api

加载AB包

1
2
// 从StreamAssets读取有专门的API
AssetBundle ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + 包名(无后缀));

加载AB包中的资源

1
ab.LoadAsset(“资源名字")

有三个重载:资源名,Type指定类型,泛型

注意:只是用名字加载,会出现同名不同类型资源分不清,因此不建议使用,且:同一个AB包不能够重复加载,否则报错

eg:

1
2
3
4
// 同步加载
GameObject obj = ab.LoadAsset<GameObject>("Cube");
GameObject obj = ab.LoadAsset("Cube", typeof(GameObject)) as GameObject;(通过Lua在C#代码加载对象时只能通过类型,因为Lua不支持泛型)
Instantiate(obj);
1
2
3
//异步加载——>协程
public GameObject obj;
StartCoroutine(LoadABRes("modle", "Cube"))
1
2
3
4
5
6
7
8
9
10

IEnumerator LoadABRes(string ABName, string resName) {
// 第一步:加载AB包
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(Application.streamingAssetsPath + "/" + ABName);
yield return abcr;
// 第二步:加载资源
AssetBundleRequest abq = abcr.assetBundle.LoadAssetAsync(resName, typeof(GameObject));
yield return abq;
obj = abq.asset as GameObject;
}
1
2
3

// 为了解除所有AB包的绑定,可以使用以下API
AssetBundle.UnloadAllAssetBundles();

如果参数为true,那么解绑定包时会将场景中通过AB包加载的资源一起卸载,false时只对AB包解绑

1
2
// 解绑单个AB包
ab.Unload()

参数true/false的意义与UnloadAllAssetBundles()相同

AB包的依赖性

在包中的一个资源A如果使用了另一个资源B,那么会自动的将B放在同一个包中,但是如果将B的AssetBundle选择另一个包,那么B会打包到另一个包中,此时如果加载包中的A并创建对象,那么A组件中与B相关的内容会丢失,除非此时也将另一个包中的B进行加载,即:

加载AB包中的资源需要将资源的依赖包一起加载才能正常显示

步骤:

(1)加载AB包1号(假设此包中的资源对2号包中的资源有依赖)

(2)加载AB包2号

(3)加载1号包中的资源

但是实际要完成以上操作,我们需要提前知道哪些包之间存在依赖关系,因此需要利用主包获取依赖信息。

步骤:假设寻找包”model“的依赖包
(1)加载主包,假设主包名为Main(打包后主包名和路径名一样)

1
AssetBundle abMain = AssetBundle.LoadFromFIle(Application.streamingAssetsPath + "/" + ”Main");

(2)加载主包中的固定文件

1
AssetBundleManifest abManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");

(3)从固定文件中,得到依赖信息,返回的即是依赖包的名字

1
2
string[] strs = abManifest.GetAllDependecies(”model");

(4)加载相关依赖包

1
2
3
4
List<AssetBundle> dependecies = new LIst<AssetBundle>();
for(int = 0; i < strs.Length; ++i) {
dependecies.Add( AssetBundle.LoadFromFIle(Application.streamingAssetsPath + "/" + strs[i]));
}

注意,只能知道包与包之间的依赖关系,不能具体知道包中的资源的具体依赖关系

AB包资源加载管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 继承这种自动创建的单例模式基类不需要我们手动去拖动或者API去添加
// 使用时直接GetInstance即可
public class SingletonAutoMono<T> : MonoBehaviour where T:MonoBehaviour {
private static T instance;

public static T GetInstance(){
if (istance == null) {
GameObject obj = new GameObject();
// 设置对象名字为脚本名
obj.name = typeof(T).ToString();
// 让这个单例模式对象过场景不移除,因为单例模式对象往往是存在于
// 整个程序生命周期的
DontDestroyOnLoad(obj);
instance = obj.AddComponent<T>();
}
return instance;
}
}
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
public class ABMgr : SingletonAutoMono<ABMgr> {
// AB包管理器目的是:让外部更方便地进行资源加载
// AB包不能重复加载,重复加载会报错,其中的资源不限制
// 用字典来存储加载过的AB包
private Dictionary<string, AssetBundle> abDic = new Dictionary<string, AssetBundle> ();
// 主包和相关配置文件只需要加载一次即可,因此声明相关变量
// 主包
private AssetBundle mainAB = null;
// 依赖包获取用的配置文件
private AssetBundleManifest manifest = null;

private ABMgr() { }

// AB包存放路径,方便修改
private string PathUrl {
get { return Application.streamingAssetsPath + "/"; /* 假设是该路径 */ }
}

// 主包名,方便修改
private string MainABName {
get {
#if UNITY_IOS
return "IOS";
#elif UNITY_ANDROID
return "Andriod";
#else
return "PC";
#endif
}
}

// 确保了每个包都只加载了一次
public void LoadAB(string abName) {
// 加载AB主包和其中的关键配置文件
if (mainAB == null) {
mainAB = AssetBundle.LoadFromFile(PahtUrl + MainABName);
manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}

// 获取依赖包的信息
AssetBundle ab = null
string[] strs = manifest.GetAllDependencies(abName);

for(int i = 0; i < strs.Length; ++i) {
if (!abDic.ContainsKey(strs[i])) {
ab = AssetBundle.LoadFromFile(PathUrl + str[i]);
abDic.Add(strs[i],ab);
}
}

// 加载资源来源包
// 如果没有加载过,再加载
if (!abDic.ContainsKey(abName)) {
ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName. ab);
}
}



// 对同步加载进行重载,因为通过泛型可以避免as转换,并且Lua不支持泛型,
// 因此还需要使用type重载
// 同步加载, 不指定类型
// 加载abName包中的resName资源
public Object LoadRes(string abName, string resName) {
// 加载AB包
LoadAB(abName);
// 加载资源
Object obj = abDic[abName].LoadAsset(resName);
// 为了外面方便,再加载资源时,判断一下资源是不是GameObject
// 如果是,直接实例化,否则返回给外部
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}

// 同步加载,根据type指定类型
public Object LoadRes(string abName, string resName, System.Type type) {
// 加载AB包
LoadAB(abName);
// 加载资源
Object obj = abDic[abName].LoadAsset(resName, type);
// 为了外面方便,再加载资源时,判断一下资源是不是GameObject
// 如果是,直接实例化,否则返回给外部
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}

// 同步加载,根据泛型指定类型
// 必须要加约束,因为LoadAsset<T>方法带有约束
public T LoadRes<T> (string abName, string resName) where T:Object{
// 加载AB包
LoadAB(abName);
// 加载资源
T obj = abDic[abName].LoadAsset<T>(resName, type);
// 为了外面方便,再加载资源时,判断一下资源是不是GameObject
// 如果是,直接实例化,否则返回给外部
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}

// 异步加载的方法,由于异步加载无法马上使用资源,需要使用委托
// 来知道资源加载完后应该怎样使用资源
// 这里的异步加载,AB包并没有使用异步加载
// 只是从AB包中加载资源时,使用异步
// 和同步一样重载
// 根据名字异步加载资源
public void LoadResAsync(string abName, string resName, UnityAction<Object> callBack) {
StartCoroutine(ReallyLoadResAsync(abName, resName, callBack));
}

private IEnumerator ReallyLoadResAsync(string abName, string resName, UnityAction<Object> callBack) {
LoadAB(abName);
AssetBundlesRequest abr = abDic[abName].LoadAssetAsync(resName);
yield return abr;

// 异步加载结束后,通过委托传递给外部来使用
if (abr.asset is GameObject)
callBack(Instantiate(abr.asset));
else
callBack(abr.asset);
}

// 根据type异步加载资源
public void LoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callBack) {
StartCoroutine(ReallyLoadResAsync(abName, resName, type, callBack));
}

private IEnumerator ReallyLoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callBack) {
LoadAB(abName);
AssetBundlesRequest abr = abDic[abName].LoadAssetAsync(resName, type);
yield return abr;

// 异步加载结束后,通过委托传递给外部来使用
if (abr.asset is GameObject)
callBack(Instantiate(abr.asset));
else
callBack(abr.asset);
}

// 根据泛型异步加载资源
public void LoadResAsync<T>(string abName, string resName, UnityAction<T> callBack) where T:Object{
StartCoroutine(ReallyLoadResAsync<T>(abName, resName, callBack));
}

private IEnumerator ReallyLoadResAsync<T>(string abName, string resName, UnityAction<T> callBack) {
LoadAB(abName);
AssetBundlesRequest abr = abDic[abName].LoadAssetAsync<T>(resName);
yield return abr;

// 异步加载结束后,通过委托传递给外部来使用
if (abr.asset is GameObject)
callBack(Instantiate(abr.asset) as T);
else
callBack(abr.asset as T);
}


// 单个包的卸载
public void UnLoad(string abName) {
if (abDic.ContainsKey(abName) {
abDic[abName].Unload(false);
abDic.Remove(abName);
}
}

// 所有包的卸载
public void ClearAB() {
AssetBundle.UnloadAllAsset Bundles(false);
abDic.Clear();
mainAB = null;
manifest = null;
}
}

同步加载和异步加载的区别及不同的加载方式

同步加载: 同步模式,又称阻塞模式,就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
即同步加载会阻止浏览器的后续处理,停止了后续的解析,因此停止了后续的文件加载(如图像)、渲染、代码执行

异步加载: 异步加载,又叫非阻塞,是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

加载方式

同步加载:

1
2
3
4
5
6
//通过名字
Object obj = ABMgr.GetInstance().LoadRes("model", "Cube");
//另外一种方式
GameObject obj = ABMgr.GetInstance().LoadRes("model", "Cube") as GameObject;


1
2
//通过类型
GameObject obj = ABMgr.GetInstance().LoadRes("model", "Cube"typeof(GameObject)) as GameObject;
1
2
//通过泛型
GameObject obj = ABMgr.GetInstance().LoadRes<GameObject>("model", "Cube");

异步加载

1
2
3
4
//通过名字
ABMgr.GetInstance().LoadResAsync("model","Cube", (obj) => {
(obj as GameObject).transform.position = -Vector3.up;
});
1
2
3
4
//通过类型
ABMgr.GetInstance().LoadResAsync("model","Cube", typeof(GameObject), (obj) => {
(obj as GameObject).transform.position = -Vector3.up;
});
1
2
3
4
//通过泛型
ABMgr.GetInstance().LoadResAsync<GameObject>("model","Cube", (obj) => {
obj.transform.position = -Vector3.up;
});