Unity资源加载模块全解析
资源加载模块
资源加载模块的主要作用是
方便统一管理各种资源加载卸载方式(Resources、AB 包、UnityWebRequest、Editor)
方便异步加载资源(Resources、AB 包、UnityWebRequest)
资源加载模块的基本原理
将各资源加载模式模块化,比如 Resources 资源管理器、AB 包资源管理器等等,让他们分别管理不同加载方式加载的资源,最终再将他们整合在一起,方便我们在加载资源时根据自己的需求进行选择性使用
Resources资源加载模块
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;/// <summary>
/// Resources资源加载模块管理器
/// </summary>
public class ResourcesMgr : BaseManager<ResourcesMgr>
{private ResourcesMgr() { }/// <summary>/// 同步加载资源方法/// </summary>/// <typeparam name="T"></typeparam>/// <param name="path">资源路径</param>/// <returns></returns>public T Load<T>(string path) where T : UnityEngine.Object{ return Resources.Load<T>(path);}/// <summary>/// 异步加载资源方法/// </summary>/// <typeparam name="T">资源类型</typeparam>/// <param name="path">资源路径</param>/// <param name="callBack">加载结束后的回调函数</param>public void LoadAsync<T>(string path,UnityAction<T> callBack) where T: UnityEngine.Object{//通过协同程序异步加载资源MonoMgr.Instance.StartCoroutine(ReallyLoadAsync<T>(path,callBack));}private IEnumerator ReallyLoadAsync<T>(string path, UnityAction<T> callBack) where T : UnityEngine.Object{//异步加载资源ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);//等待资源加载结束后才会执行后面的代码yield return resourceRequest;//资源加载结束callBack?.Invoke(resourceRequest.asset as T);}/// <summary>/// 异步加载资源方法/// </summary>/// <param name="path">资源路径</param>/// <param name="type">资源类型</param>/// <param name="callBack">加载结束后的回调函数</param>public void LoadAsync(string path,Type type, UnityAction<object> callBack){//通过协同程序异步加载资源MonoMgr.Instance.StartCoroutine(ReallyLoadAsync(path,type,callBack));}private IEnumerator ReallyLoadAsync(string path, Type type, UnityAction<object> callBack){//异步加载资源ResourceRequest resourceRequest = Resources.LoadAsync(path,type);//等待资源加载结束后才会执行后面的代码yield return resourceRequest;//资源加载结束callBack?.Invoke(resourceRequest.asset);}/// <summary>/// 指定卸载资源/// </summary>/// <param name="assetToUnload"></param>public void UnloadAsset(UnityEngine.Object assetToUnload){Resources.UnloadAsset(assetToUnload);}/// <summary>/// 异步卸载对应没有使用的Resources相关的资源/// </summary>/// <param name="callBack">回调函数</param>public void UnloadUnusedAssets(UnityAction callBack){MonoMgr.Instance.StartCoroutine(ReallyUnloadUnusedAssets(callBack)); }private IEnumerator ReallyUnloadUnusedAssets(UnityAction callBack){AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();yield return asyncOperation;//卸载完毕后通知外部callBack?.Invoke();}
}
Resources资源加载模块优化
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;public abstract class ResInfoBase { }
/// <summary>
/// 资源信息对象 主要用于存储资源信息 异步加载委托信息 异步加载协程信息
/// </summary>
/// <typeparam name="T"></typeparam>
public class ResInfo<T> : ResInfoBase
{//资源public T asset;//委托 主要用于异步加载结束后 传递资源到外部的委托public UnityAction<T> callback;//用于存储异步加载时 开启的协同程序public Coroutine coroutine;//判断是否需要移除public bool isDel = false;
}
/// <summary>
/// Resources资源加载模块管理器
/// </summary>
public class ResourcesMgr : BaseManager<ResourcesMgr>
{//用于存储加载过的资源或者加载中的资源容器private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();private ResourcesMgr() { }/// <summary>/// 同步加载资源方法/// </summary>/// <typeparam name="T"></typeparam>/// <param name="path">资源路径</param>/// <returns></returns>public T Load<T>(string path) where T : UnityEngine.Object{ResInfo<T> info;string resName = path + "_" + typeof(T).Name;if (!resDic.ContainsKey(resName)){//直接同步加载 并且记录资源信息到字典中 方便下次直接取出来用T res = Resources.Load<T>(path);info = new ResInfo<T>();info.asset = res;resDic.Add(resName, info);return res;}else{ //取出字典中的记录info = resDic[resName] as ResInfo<T>;//异步加载资源没有加载完成if (info.asset == null){ //停止异步加载 直接采用同步的方式加载成功 记录并使用MonoMgr.Instance.StopCoroutine(info.coroutine);//直接采用同步的方式加载成功T res = Resources.Load<T>(path);//记录info.asset = res;//将异步加载结束的委托去执行info.callback?.Invoke(res);//加载完毕后清空info.callback = null;info.coroutine = null;//返回加载成功的对象return res;}else{ return info.asset;}}}/// <summary>/// 异步加载资源方法/// </summary>/// <typeparam name="T">资源类型</typeparam>/// <param name="path">资源路径</param>/// <param name="callBack">加载结束后的回调函数</param>public void LoadAsync<T>(string path,UnityAction<T> callBack) where T: UnityEngine.Object{ResInfo<T> info;//资源的唯一IDstring resName = path + "_" + typeof(T).Name;if (!resDic.ContainsKey(resName)){//声明一个 资源信息对象info = new ResInfo<T>();//将资源记录添加到字典中resDic.Add(resName, info);//记录传入的委托函数 一会加载完成后使用info.callback += callBack;//通过协同程序异步加载资源info.coroutine = MonoMgr.Instance.StartCoroutine(ReallyLoadAsync<T>(path));}else{ //从字典中取出资源信息info = resDic[resName] as ResInfo<T>;//资源还没加载完if (info.asset == null)info.callback += callBack;elsecallBack?.Invoke(info.asset);}}private IEnumerator ReallyLoadAsync<T>(string path) where T : UnityEngine.Object{//异步加载资源ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);//等待资源加载结束后才会执行后面的代码yield return resourceRequest;//资源加载结束string resName = path + "_" + typeof(T).Name;if (resDic.ContainsKey(resName)){ResInfo<T> info = (resDic[resName] as ResInfo<T>);//取出资源信息 并记录加载完成的资源info.asset = resourceRequest.asset as T;if (info.isDel)UnloadAsset<T>(path);else {//将加载完成的资源传递出去info.callback?.Invoke(info.asset);//加载完毕后清空info.callback = null;info.coroutine = null;}}}/// <summary>/// 异步加载资源方法/// </summary>/// <param name="path">资源路径</param>/// <param name="type">资源类型</param>/// <param name="callBack">加载结束后的回调函数</param>[Obsolete("注意:建议使用泛型方式加载,如果必须得用Type的方式,请不要用泛型方法和type方法加载同类型同名资源")]public void LoadAsync(string path,Type type, UnityAction<UnityEngine.Object> callBack){ResInfo<UnityEngine.Object> info;//资源的唯一IDstring resName = path + "_" + type.Name;if (!resDic.ContainsKey(resName)){//声明一个 资源信息对象info = new ResInfo<UnityEngine.Object>();//将资源记录添加到字典中resDic.Add(resName, info);//记录传入的委托函数 一会加载完成后使用info.callback += callBack;//通过协同程序异步加载资源info.coroutine = MonoMgr.Instance.StartCoroutine(ReallyLoadAsync(path,type));}else{//从字典中取出资源信息info = resDic[resName] as ResInfo<UnityEngine.Object>;//资源还没加载完if (info.asset == null)info.callback += callBack;elsecallBack?.Invoke(info.asset);}}private IEnumerator ReallyLoadAsync(string path, Type type){//异步加载资源ResourceRequest resourceRequest = Resources.LoadAsync(path,type);//等待资源加载结束后才会执行后面的代码yield return resourceRequest;//资源加载结束string resName = path + "_" + type.Name;if (resDic.ContainsKey(resName)){ResInfo<UnityEngine.Object> info = (resDic[resName] as ResInfo<UnityEngine.Object>);//取出资源信息 并记录加载完成的资源info.asset = resourceRequest.asset;if (info.isDel)UnloadAsset(path,type);else{//将加载完成的资源传递出去info.callback?.Invoke(info.asset);//加载完毕后清空info.callback = null;info.coroutine = null;}}}/// <summary>/// 指定卸载资源/// </summary>/// <param name="assetToUnload"></param>public void UnloadAsset<T>(string path){//资源的唯一IDstring resName = path + "_" + typeof(T).Name;//判断是否存在对应资源if (resDic.ContainsKey(resName)){ResInfo<T> resInfo = resDic[resName] as ResInfo<T>;//资源已经加载结束if (resInfo.asset != null){resDic.Remove(resName);Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);}else//资源在加载中{ resInfo.isDel = true;}}}public void UnloadAsset(string path,Type type){//资源的唯一IDstring resName = path + "_" + type.Name;//判断是否存在对应资源if (resDic.ContainsKey(resName)){ResInfo<UnityEngine.Object> resInfo = resDic[resName] as ResInfo<UnityEngine.Object>;//资源已经加载结束if (resInfo.asset != null){resDic.Remove(resName);Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);}else//资源在加载中{resInfo.isDel = true;}}}/// <summary>/// 异步卸载对应没有使用的Resources相关的资源/// </summary>/// <param name="callBack">回调函数</param>public void UnloadUnusedAssets(UnityAction callBack){MonoMgr.Instance.StartCoroutine(ReallyUnloadUnusedAssets(callBack)); }private IEnumerator ReallyUnloadUnusedAssets(UnityAction callBack){AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();yield return asyncOperation;//卸载完毕后通知外部callBack?.Invoke();}
}
Resources资源加载模块加入引用计数
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;public abstract class ResInfoBase
{//引用计数public int refCount;
}
/// <summary>
/// 资源信息对象 主要用于存储资源信息 异步加载委托信息 异步加载协程信息
/// </summary>
/// <typeparam name="T"></typeparam>
public class ResInfo<T> : ResInfoBase
{//资源public T asset;//委托 主要用于异步加载结束后 传递资源到外部的委托public UnityAction<T> callback;//用于存储异步加载时 开启的协同程序public Coroutine coroutine;//判断是否需要移除public bool isDel = false;public void AddRefCount() { ++refCount; }public void SubRefCount() { --refCount;if (refCount < 0)Debug.LogError("引用计数小于0了,请检查使用和卸载是否配对");}
}
/// <summary>
/// Resources资源加载模块管理器
/// </summary>
public class ResourcesMgr : BaseManager<ResourcesMgr>
{//用于存储加载过的资源或者加载中的资源容器private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();private ResourcesMgr() { }/// <summary>/// 同步加载资源方法/// </summary>/// <typeparam name="T"></typeparam>/// <param name="path">资源路径</param>/// <returns></returns>public T Load<T>(string path) where T : UnityEngine.Object{ResInfo<T> info;string resName = path + "_" + typeof(T).Name;if (!resDic.ContainsKey(resName)){//直接同步加载 并且记录资源信息到字典中 方便下次直接取出来用T res = Resources.Load<T>(path);info = new ResInfo<T>();info.asset = res;info.AddRefCount();resDic.Add(resName, info);return res;}else{ //取出字典中的记录info = resDic[resName] as ResInfo<T>;info.AddRefCount();//异步加载资源没有加载完成if (info.asset == null){ //停止异步加载 直接采用同步的方式加载成功 记录并使用MonoMgr.Instance.StopCoroutine(info.coroutine);//直接采用同步的方式加载成功T res = Resources.Load<T>(path);//记录info.asset = res;//将异步加载结束的委托去执行info.callback?.Invoke(res);//加载完毕后清空info.callback = null;info.coroutine = null;//返回加载成功的对象return res;}else{ return info.asset;}}}/// <summary>/// 异步加载资源方法/// </summary>/// <typeparam name="T">资源类型</typeparam>/// <param name="path">资源路径</param>/// <param name="callBack">加载结束后的回调函数</param>public void LoadAsync<T>(string path,UnityAction<T> callBack) where T: UnityEngine.Object{ResInfo<T> info;//资源的唯一IDstring resName = path + "_" + typeof(T).Name;if (!resDic.ContainsKey(resName)){//声明一个 资源信息对象info = new ResInfo<T>();info.AddRefCount();//将资源记录添加到字典中resDic.Add(resName, info);//记录传入的委托函数 一会加载完成后使用info.callback += callBack;//通过协同程序异步加载资源info.coroutine = MonoMgr.Instance.StartCoroutine(ReallyLoadAsync<T>(path));}else{ //从字典中取出资源信息info = resDic[resName] as ResInfo<T>;info.AddRefCount();//资源还没加载完if (info.asset == null)info.callback += callBack;elsecallBack?.Invoke(info.asset);}}private IEnumerator ReallyLoadAsync<T>(string path) where T : UnityEngine.Object{//异步加载资源ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);//等待资源加载结束后才会执行后面的代码yield return resourceRequest;//资源加载结束string resName = path + "_" + typeof(T).Name;if (resDic.ContainsKey(resName)){ResInfo<T> info = (resDic[resName] as ResInfo<T>);//取出资源信息 并记录加载完成的资源info.asset = resourceRequest.asset as T;if (info.refCount == 0)UnloadAsset<T>(path,info.isDel,false);else {//将加载完成的资源传递出去info.callback?.Invoke(info.asset);//加载完毕后清空info.callback = null;info.coroutine = null;}}}/// <summary>/// 异步加载资源方法/// </summary>/// <param name="path">资源路径</param>/// <param name="type">资源类型</param>/// <param name="callBack">加载结束后的回调函数</param>[Obsolete("注意:建议使用泛型方式加载,如果必须得用Type的方式,请不要用泛型方法和type方法加载同类型同名资源")]public void LoadAsync(string path,Type type, UnityAction<UnityEngine.Object> callBack){ResInfo<UnityEngine.Object> info;//资源的唯一IDstring resName = path + "_" + type.Name;if (!resDic.ContainsKey(resName)){//声明一个 资源信息对象info = new ResInfo<UnityEngine.Object>();info.AddRefCount();//将资源记录添加到字典中resDic.Add(resName, info);//记录传入的委托函数 一会加载完成后使用info.callback += callBack;//通过协同程序异步加载资源info.coroutine = MonoMgr.Instance.StartCoroutine(ReallyLoadAsync(path,type));}else{//从字典中取出资源信息info = resDic[resName] as ResInfo<UnityEngine.Object>;info.AddRefCount();//资源还没加载完if (info.asset == null)info.callback += callBack;elsecallBack?.Invoke(info.asset);}}private IEnumerator ReallyLoadAsync(string path, Type type){//异步加载资源ResourceRequest resourceRequest = Resources.LoadAsync(path,type);//等待资源加载结束后才会执行后面的代码yield return resourceRequest;//资源加载结束string resName = path + "_" + type.Name;if (resDic.ContainsKey(resName)){ResInfo<UnityEngine.Object> info = (resDic[resName] as ResInfo<UnityEngine.Object>);//取出资源信息 并记录加载完成的资源info.asset = resourceRequest.asset;if (info.refCount == 0)UnloadAsset(path,type,info.isDel,false);else{//将加载完成的资源传递出去info.callback?.Invoke(info.asset);//加载完毕后清空info.callback = null;info.coroutine = null;}}}/// <summary>/// 指定卸载资源/// </summary>/// <param name="assetToUnload"></param>public void UnloadAsset<T>(string path,bool isDel = false,bool isSub = true, UnityAction<T> callback = null){//资源的唯一IDstring resName = path + "_" + typeof(T).Name;//判断是否存在对应资源if (resDic.ContainsKey(resName)){ResInfo<T> resInfo = resDic[resName] as ResInfo<T>;if(isSub)resInfo.SubRefCount();resInfo.isDel = isDel;//资源已经加载结束if (resInfo.asset != null && resInfo.refCount == 0 && resInfo.isDel){resDic.Remove(resName);Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);}else if(resInfo.asset == null)//资源在加载中{if(callback != null)resInfo.callback -= callback;}}}public void UnloadAsset(string path, Type type, bool isDel = false, bool isSub = true, UnityAction<UnityEngine.Object> callback = null){//资源的唯一IDstring resName = path + "_" + type.Name;//判断是否存在对应资源if (resDic.ContainsKey(resName)){ResInfo<UnityEngine.Object> resInfo = resDic[resName] as ResInfo<UnityEngine.Object>;if(isSub)resInfo.SubRefCount();resInfo.isDel = isDel;//资源已经加载结束if (resInfo.asset != null && resInfo.refCount == 0 && resInfo.isDel){resDic.Remove(resName);Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);}else if(resInfo.asset == null)//资源在加载中{if (callback != null)resInfo.callback -= callback;}}}/// <summary>/// 异步卸载对应没有使用的Resources相关的资源/// </summary>/// <param name="callBack">回调函数</param>public void UnloadUnusedAssets(UnityAction callBack){MonoMgr.Instance.StartCoroutine(ReallyUnloadUnusedAssets(callBack)); }private IEnumerator ReallyUnloadUnusedAssets(UnityAction callBack){//就是在真正移除不使用资源之前 应该把我们自己记录的那些引用计数为0 并且没有被移除记录的资源移除掉List<string> list = new List<string>();foreach (string path in resDic.Keys){if (resDic[path].refCount == 0)list.Add(path);}foreach (string path in list){resDic.Remove(path);}AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();yield return asyncOperation;//卸载完毕后通知外部callBack?.Invoke();}/// <summary>/// 获取某个资源的引用计数/// </summary>/// <typeparam name="T"></typeparam>/// <param name="path"></param>/// <returns></returns>public int GetRefCount<T>(string path){ string resName = path + "_" + typeof(T).Name;if (resDic.ContainsKey(resName))return (resDic[resName] as ResInfo<T>).refCount;elsereturn 0;}/// <summary>/// 清空字典/// </summary>/// <param name="callBack"></param>public void ClearDic(UnityAction callBack){MonoMgr.Instance.StartCoroutine(ReallyClearDic(callBack));}private IEnumerator ReallyClearDic(UnityAction callBack){resDic.Clear();AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();yield return asyncOperation;//卸载完毕后通知外部callBack?.Invoke();}
}
Editor资源加载模块
Editor 资源加载的主要作用,在进行项目开发时,最终发布的项目一般为了达到以下两个目的
1.减小包体大小
2.热更新
最终会通过 AssetBundle 资源加载作为主要加载方式
那么也就是说最终我们发布项目时
几乎所有的的游戏资源都会在 AB 包中的
AB 包最终会放置在 StreamingAssets 中 或者 远程资源服务器中
但是在开发期间,如果进行频繁的资源打包(资源放入 AssetBundle 包),会大大降低我们的开发效率。
因此我们在开发功能时,可以不通过 AB 包加载资源,而是在最终要测试或发布时才把资源整合进 AB 包。但是为了方便资源管理,我们也不能将资源放在 Resources 中来进行加载,因为最终 Resources 中的资源会被打包出去。那么此时 Editor 资源加载的作用就体现出来了,我们可以将资源放进不会被打包的 Editor 文件夹中,开发时通过 Editor 资源加载,测试和发布时使用 AssetBundle 资源加载。
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;/// <summary>
/// 编辑器资源管理
/// 只有开发时能使用该管理器加载资源
/// </summary>
public class EditorResMgr : BaseManager<EditorResMgr>
{//用于放置需要打包进AB包中的资源路径private string rootPath = "Assets/Editor/ArtRes/"; private EditorResMgr() { }/// <summary>/// 加载单个资源/// </summary>/// <typeparam name="T"></typeparam>/// <param name="path"></param>/// <returns></returns>public T LoadEditorRes<T>(string path) where T : UnityEngine.Object{
#if UNITY_EDITORstring suffixName = "";if (typeof(T) == typeof(GameObject))suffixName = ".prefab";else if (typeof(T) == typeof(Material))suffixName = ".mat";return AssetDatabase.LoadAssetAtPath<T>(rootPath + path + suffixName);
#elsereturn null;
#endif}/// <summary>/// 加载图集相关资源的方法/// </summary>/// <param name="path"></param>/// <param name="spriteName"></param>/// <returns></returns>public Sprite LoadSprite(string path, string spriteName){
#if UNITY_EDITOR//加载图集中的所有子资源Object[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(rootPath + path);foreach (var item in sprites){if (item.name == spriteName)return item as Sprite;}return null;
#elsereturn null;
#endif}/// <summary>/// 获取整个图集/// </summary>/// <param name="path"></param>/// <returns></returns>public Dictionary<string, Sprite> LoadSprites(string path){
#if UNITY_EDITORDictionary<string, Sprite> spDic = new Dictionary<string, Sprite>();//加载图集中的所有子资源Object[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(rootPath + path);foreach (var item in sprites){spDic.Add(item.name, item as Sprite);}return spDic;
#elsereturn null;
#endif}
}
AssetBundle资源管理模块
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;public class ABMgr : SingletonAutoMono<ABMgr>
{//主包private AssetBundle mainAB = null;//主包依赖获取配置文件private AssetBundleManifest manifest = null;//选择存储 AB包的容器//AB包不能够重复加载 否则会报错//字典知识 用来存储 AB包对象private Dictionary<string, AssetBundle> abDic = new Dictionary<string, AssetBundle>();/// <summary>/// 获取AB包加载路径/// </summary>private string PathUrl{get{return Application.streamingAssetsPath + "/";}}/// <summary>/// 主包名 根据平台不同 报名不同/// </summary>private string MainName{get{
#if UNITY_IOSreturn "IOS";
#elif UNITY_ANDROIDreturn "Android";
#elsereturn "PC";
#endif}}/// <summary>/// 加载主包 和 配置文件/// 因为加载所有包是 都得判断 通过它才能得到依赖信息/// 所以写一个方法/// </summary>private void LoadMainAB(){if( mainAB == null ){mainAB = AssetBundle.LoadFromFile( PathUrl + MainName);manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");}}/// <summary>/// 泛型异步加载资源/// </summary>/// <typeparam name="T"></typeparam>/// <param name="abName"></param>/// <param name="resName"></param>/// <param name="callBack"></param>public void LoadResAsync<T>(string abName, string resName, UnityAction<T> callBack, bool isSync = false) where T:Object{StartCoroutine(ReallyLoadResAsync<T>(abName, resName, callBack,isSync));}//协程函数private IEnumerator ReallyLoadResAsync<T>(string abName, string resName, UnityAction<T> callBack, bool isSync) where T : Object{//加载主包LoadMainAB();//获取依赖包string[] strs = manifest.GetAllDependencies(abName);for (int i = 0; i < strs.Length; i++){if (!abDic.ContainsKey(strs[i])){if (isSync) //同步加载{AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);abDic.Add(strs[i], ab);}else //异步加载{//一开始异步加载就记录 如果值为null说明正在加载abDic.Add(strs[i], null);AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + strs[i]);yield return rep;//不为空加载结束abDic[strs[i]] = rep.assetBundle;}}else//证明字典中已经记录了一个AB包信息{//如果字典中记录的是null 那就证明正在加载//我们需要等待加载结束 直接使用while (abDic[strs[i]] == null){ //只有发现正在加载中 就不停的等待一帧yield return 0;}}}//加载目标包if (!abDic.ContainsKey(abName)){if (isSync) //同步加载{AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);abDic.Add(abName, ab);}else{//一开始异步加载就记录 如果值为null说明正在加载abDic.Add(abName, null);AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + abName);yield return rep;abDic[abName] = rep.assetBundle;}}else{//如果字典中记录的是null 那就证明正在加载//我们需要等待加载结束 直接使用while (abDic[abName] == null){//只有发现正在加载中 就不停的等待一帧yield return 0;}}if (isSync) //同步加载{//即使是同步加载也需要通过回调函数传结果给外部T res = abDic[abName].LoadAsset<T>(resName);callBack(res);}else{//异步加载包中资源AssetBundleRequest abq = abDic[abName].LoadAssetAsync<T>(resName);yield return abq;callBack(abq.asset as T);}}/// <summary>/// Type异步加载资源/// </summary>/// <param name="abName"></param>/// <param name="resName"></param>/// <param name="type"></param>/// <param name="callBack"></param>public void LoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callBack, bool isSync = false){StartCoroutine(ReallyLoadResAsync(abName, resName, type, callBack, isSync));}private IEnumerator ReallyLoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callBack, bool isSync){//加载主包LoadMainAB();//获取依赖包string[] strs = manifest.GetAllDependencies(abName);for (int i = 0; i < strs.Length; i++){if (!abDic.ContainsKey(strs[i])){if (isSync) //同步加载{AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);abDic.Add(strs[i], ab);}else //异步加载{//一开始异步加载就记录 如果值为null说明正在加载abDic.Add(strs[i], null);AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + strs[i]);yield return rep;//不为空加载结束abDic[strs[i]] = rep.assetBundle;}}else//证明字典中已经记录了一个AB包信息{//如果字典中记录的是null 那就证明正在加载//我们需要等待加载结束 直接使用while (abDic[strs[i]] == null){//只有发现正在加载中 就不停的等待一帧yield return 0;}}}//加载目标包if (!abDic.ContainsKey(abName)){if (isSync) //同步加载{AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);abDic.Add(abName, ab);}else{//一开始异步加载就记录 如果值为null说明正在加载abDic.Add(abName, null);AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + abName);yield return rep;abDic[abName] = rep.assetBundle;}}else{//如果字典中记录的是null 那就证明正在加载//我们需要等待加载结束 直接使用while (abDic[abName] == null){//只有发现正在加载中 就不停的等待一帧yield return 0;}}if (isSync){Object res = abDic[abName].LoadAsset(resName, type);callBack(res);}else{//异步加载包中资源AssetBundleRequest abq = abDic[abName].LoadAssetAsync(resName, type);yield return abq;callBack(abq.asset);}}/// <summary>/// 名字 异步加载 指定资源/// </summary>/// <param name="abName"></param>/// <param name="resName"></param>/// <param name="callBack"></param>public void LoadResAsync(string abName, string resName, UnityAction<Object> callBack, bool isSync = false){StartCoroutine(ReallyLoadResAsync(abName, resName, callBack, isSync));}private IEnumerator ReallyLoadResAsync(string abName, string resName, UnityAction<Object> callBack, bool isSync){//加载主包LoadMainAB();//获取依赖包string[] strs = manifest.GetAllDependencies(abName);for (int i = 0; i < strs.Length; i++){if (!abDic.ContainsKey(strs[i])){if (isSync) //同步加载{AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);abDic.Add(strs[i], ab);}else //异步加载{//一开始异步加载就记录 如果值为null说明正在加载abDic.Add(strs[i], null);AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + strs[i]);yield return rep;//不为空加载结束abDic[strs[i]] = rep.assetBundle;}}else//证明字典中已经记录了一个AB包信息{//如果字典中记录的是null 那就证明正在加载//我们需要等待加载结束 直接使用while (abDic[strs[i]] == null){//只有发现正在加载中 就不停的等待一帧yield return 0;}}}//加载目标包if (!abDic.ContainsKey(abName)){if (isSync) //同步加载{AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);abDic.Add(abName, ab);}else{//一开始异步加载就记录 如果值为null说明正在加载abDic.Add(abName, null);AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + abName);yield return rep;abDic[abName] = rep.assetBundle;}}else{//如果字典中记录的是null 那就证明正在加载//我们需要等待加载结束 直接使用while (abDic[abName] == null){//只有发现正在加载中 就不停的等待一帧yield return 0;}}if (isSync) //同步加载{Object res = abDic[abName].LoadAsset(resName);callBack(res);}else{//异步加载包中资源AssetBundleRequest abq = abDic[abName].LoadAssetAsync(resName);yield return abq;callBack(abq.asset);}}//卸载AB包的方法public void UnLoadAB(string name,UnityAction<bool> callBackResult){if( abDic.ContainsKey(name) ){if (abDic[name] == null){//代表正在异步加载,没有卸载成功callBackResult(false);return;}abDic[name].Unload(false);abDic.Remove(name);//卸载成功callBackResult(true);}}//清空AB包的方法public void ClearAB(){//清理之前 停止协同程序StopAllCoroutines();AssetBundle.UnloadAllAssetBundles(false);abDic.Clear();//卸载主包mainAB = null;}
}
UnityWebRequest类资源加载模块
WWW 和 UnityWebRequest 都是 Unity 提供给我们的简单的访问网页的类
我们可以利用它们来进行数据的上传和下载
除此之外,它们都支持 file:// 本地文件传输协议,我们可以利用该协议来异步加载本地文件
特别是在 Android 平台时,我们无法直接通过 C# 中的 File 公共类加载 StreamingAssets 文件夹中的内容,我们需要使用 WWW 或 UnityWebRequest 类来加载
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;public class UnityWebRequestResMgr : BaseManager<UnityWebRequestResMgr>
{private UnityWebRequestResMgr() { }/// <summary>/// 利用UnityWebRequest去加载资源/// </summary>/// <typeparam name="T">资源类型只能是 string byte[] Texture AssetBundle</typeparam>/// <param name="path">路径需要自己加上协议 http:// ftp:// file://</param>/// <param name="callback">加载成功回调函数</param>/// <param name="failCallBack">加载失败回调函数</param>public void LoadRes<T>(string path, UnityAction<T> callback, UnityAction failCallBack) where T : class{MonoMgr.Instance.StartCoroutine(ReallyLoadRes<T>(path, callback, failCallBack));}private IEnumerator ReallyLoadRes<T>(string path, UnityAction<T> callback, UnityAction failCallBack) where T : class{ Type type = typeof(T);//用于加载的对象UnityWebRequest req = null;if(type == typeof(string) || type == typeof(byte[]))req = UnityWebRequest.Get(path);if(type == typeof(Texture))req = UnityWebRequestTexture.GetTexture(path);if (type == typeof(AssetBundle))req = UnityWebRequestAssetBundle.GetAssetBundle(path);else{failCallBack?.Invoke();yield break;}yield return req.SendWebRequest();//如果加载成功if (req.result == UnityWebRequest.Result.Success){if (type == typeof(string))callback?.Invoke(req.downloadHandler.text as T);else if (type == typeof(byte[]))callback?.Invoke(req.downloadHandler.data as T);else if (type == typeof(Texture))callback?.Invoke(DownloadHandlerTexture.GetContent(req) as T);else if (type == typeof(AssetBundle))callback?.Invoke(DownloadHandlerAssetBundle.GetContent(req) as T);}elsefailCallBack?.Invoke();//释放对象req.Dispose();}
}
整合EditorResMgr和ABMgr
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;/// <summary>
/// 用于进行加载AB包相关资源的整合,在开发中通过EditorResMgr去加载对应资源
/// </summary>
public class ABResMgr : BaseManager<ABResMgr>
{private bool isDebug = true;private ABResMgr() { }public void LoadResAsync<T>(string abName, string resName, UnityAction<T> callBack, bool isSync = false) where T : Object{
#if UNITY_EDITORif (isDebug){T res = EditorResMgr.Instance.LoadEditorRes<T>($"{abName}/{resName}");callBack?.Invoke(res);}else{ABMgr.Instance.LoadResAsync<T>(abName, resName, callBack, isSync);}
#elseABMgr.Instance.LoadResAsync<T>(abName, resName, callBack, isSync);
#endif}
}
