UH1L4——AB包资源管理器

前置知识:字典,协程,单例模式,AB包相关API,委托,lambda表达式

AB包资源管理器要做的工作

封装AssetBundle​的一系列方法,在管理器内部实现加载主包,加载依赖,加载AB包,加载资源的逻辑
对外提供直接同步和异步加载AB包内资源的接口,以及卸载AB包的接口,减少外部加载资源的代码量

继承单例模式基类

这里让ABManager​继承了单例模式基类SingletonAutoMonoBehaviour<T>​,作为单例模式管理器使用

基础成员与属性

每个项目都需要加载其主包以获取依赖信息,因此需要单独的mainAB​和manifest​字段装载主包及其依赖配置信息
为了防止AB包重复加载,这里以包名为键,AB包为值,将加载出来的AB包存储起来,当重新需要该AB包时就直接通过字典获取

将AB包存放路径直接封装为属性,供内部直接调用,这样当AB包存储位置发生改变时,直接修改该属性返回的值即可
将主包名字直接封装为属性,供内部直接调用,可以使用#if​来处理不同平台下返回的不同主包

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
//主包
private AssetBundle mainAB = null;
//依赖包获取用的配置文件
private AssetBundleManifest manifest = null;

private Dictionary<string, AssetBundle> abDic = new Dictionary<string, AssetBundle>();

/// <summary>
/// AB包存放路径,随项目修改
/// </summary>
private string PathUrl
{
get
{
return Application.streamingAssetsPath + "/";
}
}

private string MainABName
{
get
{
#if UNITY_IOS
return "IOS";
#elif UNITY_ANDROID
return "Android";
#else
return "PC";
#endif
}
}

同步加载方法

同步加载有三种重载,对应AssetBundle​加载其中的资源也有三种重载那样,三种重载除了参数与返回的类型不同外都差不多
由于三种重载都需要加载对应的AB包,因此将加载AB包的部分代码直接封装为一个函数供外部及加载方法使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
LoadAB伪代码:
public void LoadAB(string abName)
{
if 主包未加载
加载主包及其依赖配置文件
根据传入的abName获取其所有依赖的包名
for 遍历所有依赖的包名
if 遍历到的依赖包名不存在于AB包字典
根据遍历到的包名,加载AB包
将加载的AB包加入到字典
if abName不存在于字典
根据abName,加载AB包
将加载的AB包加入到字典
}

三种加载方法还有一个共同点,即当发现加载出来的对象是GameObject​时就直接实例化它再返回,否则直接返回该对象

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
public void LoadAB(string abName)
{
//加载主包
if (mainAB == null)
{
mainAB = AssetBundle.LoadFromFile(PathUrl + MainABName);
manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
//获取依赖包相关信息
AssetBundle ab;
//加载主包中的关键配置文件 获取依赖包
string[] strs = manifest.GetAllDependencies(abName);
//加载依赖包
for (int i = 0; i < strs.Length; i++)
{
//判断包是否加载过
if (!abDic.ContainsKey(strs[i]))
{
ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);
abDic.Add(strs[i], ab);
}
}
//加载目标包
if (!abDic.ContainsKey(abName))
{
ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName, ab);
}
}

//同步加载,不指定类型
public Object LoadRes(string abName, string resName)
{
//加载AB包
LoadAB(abName);
Object obj = abDic[abName].LoadAsset(resName);
//为了外部使用方便,在加载资源时,先判断是否为Object,如果是直接实例化返回出去
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);
//为了外部使用方便,在加载资源时,先判断是否为Object,如果是直接实例化返回出去
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}

//同步加载,使用泛型指定类型
public T LoadRes<T>(string abName, string resName) where T : Object
{
//加载AB包
LoadAB(abName);
T obj = abDic[abName].LoadAsset<T>(resName);
//为了外部使用方便,在加载资源时,先判断是否为Object,如果是直接实例化返回出去
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}

异步加载方法

这里的异步加载,AB包并没有使用异步加载,只是从AB包中加载资源时异步加载

异步加载有三种重载,对应AssetBundle​加载其中的资源也有三种重载那样,三种重载除了参数与类型不同外都差不多
与同步加载方法不同的是,它们没有返回值,外部需将加载到对象后作何处理的委托作为参数传入到方法内,委托的参数是加载到的对象
异步加载的三种重载都是直接开启对应的协程方法,协程方法也是三种重载,参数与异步方法都一样

三种协程都会调用之前声明的LoadAB​方法以先加载AB包,再调用对应的AssetBundle​异步加载方法,将协程挂起等待加载完毕
加载完毕后,将加载完的对象传入到外部传入的委托内并执行,若发现加载出来的对象是GameObject​时就则先实例化它再返回

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
//通过名字异步加载资源
public void LoadResAsync(string abName, string resName, UnityAction<Object> callBack)
{
StartCoroutine(LoadResCoroutine(abName, resName, callBack));
}

