基础准备
我们需要使用协程来开启异步加载
在ResourceManager
中添加
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
| public class ResourceManager : SingletonPattern<ResourceManager> { protected ClassObjectPool<AsyncLoadResParam> m_AsyncLoadResParamPool = ObjectPoolManager.Instance.GetOrCreatClassPool<AsyncLoadResParam>(50); protected ClassObjectPool<AsyncCallBack> m_AsyncCallBackPool = ObjectPoolManager.Instance.GetOrCreatClassPool<AsyncCallBack>(100); protected MonoBehaviour m_StartMono; protected List<AsyncLoadResParam>[] m_LoadingAssetList = new List<AsyncLoadResParam>[(int)LoadResPriority.RES_NUM]; protected Dictionary<uint,AsyncLoadResParam> m_LoadingAssetDic = new Dictionary<uint,AsyncLoadResParam>(); public void Init(MonoBehaviour mono) { for (int i = 0; i < (int)LoadResPriority.RES_NUM; i++) { m_LoadingAssetList[i] = new List<AsyncLoadResParam>(); } m_StartMono = mono; m_StartMono.StartCoroutine(AsyncLoadCor()); } public void AsyncLoadResource(string path,OnAsyncObjFinish dealFinish,LoadResPriority priority,bool isSprite = false, object param1 = null, object param2 = null, object param3 = null,uint crc = 0) { if (crc == 0) { crc = CRC32.GetCRC32(path); } ResourceItem item = GetCacheResourceItem(crc); if (item != null) { if (dealFinish != null) { dealFinish(path,item.m_Obj,param1,param2,param3); } return; } AsyncLoadResParam para = null; if (!m_LoadingAssetDic.TryGetValue(crc, out para) || para == null) { para = m_AsyncLoadResParamPool.Spawn(true); para.m_Crc = crc; para.m_Path = path; para.m_IsSprite = isSprite; para.m_Priority = priority; m_LoadingAssetDic.Add(crc, para); m_LoadingAssetList[(int)priority].Add(para); } AsyncCallBack callBack = m_AsyncCallBackPool.Spawn(true); callBack.m_DealFinish = dealFinish; callBack.m_Param1 = param1; callBack.m_Param2 = param2; callBack.m_Param3 = param3; para.m_CallBackList.Add(callBack); }
IEnumerator AsyncLoadCor() { while(true) { yield return null; } }
} public enum LoadResPriority { RES_HIGH = 0, RES_MIDDLE = 1, RES_LOW = 2, RES_NUM = 3, } public class AsyncLoadResParam { public List<AsyncCallBack> m_CallBackList = new List<AsyncCallBack>(); public uint m_Crc; public string m_Path; public bool m_IsSprite = false; public LoadResPriority m_Priority = LoadResPriority.RES_LOW; public void Reset() { m_CallBackList.Clear(); m_Crc = 0; m_Path = string.Empty ; m_IsSprite = false; m_Priority = LoadResPriority.RES_LOW; } } public class AsyncCallBack { public OnAsyncObjFinish m_DealFinish = null; public object m_Param1 = null; public object m_Param2 = null; public object m_Param3 = null; public void Reset() { m_DealFinish = null; m_Param1 = null ; m_Param2 = null ; m_Param3 = null ; } } public delegate void OnAsyncObjFinish(string path, Object obj,object param1 = null,object param2 = null,object param3 = null);
|
解释
我们添加了AsyncLoadResParam
中间类,这个中间类通过m_AsyncLoadResParamPool
类对象池来缓存,它是异步加载列表m_LoadingAssetList
的最基本单位,其中包括了资源的crc、path以及加载优先级等
我们添加了异步资源加载的回调类AsyncCallBack
,这个类通过m_AsyncCallBackPool
类对象池来缓存,我们还添加了OnAsyncObjFinish
委托。AsyncCallBack
回调类的目的就是储存回调参数和回调方法。
当开启一个异步加载时,会先判断此资源是否已经加载完毕,如果没有,会再判断此资源是否在加载中(m_LoadingAssetDic
来记录),如果在加载中,我们只需要在AsyncLoadResParam
中间类的回调列表添加回调类AsyncCallBack
即可。之所以这么做,是因为在异步加载时同一个资源可能会被多个地方请求,在资源加载完成之前,我们需要将这些请求的回调来缓存。同步加载不这么做,是因为同步加载需要一帧内完成,所以不需要缓存。
异步加载协程
我们来补充上异步加载协程内部的内容
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
| public class ResourceManager : SingletonPattern<ResourceManager> { private const long MAXLOADRESTIME = 200000; IEnumerator AsyncLoadCor() { List<AsyncCallBack> callBackList = null; long lastYieldTime = System.DateTime.Now.Ticks; while(true) { bool haveYield =false; for (int i = 0; i < (int)LoadResPriority.RES_NUM; i++) { if (m_LoadingAssetList[(int)LoadResPriority.RES_HIGH].Count > 0) { i = (int)LoadResPriority.RES_HIGH; } else if(m_LoadingAssetList[(int)LoadResPriority.RES_MIDDLE].Count > 0) { i = (int)LoadResPriority.RES_MIDDLE; } List<AsyncLoadResParam> loadingList = m_LoadingAssetList[i]; if(loadingList.Count <= 0) { continue; } AsyncLoadResParam loadingItem = loadingList[0]; loadingList.RemoveAt(0); callBackList = loadingItem.m_CallBackList;
Object obj = null; ResourceItem resItem = null; #if UNITY_EDITOR if (!m_LoadFormAssetBundle) { if(loadingItem.m_IsSprite) { obj = LoadAssetByEditor<Sprite>(loadingItem.m_Path); } else { obj = LoadAssetByEditor<Object>(loadingItem.m_Path); } yield return new WaitForSeconds(0.5f); resItem = AssetBundleManager.Instance.FindResourceItem(loadingItem.m_Crc); if (resItem == null) { resItem = new ResourceItem { m_Crc = loadingItem.m_Crc, }; } } #endif if(obj == null) { resItem = AssetBundleManager.Instance.LoadResourceAssetBundle(loadingItem.m_Crc); if(resItem != null && resItem.m_AssetBundle != null) { AssetBundleRequest abRequest = null; if (loadingItem.m_IsSprite) { abRequest = resItem.m_AssetBundle.LoadAssetAsync<Sprite>(resItem.m_AssetName); } else {
abRequest = resItem.m_AssetBundle.LoadAssetAsync(resItem.m_AssetName); } yield return abRequest; if(abRequest.isDone) { obj = abRequest.asset; } lastYieldTime = System.DateTime.Now.Ticks; } } CacheResource(loadingItem.m_Path,ref resItem,loadingItem.m_Crc,obj,callBackList.Count); for (int j = 0; j < callBackList.Count; j++) { AsyncCallBack callBack = callBackList[j]; if(callBack != null && callBack.m_DealFinish != null) { callBack.m_DealFinish(loadingItem.m_Path, obj, callBack.m_Param1, callBack.m_Param2, callBack.m_Param3); callBack.m_DealFinish = null; } callBack.Reset(); m_AsyncCallBackPool.Recycle(callBack); }
obj = null; callBackList.Clear(); m_LoadingAssetDic.Remove(loadingItem.m_Crc);
loadingItem.Reset(); m_AsyncLoadResParamPool.Recycle(loadingItem);
if (System.DateTime.Now.Ticks - lastYieldTime > MAXLOADRESTIME) { yield return null; lastYieldTime = System.DateTime.Now.Ticks; haveYield = true; } } if(!haveYield || System.DateTime.Now.Ticks - lastYieldTime > MAXLOADRESTIME) { lastYieldTime = System.DateTime.Now.Ticks; yield return null; } } } }
|
解释
异步加载的逻辑是:
在一个大循环(while(true)
)内,将m_LoadingAssetList
列表内部的资源按照“RES_HIGH
-RES_MIDDLE
-RES_LOW
” 的顺序加载出来。
注意我们的AsyncLoadResParam
有一个m_IsSprite
变量,我们读取Sprite的时候需要单独设定,因为Sprite不能通过as关键字转换出来,那样做的话as出来的是Texture2D而不是Sprite。
异步加载演示
如果想要异步加载,我们首先要初始化ResourceManager
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
| using UnityEngine;
public class GameStart : MonoBehaviour { public AudioSource m_AudioSource; private AudioClip m_AudioClip; private void Awake() { AssetBundleManager.Instance.LoadAssetBundleConfig(); ResourceManager.Instance.Init(this); } private void Start() { ResourceManager.Instance.AsyncLoadResource("Assets/GameData/Sounds/menusound.mp3", OnLoadFinish, LoadResPriority.RES_MIDDLE); } void OnLoadFinish(string path,Object obj,object param1,object param2,object param3) { m_AudioClip = (AudioClip)obj; m_AudioSource.clip = m_AudioClip; m_AudioSource.Play(); } private void Update() { if(Input.GetKeyDown(KeyCode.A)) { m_AudioSource.Stop(); ResourceManager.Instance.ReleaseResource(m_AudioClip,true); m_AudioSource.clip = null; m_AudioClip = null; } }
}
|
我们可以在Profiler中点击Memory——切换为Detailed——再点击Make Sample,查看我们的AudioClip在内存中的引用,这是我们按下A键,由于我们调用了ResourceManager.Instance.ReleaseResource(m_AudioClip,true);
,第二个参数true代表着我们将此资源完全清除,这时我们再Make Sample,AudioClip已经不在内存中了。
注意,我们可以修改ResourceManager
的m_LoadFromAssetBundle
来测试编辑器下加载和AssetBundle加载的情况。