以下代码是基于上一篇文章《AssetBundle资源同步加载》的代码新增了异步加载和AB包卸载功能,有兴趣的同学可以去看看我的上一篇文章。
由于异步加载需要配合协程进行功能开发,所以我对之前封装的AB包资源加载管理器进行修改,改为继承MonoBehaviour的单例模式。
主要修改以下内容
public class ABPageManager : MonoBehaviour
{
private static ABPageManager instance;
public static ABPageManager Instance
{
get
{
//预防忘记把脚本挂上去
if(instance == null)
{
//如果instance为空,说明脚本没有挂上去,这里new了个GameObject
GameObject ABPageMgrObj = new GameObject("ABPageMgr");
//添加管理器脚本
ABPageMgrObj.AddComponent<ABPageManager>();
//并设置过场景不移除
DontDestroyOnLoad(ABPageMgrObj);
}
return instance;
}
}
private void Awake()
{
//游戏一运行,会先执行Awake函数,这里给instance赋值
instance = this;
}
}
这里主要用到协程配合AssetBundle提供的异步加载函数进行加载。
AB包异步加载函数:
AssetBundleCreateRequest AssetBundle.LoadFromFileAsync(string path);
这里我写了一个协程专门异步加载AB包
///
/// 异步加载AB包
///
/// 路径
/// 包名
/// 是否为主包
///
private void LoadABFromFileAsync(string path, string name, bool isMainPage = false)
{
StartCoroutine(IE_LoadABAsync(path, name, isMainPage));
}
IEnumerator IE_LoadABAsync(string path, string name, bool isMainPage = false)
{
if (isMainPage)
{
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(path + "/" + name);
yield return abcr;
mainPage = abcr.assetBundle;
abManifest = mainPage.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
else
{
abPageDic.Add(name, null);
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(path + "/" + name);
yield return abcr;
abPageDic[name] = abcr.assetBundle;
}
}
需要加载AB包调用LoadABFromFileAsync这个函数开启协程加载即可,比较关键的是,因为异步加载是不能立即得到加载的资源的,所以需要先添加空进字典里面也就是这一句abPageDic.Add(name, null);等加载完成后再进行赋值abPageDic[name] = abcr.assetBundle;。
以下是正儿八经的异步加载主包,依赖包和资源包了
//这个协程函数没有用unity提供的协程调度器来执行
//是自定义了规则执行,所以函数里面yield return了一个string并不是在下一帧就往下执行
///
/// 异步加载AB包以及主包和其依赖包协程函数
///
/// AB包名
/// 加载完所有包回调函数
///
IEnumerator IE_LoadABPage(string pageName, UnityAction callback)
{
//判断主包有没有加载过
if (mainPage == null && !isAnsycLoadMainPage)
{
//没有加载过的话异步加载主包
LoadABFromFileAsync(pagePath, MainABPageName, true);
isAnsycLoadMainPage = true;
yield return MainABPageName;
}
else
{
//加载过主包的话,就直接yield return,检测到加载完后就能继续执行下一步了
yield return MainABPageName;
}
string[] pagesName = abManifest.GetAllDependencies(pageName);//得到所有依赖包的名称
//加载资源所在的ab包的所有依赖包 因为同一个ab包不能同时加载,所以用一个字典记录加载过的ab包
for (int i = 0; i < pagesName.Length; i++)
{
if (!abPageDic.ContainsKey(pagesName[i]))
{
LoadABFromFileAsync(pagePath, pagesName[i]);
yield return pagesName[i];
}
else
yield return pagesName[i];
}
if (!abPageDic.ContainsKey(pageName))
{
//加载资源所在的AB包
LoadABFromFileAsync(pagePath, pageName);
yield return pageName;
}
else
yield return pageName;
callback();
}
这里又写了一个协程函数,因为需要先加载主包,才能得到资源包的依赖信息加载依赖包,异步加载主包不能立刻得到主包的资源,所以就需要协程配合作出分时分步去加载。这个协程函数比较特殊,没用unity提供的协程调度器开启协程,因为我需要yield return 一个AB包名,在Update函数中判断这个包有没有加载完再进行下一步,所以,在开启这个协程的时候,我用了一个List把返回回来的协程对象保存了。
///
/// 异步加载AB包以及主包和其依赖包
///
/// AB包名
private void LoadABPageAsync(string pageName, UnityAction callback)
{
//获得IEnumerator对象
IEnumerator ie = IE_LoadABPage(pageName, callback);
//执行下一步
ie.MoveNext();
//保存IEnumerator对象
ieList.Add(ie);
}
因为协程本质上是一个迭代器,我们来看看IEnumerator对象
yield return回来的参数可以通过Current得到,MoveNext()可以执行一个协程函数,遇到yield return停止,下一次调用MoveNext()再从上一次停止的yield return语句的下一句开始执行,达到分时分步的效果。MoveNext()返回一个bool值,当返回false时,就说明协程函数执行完了。
回到咱们代码,ie.MoveNext();执行后会遇到yield return MainABPageName;,协程函数会暂时挂起,这里我封装了一个检测AB包加载是否完成的函数,在Update中调用
///
/// 检测异步加载是否完成,如果完成,IE_LoadABPage协程继续执行
///
private void DetectionLoadingCompleted()
{
for (var i = 0; i < ieList.Count; i++)
{
if ((abPageDic.ContainsKey((string)ieList[i].Current) && abPageDic[(string)ieList[i].Current] != null)
|| ((string)ieList[i].Current == MainABPageName && mainPage != null))
{
if (!ieList[i].MoveNext())
{
ieList.Remove(ieList[i]);
}
}
}
}
因为在前面异步加载的时候往字典里面添加了key包名和null值,加载结束后再把加载好的AssetBundle对象赋值到字典中,所以这里判断如果字典中的包名对应的值不为空,就说明加载好了,就可以MoveNext了,当MoveNext返回false,就说明该协程加载好了,就把该协程移出ieList。
在Update中调用这个函数
private void Update()
{
DetectionLoadingCompleted();
}
加载AB包的逻辑就结束了,AB包加载好了,加载完成后调用传入的callback,就能通知下一步加载AB包中的资源了
重载了三个方法提供给外部使用,三个方法大同小异,和同步加载的三个方法差不多
方法一
//因为异步加载不能实时获得想要的资源,所以需要传入一个UnityAction
public void LoadABPageResAsync(string pageName, string resName, UnityAction<Object> callback)
{
//先异步加载AB包
LoadABPageAsync(pageName, () =>
{
//AB包异步加载结束后,开启协程异步加载资源
StartCoroutine(IE_LoadABPageRes(pageName, resName, callback));
});
}
IEnumerator IE_LoadABPageRes(string pageName, string resName, UnityAction<Object> callback)
{
//异步加载资源
AssetBundleRequest abRequest = abPageDic[pageName].LoadAssetAsync(resName);
yield return abRequest;
//资源加载完成后调用callback,把加载好的资源传出去
callback(abRequest.asset);
}
方法二
public void LoadABPageResAsync(string pageName, string resName, System.Type resType, UnityAction<Object> callback)
{
LoadABPageAsync(pageName, () =>
{
StartCoroutine(IE_LoadABPageRes(pageName, resName, resType, callback));
});
}
IEnumerator IE_LoadABPageRes(string pageName, string resName, System.Type resType, UnityAction<Object> callback)
{
AssetBundleRequest abRequest = abPageDic[pageName].LoadAssetAsync(resName, resType);
yield return abRequest;
callback(abRequest.asset);
}
方法三
public void LoadABPageResAsync<T>(string pageName, string resName, UnityAction<T> callback) where T : Object
{
LoadABPageAsync(pageName, () =>
{
StartCoroutine(IE_LoadABPageRes<T>(pageName, resName, callback));
});
}
IEnumerator IE_LoadABPageRes<T>(string pageName, string resName, UnityAction<T> callback) where T : Object
{
AssetBundleRequest abRequest = abPageDic[pageName].LoadAssetAsync<T>(resName);
yield return abRequest;
callback(abRequest.asset as T);
}
外部使用
void Start()
{
ABPageManager.Instance.LoadABPageResAsync("model", "Cube", (obj) =>
{
Instantiate(obj as GameObject).transform.position = new Vector3(0,2,0);
});
ABPageManager.Instance.LoadABPageResAsync("model", "Capsule", typeof(GameObject), (obj) =>
{
Instantiate(obj as GameObject).transform.position = new Vector3(2, 2, 0);
});
ABPageManager.Instance.LoadABPageResAsync<GameObject>("model", "Sphere", (obj) =>
{
Instantiate(obj).transform.position = new Vector3(4, 2, 0);
});
}
异步加载AB包和AB包中的资源功能就完成了,上面代码有个缺点是,当先异步加载A包的资源a,再同步加载A包的资源b,就会发生AB包重复加载报错的情况,暂时还没想到怎么解决,如有大佬看到麻烦指点一二。
AB包卸载比较简单,使用AssetBundle提供的卸载函数就行
///
/// 卸载单个AB包
///
/// AB包名
public void UnloadABPackage(string pageName)
{
if (abPageDic.ContainsKey(pageName))
{
abPageDic[pageName].Unload(false);
abPageDic.Remove(pageName);
}
}
///
/// 卸载所有AB包
///
public void UnloadAllABPackage()
{
AssetBundle.UnloadAllAssetBundles(false);
abPageDic.Clear();
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class ABPageManager : MonoBehaviour
{
private static ABPageManager instance;
public static ABPageManager Instance
{
get
{
if(instance == null)
{
GameObject ABPageMgrObj = new GameObject("ABPageMgr");
ABPageMgrObj.AddComponent<ABPageManager>();
DontDestroyOnLoad(ABPageMgrObj);
}
return instance;
}
}
//根据平台自定义主包名,需要和打包的ab包名称相同,用来加载主包
private string MainABPageName
{
get
{
#if UNITY_IOS
return "IOS";
#elif UNITY_ANDROID
return "ANDROID";
#else
return "PC";
#endif
}
}
private AssetBundle mainPage;
private AssetBundleManifest abManifest;
private string pagePath = Application.streamingAssetsPath;
private Dictionary<string, AssetBundle> abPageDic = new Dictionary<string, AssetBundle>();
List<IEnumerator> ieList = new List<IEnumerator>();
private bool isAnsycLoadMainPage = false;
private void Awake()
{
instance = this;
}
private void Update()
{
DetectionLoadingCompleted();
}
///
/// 同步加载AB包以及主包和其依赖包
///
/// AB包名
private void LoadABPage(string pageName)
{
//加载主包
if (mainPage == null)
{
mainPage = AssetBundle.LoadFromFile(pagePath + "/" + MainABPageName);
//加载主包中的依赖信息文件,主包中包含了一个记录主包下所有的包的依赖信息,加载此文件可以得到资源所在包的所有依赖包信息
abManifest = mainPage.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
string[] pagesName = abManifest.GetAllDependencies(pageName);//得到所有依赖包的名称
//加载资源所在的ab包的所有依赖包 因为同一个ab包不能同时加载,所以用一个字典记录加载过的ab包
for (int i = 0; i < pagesName.Length; i++)
{
if (!abPageDic.ContainsKey(pagesName[i]))
{
abPageDic.Add(pagesName[i], AssetBundle.LoadFromFile(pagePath + "/" + pagesName[i]));
}
}
if (!abPageDic.ContainsKey(pageName))
{
//加载资源所在的AB包
AssetBundle abPage = AssetBundle.LoadFromFile(pagePath + "/" + pageName);
abPageDic.Add(pageName, abPage);
}
}
///
/// 检测异步加载是否完成,如果完成,IE_LoadABPage协程继续执行
///
private void DetectionLoadingCompleted()
{
for (var i = 0; i < ieList.Count; i++)
{
if ((abPageDic.ContainsKey((string)ieList[i].Current) && abPageDic[(string)ieList[i].Current] != null)
|| ((string)ieList[i].Current == MainABPageName && mainPage != null))
{
if (!ieList[i].MoveNext())
{
ieList.Remove(ieList[i]);
}
}
}
}
///
/// 异步加载AB包
///
/// 路径
/// 包名
/// 是否为主包
///
private void LoadABFromFileAsync(string path, string name, bool isMainPage = false)
{
StartCoroutine(IE_LoadABAsync(path, name, isMainPage));
}
IEnumerator IE_LoadABAsync(string path, string name, bool isMainPage = false)
{
if (isMainPage)
{
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(path + "/" + name);
yield return abcr;
mainPage = abcr.assetBundle;
abManifest = mainPage.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
}
else
{
abPageDic.Add(name, null);
AssetBundleCreateRequest abcr = AssetBundle.LoadFromFileAsync(path + "/" + name);
yield return abcr;
abPageDic[name] = abcr.assetBundle;
}
}
///
/// 异步加载AB包以及主包和其依赖包
///
/// AB包名
private void LoadABPageAsync(string pageName, UnityAction callback)
{
IEnumerator ie = IE_LoadABPage(pageName, callback);
ie.MoveNext();
ieList.Add(ie);
}
///
/// 异步加载AB包以及主包和其依赖包协程函数
///
/// AB包名
/// 加载完所有包回调函数
///
IEnumerator IE_LoadABPage(string pageName, UnityAction callback)
{
if (mainPage == null && !isAnsycLoadMainPage)
{
LoadABFromFileAsync(pagePath, MainABPageName, true);
isAnsycLoadMainPage = true;
yield return MainABPageName;
}
else
{
yield return MainABPageName;
}
string[] pagesName = abManifest.GetAllDependencies(pageName);//得到所有依赖包的名称
//加载资源所在的ab包的所有依赖包 因为同一个ab包不能同时加载,所以用一个字典记录加载过的ab包
for (int i = 0; i < pagesName.Length; i++)
{
if (!abPageDic.ContainsKey(pagesName[i]))
{
LoadABFromFileAsync(pagePath, pagesName[i]);
yield return pagesName[i];
}
else
yield return pagesName[i];
}
if (!abPageDic.ContainsKey(pageName))
{
//加载资源所在的AB包
LoadABFromFileAsync(pagePath, pageName);
yield return pageName;
}
else
yield return pageName;
callback();
}
///
/// 同步加载AB包
///
/// AB包名
/// 资源名
/// 返回对应的资源对象 Object类型
public Object LoadABPageRes(string pageName, string resName)
{
LoadABPage(pageName);
//加载资源并返回
return abPageDic[pageName].LoadAsset(resName);
}
///
/// 同步加载AB包
///
/// AB包名
/// 资源名
/// 资源类型
/// 返回对应的资源对象 Object类型
public Object LoadABPageRes(string pageName, string resName, System.Type resType)
{
LoadABPage(pageName);
return abPageDic[pageName].LoadAsset(resName, resType);
}
///
/// 同步加载AB包
///
/// 资源类型
/// AB包名
/// 资源名
/// 返回对应的资源对象 对应资源的类型
public T LoadABPageRes<T>(string pageName, string resName) where T : Object
{
LoadABPage(pageName);
return abPageDic[pageName].LoadAsset<T>(resName);
}
public void LoadABPageResAsync(string pageName, string resName, UnityAction<Object> callback)
{
LoadABPageAsync(pageName, () =>
{
StartCoroutine(IE_LoadABPageRes(pageName, resName, callback));
});
}
IEnumerator IE_LoadABPageRes(string pageName, string resName, UnityAction<Object> callback)
{
AssetBundleRequest abRequest = abPageDic[pageName].LoadAssetAsync(resName);
yield return abRequest;
callback(abRequest.asset);
}
public void LoadABPageResAsync(string pageName, string resName, System.Type resType, UnityAction<Object> callback)
{
LoadABPageAsync(pageName, () =>
{
StartCoroutine(IE_LoadABPageRes(pageName, resName, resType, callback));
});
}
IEnumerator IE_LoadABPageRes(string pageName, string resName, System.Type resType, UnityAction<Object> callback)
{
AssetBundleRequest abRequest = abPageDic[pageName].LoadAssetAsync(resName, resType);
yield return abRequest;
callback(abRequest.asset);
}
public void LoadABPageResAsync<T>(string pageName, string resName, UnityAction<T> callback) where T : Object
{
LoadABPageAsync(pageName, () =>
{
StartCoroutine(IE_LoadABPageRes<T>(pageName, resName, callback));
});
}
IEnumerator IE_LoadABPageRes<T>(string pageName, string resName, UnityAction<T> callback) where T : Object
{
AssetBundleRequest abRequest = abPageDic[pageName].LoadAssetAsync<T>(resName);
yield return abRequest;
callback(abRequest.asset as T);
}
///
/// 卸载单个AB包
///
/// AB包名
public void UnloadABPackage(string pageName)
{
if (abPageDic.ContainsKey(pageName))
{
abPageDic[pageName].Unload(false);
abPageDic.Remove(pageName);
}
}
///
/// 卸载所有AB包
///
public void UnloadAllABPackage()
{
AssetBundle.UnloadAllAssetBundles(false);
abPageDic.Clear();
}
}
本文是学习AB包加载时的记录,如有写的不好地方,欢迎指出,感谢感谢!