private IEnumerator LoadResCoroutine(string abName, string resName, UnityAction<Object> callBack)
{
//加载AB包
LoadAB(abName);
AssetBundleRequest 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(LoadResCoroutine(abName, resName, type, callBack));
}

private IEnumerator LoadResCoroutine(string abName, string resName, System.Type type, UnityAction<Object> callBack)
{
//加载AB包
LoadAB(abName);
AssetBundleRequest 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(LoadResCoroutine(abName, resName, callBack));
}

private IEnumerator LoadResCoroutine<T>(string abName, string resName, UnityAction<T> callBack) where T : Object
{
//加载AB包
LoadAB(abName);
AssetBundleRequest 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);
}

卸载AB包方法

就是将卸载AB包的方法封装了起来,封装的方法还包括了从字典内移除或者清空成员变量等逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//单个包卸载
public void UnLoad(string abName)
{
if (abDic.ContainsKey(abName))
{
abDic[abName].Unload(false);
abDic.Remove(abName);
}
}

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

ABManager代码

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
194
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 通过AB包进行资源加载的管理器
/// </summary>
public class ABManager : SingletonAutoMonoBehaviour<ABManager>
{
//主包
private AssetBundle mainAB = null;
//依赖包获取用的配置文件
private AssetBundleManifest manifest = null;

private Dictionary<string, AssetBundle> abDic = new Dictionary<string, AssetBundle>();

/// <summary>
/// AB包存放路径,随项目修改
/// </summary>
private string PathUrl
{
get
{
return Application.streamingAssetsPath + "/";
}
}

private string MainABName
{
get
{
#if UNITY_IOS
return "IOS";
#elif UNITY_ANDROID
return "Android";
#else
return "PC";
#endif
}
}

#region 同步加载
public void LoadAB(string abName)
{
//加载主包
if (mainAB == null)
{
mainAB = AssetBundle.LoadFromFile(PathUrl + MainABName);
manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
//获取依赖包相关信息
AssetBundle ab;
//加载主包中的关键配置文件 获取依赖包
string[] strs = manifest.GetAllDependencies(abName);
//加载依赖包
for (int i = 0; i < strs.Length; i++)
{
//判断包是否加载过
if (!abDic.ContainsKey(strs[i]))
{
ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);
abDic.Add(strs[i], ab);
}
}
//加载目标包
if (!abDic.ContainsKey(abName))
{
ab = AssetBundle.LoadFromFile(PathUrl + abName);
abDic.Add(abName, ab);
}
}

//同步加载,不指定类型
public Object LoadRes(string abName, string resName)
{
//加载AB包
LoadAB(abName);
Object obj = abDic[abName].LoadAsset(resName);
//为了外部使用方便,在加载资源时,先判断是否为Object,如果是直接实例化返回出去
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);
//为了外部使用方便,在加载资源时,先判断是否为Object,如果是直接实例化返回出去
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}

//同步加载,使用泛型指定类型
public T LoadRes<T>(string abName, string resName) where T : Object
{
//加载AB包
LoadAB(abName);
T obj = abDic[abName].LoadAsset<T>(resName);
//为了外部使用方便,在加载资源时,先判断是否为Object,如果是直接实例化返回出去
if (obj is GameObject)
return Instantiate(obj);
else
return obj;
}

#endregion

#region 异步加载
//这里的异步加载 AB包并没有使用异步加载,只是从AB包中加载资源时异步加载

//通过名字异步加载资源
public void LoadResAsync(string abName, string resName, UnityAction<Object> callBack)
{
StartCoroutine(LoadResCoroutine(abName, resName, callBack));
}

private IEnumerator LoadResCoroutine(string abName, string resName, UnityAction<Object> callBack)
{
//加载AB包
LoadAB(abName);
AssetBundleRequest 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(LoadResCoroutine(abName, resName, type, callBack));
}

private IEnumerator LoadResCoroutine(string abName, string resName, System.Type type, UnityAction<Object> callBack)
{
//加载AB包
LoadAB(abName);
AssetBundleRequest 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(LoadResCoroutine(abName, resName, callBack));
}

private IEnumerator LoadResCoroutine<T>(string abName, string resName, UnityAction<T> callBack) where T : Object
{
//加载AB包
LoadAB(abName);
AssetBundleRequest 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);
}
#endregion

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

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

补充:AB包资源管理器的拓展

重置版小框架引用了这里的代码,并进行了拓展(使用的单例基类存在修改):UFL5——资源加载模块