【Unity】AssetBundle资源异步加载以及AB包卸载

以下代码是基于上一篇文章《AssetBundle资源同步加载》的代码新增了异步加载和AB包卸载功能,有兴趣的同学可以去看看我的上一篇文章。

修改单例模式继承MonoBehaviour

由于异步加载需要配合协程进行功能开发,所以我对之前封装的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;
    }
}

异步加载AB包以及依赖包

这里主要用到协程配合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对象
【Unity】AssetBundle资源异步加载以及AB包卸载_第1张图片
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包中的资源了

异步加载AB包中的资源

重载了三个方法提供给外部使用,三个方法大同小异,和同步加载的三个方法差不多
方法一

//因为异步加载不能实时获得想要的资源,所以需要传入一个UnityAction callback
	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包卸载

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包加载时的记录,如有写的不好地方,欢迎指出,感谢感谢!

你可能感兴趣的:(Unity,unity,游戏引擎,c#)