简单的异步AB加载框架
简述打包流程
- 设置资源的BundleName以及BundleVariant
- 打包AssetBundle
- (将AB包上传到服务端,若放在本地则可跳过)
- 加载AssetBundle
- 实例化AssetBundle中的资源
- AssetBundle的卸载
着重讲解AB包的异步加载。
包与包的依赖关系:
如上图所示包和包的依赖关系可能是以上图树状结构,如果我们要使用A包里面的资源,但是A包里面的资源依赖C包资源,而C包资源又依赖D包资源,所以需要根据Manefist的依赖关系去整合信息,使用A包资源前要先加载C包和D包。
依赖关系处理:
如上图所示通过Manefist可以获取到包与包之间的依赖关系,将依赖关系抽象为一个类,根据我们加载的资源判断该包是否需要加载依赖包(如果依赖包已经存在就不再重复加载依赖包)。
资源加载处理:
根据依赖关系层层递进将最深层次的包进行加载,然后再确定最初的包加载好,根据引用计数的方式处理包的卸载加载。
异步加载关于回调:
这个Demo使用的是协程,所以使用的是回调,网上很多大佬的处理方式是去携程或者使用await或者async的方式。
代码解析:
/// <summary>
/// 将AB包的信息抽象到一个类里面
/// </summary>
private class AssetBundleObject
{
public string HashName; //hash标识符
public int RefCount; //被引用计数
public List<AssetBundleLoadCallBack> CallFunList = new List<AssetBundleLoadCallBack>(); //回调函数表 加载好后可以调用该函数表
public AssetBundleCreateRequest Request; //异步加载请求
public AssetBundle AB; //加载到的ab
public int DependLoadingCount; //正在加载的依赖数量 为0代表其依赖已经加载完毕就可以被使用了
public List<AssetBundleObject> Depends = new List<AssetBundleObject>(); //直接的依赖项ABObj
}
/// <summary>
/// Load主Manifest 能够获得AB包所有的依赖包
/// </summary>
/// <param name="rPath">Manifest路径</param>
public void LoadManifest(string rPath)
{
GlobalABdps.Clear();
//Manifest也是一种AB资源
AssetBundle rAB = AssetBundle.LoadFromFile(rPath);
AssetBundleManifest rMainfest = rAB.LoadAsset("AssetBundleManifest") as AssetBundleManifest;
//遍历Manifest中的ABs
foreach (string rAssetName in rMainfest.GetAllAssetBundles()) //GetAllAssetBundles => Get all the AssetBundles in the manifest.
{
string rHashName = rAssetName.Replace(".ab", "");
string[] dps = rMainfest.GetAllDependencies(rAssetName); //返回该AB的依赖AB
for (int i = 0; i < dps.Length; i++)
{
dps[i] = dps[i].Replace(".ab", "");
}
GlobalABdps.Add(rHashName, dps);
}
rAB.Unload(true);
rAB = null;
}
/// <summary>
/// 深度递归遍历依赖项 刷新自己被引用计数
/// </summary>
private void DFSDependRef(AssetBundleObject rABObj)
{
rABObj.RefCount++;
foreach (var rParentAB in rABObj.Depends)
{
DFSDependRef(rParentAB);
}
}
/// <summary>
/// 深度递归遍历依赖项 刷新被计数 达到条件则卸载资源
/// </summary>
private void DFSDependRefReduce(AssetBundleObject rABObj)
{
rABObj.RefCount--;
foreach (var rParentAB in rABObj.Depends)
{
DFSDependRefReduce(rParentAB);
}
if (rABObj.RefCount == 0)
{
rABObj.AB.Unload(false);
this.LoadedAssetBundle[rABObj.HashName] = null;
this.LoadedAssetBundle.Remove(rABObj.HashName);
rABObj = null;
}
}
/// <summary>
/// 递归加载资源 会同时加载其依赖包
/// </summary>
/// <param name="rABName">AB包的名字</param>
/// <returns></returns>
private AssetBundleObject LoadAssetBundleAsync(string rABName, UnityAction rCallBack = null)
{
if (!GlobalABdps.ContainsKey(rABName))
{
Debug.Log($"没有 {rABName} 包");
return null;
}
AssetBundleObject rABObj = null;
//如果存在
if (LoadedAssetBundle.ContainsKey(rABName))
{
rABObj = LoadedAssetBundle[rABName];
DFSDependRef(rABObj);
return rABObj;
}
//创建并初始化新ABObj
rABObj = new AssetBundleObject();
rABObj.HashName = rABName;
rABObj.RefCount = 1;
//加载依赖
var rDps = this.GlobalABdps[rABObj.HashName];
rABObj.DependLoadingCount = rDps.Length;
foreach (var rDpName in rDps)
{
var dpObj = LoadAssetBundleAsync(rDpName, delegate { rABObj.DependLoadingCount--; });
rABObj.Depends.Add(dpObj);
}
//异步加载AB包
LoadAssetAsyn(rABObj, rCallBack);
return rABObj;
}
/// <summary>
/// 启用协程异步加载AB包
/// </summary>
/// <param name="rABObj">ABObj</param>
/// <param name="rCallBack">回调函数表</param>
private void LoadAssetAsyn(AssetBundleObject rABObj, UnityAction rCallBack) //List<UnityAction<object[]>> rCallBack)
{
Game.Instance.StartCoroutine(CoroutineLoad(rABObj, rCallBack));//因为这个类没有继承Mono 所以将协程抛到其他Mono下执行
}
private IEnumerator CoroutineLoad(AssetBundleObject rABObj, UnityAction rCallBack)
{
AssetBundleCreateRequest rABAns = AssetBundle.LoadFromFileAsync(this.GetABPath(rABObj.HashName));
yield return rABAns;
while (rABObj.DependLoadingCount != 0)//如果依赖项都没加载完就一直执行
{
yield return new WaitForEndOfFrame();
}
rABObj.AB = rABAns.assetBundle;
this.LoadedAssetBundle.Add(rABObj.HashName, rABObj);
rCallBack?.Invoke();//执行回调
}
/// <summary>
/// 卸载资源
/// </summary>
/// <param name="rABpath">AB包路径</param>
/// <param name="rCallback">回调</param>
public void UnLoadAssetAsync(string rABpath, UnityAction rCallback)
{
if (this.LoadedAssetBundle.ContainsKey(rABpath))
{
DFSDependRefReduce(this.LoadedAssetBundle[rABpath]);
rCallback?.Invoke();
}
}