1. 概述
在上一节(基于迭代器的资源异步加载设计)中,我们封装了各基础类型资源的加载接口。本节我们将对批量资源的载入进行管理,实现串/行可控的通用加载器。
2. 主要代码实现
- BaseLoadOperation 异步加载基类
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 |
using System; using System.Collections; namespace GEX.Resource { public abstract class BaseLoadOperation : IEnumerator { /// <summary> /// 加载进度 /// </summary> public float progress { get; protected set; } /// <summary> /// 加载完成时的回调 /// </summary> public Action<BaseLoadOperation> OnFinish; /// <summary> /// 资源路径 /// </summary> public string assetPath { get; protected set; } protected bool hasLoaded = false; public BaseLoadOperation() {} public BaseLoadOperation(string path) { this.assetPath = path.ToLower(); } public virtual bool MoveNext() { if (!hasLoaded) { hasLoaded = true; this.OnLoad(); } return !IsDone(); } public virtual void Reset() { } public virtual object Current { get { return null; } } public abstract void OnLoad(); public abstract bool IsDone(); public abstract T GetAsset<T>() where T : UnityEngine.Object; protected virtual void onFinishEvent() { this.Finish(this); } public void Finish(BaseLoadOperation loader) { progress = 1.0f; if (OnFinish != null) { OnFinish.Invoke(loader); OnFinish = null; } } } public class AsyncLoaderData { public int weight; public BaseLoadOperation loader; } } |
- SerialLoader 用于支持串行模式的载入
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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
using UnityEngine; using System.Collections; using System.Collections.Generic; namespace GEX.Resource { /// <summary> /// 串形异步加载容器,容器内部自动缓存加载器,需要手动释放 /// 使用方法: /// var async = new SerialAsync(); /// /// async.AddLoader("xxx/press/yy.prefab"); /// async.AddLoader("xxx/pres/zzz.ogg" , 5); /// /// while(async.MoveNext()) /// { /// if(async.CurrentLoader.IsDone()){ /// var loader = async.CurrentLoader; /// var gameObj = GameObject.Instantiate(loader.GetAsset<GameObject>()); /// } /// // progress /// if(progressAction != null) /// progressAction.Invoke(async.Progress); /// yield return null; /// } /// </summary> public class SerialLoader : BaseLoadOperation { /// <summary> /// 权重,占总场景资源量的比重 /// </summary> public int weight { get; private set; } private int completeWeight; private List<AsyncLoaderData> assets = new List<AsyncLoaderData>(); private AsyncLoaderData curLoader; private AsyncLoaderData nextLoader; private int moveIndex; //缓存 private Dictionary<BaseLoadOperation, AsyncLoaderData> cacheLoader; public SerialLoader() { cacheLoader = new Dictionary<BaseLoadOperation, AsyncLoaderData>(); } /// <summary> /// 添加需要被加载的资源 /// </summary> /// <param name="loader">异步加载器</param> /// <param name="weight">权重,用于计算进度</param> public BaseLoadOperation AddLoader(BaseLoadOperation loader, int weight = 1) { this.weight += weight; AsyncLoaderData asyncLoader = new AsyncLoaderData(); asyncLoader.weight = weight; asyncLoader.loader = loader; this.assets.Add(asyncLoader); if (nextLoader == null) { nextLoader = asyncLoader; curLoader = nextLoader; } return loader; } /// <summary> /// 添加加载数据 /// </summary> /// <param name="path">资源路径,类似"Assets/Res/XXXX.yyy"</param> /// <param name="weight">权重,用于计算进度</param> public BaseLoadOperation AddLoader(string path, int weight = 1) { BaseLoadOperation loadOpt = GResource.LoadAssetAsync(path); this.AddLoader(loadOpt, weight); return loadOpt; } public override bool MoveNext() { if (assets.Count <= 0) return false; bool isMoveNext = false; AsyncLoaderData temLoader = null; if (cacheLoader.TryGetValue(nextLoader.loader, out temLoader)) { nextLoader.loader.Finish(temLoader.loader); isMoveNext = true; }else isMoveNext = !nextLoader.loader.MoveNext(); if (isMoveNext) { moveIndex++; curLoader = nextLoader; if (!cacheLoader.TryGetValue(curLoader.loader, out temLoader)) { cacheLoader[curLoader.loader] = curLoader; } if (moveIndex < assets.Count) { completeWeight += nextLoader.weight; nextLoader = assets[moveIndex]; } } float completedProgress = completeWeight + nextLoader.loader.progress * nextLoader.weight; progress = completedProgress / weight; return IsDone() == false; } public BaseLoadOperation CurrentLoader { get { return curLoader.loader; } } public override object Current { get { return CurrentLoader; } } public override bool IsDone() { bool result = moveIndex >= assets.Count; if(result) this.onFinishEvent(); return result; } public override void Reset() { moveIndex = 0; for (int i = 0; i < assets.Count; i++) { assets[i].loader.Reset(); assets[i].loader.OnFinish = null; } assets.Clear(); cacheLoader.Clear(); nextLoader = null; curLoader = null; progress = 0; weight = 0; } public override void OnLoad() { throw new System.NotImplementedException(); } public override T GetAsset<T>() { throw new System.NotImplementedException(); } } } |
- ParallerLoader 用于支持并行模式载入
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 124 125 126 127 128 129 130 131 132 133 134 |
using System.Collections.Generic; namespace GEX.Resource { /// <summary> /// 并行异步加载容器,容器内部自动缓存加载器,需要手动释放 /// 使用方法: /// var async = new ParallelAsync(); /// /// async.AddLoader("xxx/press/yy.prefab"); /// async.AddLoader("xxx/pres/zzz.ogg" , 5); /// /// while(async.MoveNext()) /// { /// // progress /// if(progressAction != null) /// progressAction.Invoke(async.Progress); /// yield return null; /// } /// </summary> public class ParallelLoader : BaseLoadOperation { /// <summary> /// 权重,占总场景资源量的比重 /// </summary> public int weight { get; private set; } private float completeWeight; private int maxParallelCount = 5; private List<AsyncLoaderData> loaders = new List<AsyncLoaderData>(); public ParallelLoader() : base("") { } public ParallelLoader(int maxParallelCount) : this() { this.maxParallelCount = maxParallelCount; } /// <summary> /// 添加需要被加载的资源 /// </summary> /// <param name="loader">异步加载器</param> /// <param name="weight">权重,用于计算进度</param> public BaseLoadOperation AddLoader(BaseLoadOperation loader, int weight = 1) { this.weight += weight; AsyncLoaderData asyncLoader = new AsyncLoaderData(); asyncLoader.weight = weight; asyncLoader.loader = loader; this.loaders.Add(asyncLoader); return loader; } /// <summary> /// 添加加载数据 /// </summary> /// <param name="path">资源路径,类似"Assets/Res/XXXX.yyy"</param> /// <param name="weight">权重,用于计算进度</param> public BaseLoadOperation AddLoader(string path, int weight = 1) { BaseLoadOperation loadOpt = GResource.LoadAssetAsync(path); this.AddLoader(loadOpt, weight); return loadOpt; } public override void OnLoad() { throw new System.NotImplementedException(); } public override T GetAsset<T>() { throw new System.NotImplementedException(); } public override bool MoveNext() { if (loaders.Count <= 0) return false; float weigeting = 0; for (int i = 0; i < loaders.Count; ) { if (i >= maxParallelCount) break; AsyncLoaderData asynloader = loaders[i]; if (!asynloader.loader.MoveNext()) { completeWeight += asynloader.weight; loaders.RemoveAt(i); } else { weigeting += asynloader.loader.progress * asynloader.weight; i++; } } progress = (weigeting + completeWeight) / weight; return IsDone() == false; } public override object Current { get { return null; } } public override bool IsDone() { bool result = loaders.Count <= 0; if(result) this.onFinishEvent(); return result; } public override void Reset() { for (int i = 0; i < loaders.Count; i++) { loaders[i].loader.Reset(); } loaders.Clear(); weight = 0; } } } |
- StageLoader 用于支持场景切换载入
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 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
using System.Collections; using System.Collections.Generic; namespace GEX.Resource { /// <summary> /// 异步加载场景 /// </summary> public class StageLoader : IEnumerator { /// <summary> /// 权重,占总场景资源量的比重 /// </summary> public int weight { get; private set; } /// <summary> /// 当前加载总进度 /// </summary> public float Progress { get; private set; } private int completeWeight; private List<AsyncLoaderData> assets = new List<AsyncLoaderData>(); private AsyncLoaderData curLoader; private AsyncLoaderData nextLoader; private int moveIndex; private LoadSceneAsync sceneLoader; public LoadSceneAsync SceneLoader { get { return sceneLoader; } } public StageLoader() { } public StageLoader(string sceneName) { sceneLoader = new LoadSceneAsync(sceneName); this.AddLoader(sceneLoader , UnityEngine.Random.Range(70, 90)); } /// <summary> /// 添加场景资源加载器 /// </summary> /// <param name="loader">加载器</param> /// <param name="weight">资源权重</param> public BaseLoadOperation AddLoader(BaseLoadOperation loader , int weight) { this.weight += weight; AsyncLoaderData asyncLoader = new AsyncLoaderData(); asyncLoader.weight = weight; asyncLoader.loader = loader; this.assets.Add(asyncLoader); if (nextLoader == null) { nextLoader = asyncLoader; curLoader = nextLoader; } return loader; } /// <summary> /// 批量添加资源加载器 /// </summary> /// <param name="loaders">加载器集合</param> /// <param name="weight">总资源权重</param> public void AddRangeLoader(BaseLoadOperation[] loaders , int weight) { int childWeight = weight/loaders.Length; this.weight += childWeight * loaders.Length; for (int i = 0; i < loaders.Length; i++) { AsyncLoaderData asyncLoader = new AsyncLoaderData(); asyncLoader.weight = childWeight; asyncLoader.loader = loaders[i]; this.assets.Add(asyncLoader); } if (nextLoader == null) { nextLoader = this.assets[0]; curLoader = nextLoader; } } /// <summary> /// 自定义排序规则 /// </summary> public void Sort(IComparer<AsyncLoaderData> comparer) { this.assets.Sort(comparer); } public bool MoveNext() { if (assets.Count <= 0) return false; if (!nextLoader.loader.MoveNext()) { moveIndex++; curLoader = nextLoader; if (moveIndex < assets.Count) { completeWeight += nextLoader.weight; nextLoader = assets[moveIndex]; } } float completedProgress = completeWeight + nextLoader.loader.progress * nextLoader.weight; Progress = completedProgress / weight; return IsDone() == false; } /// <summary> /// 立即激活场景切换 /// </summary> public void OnActiveImmediate() { if (sceneLoader == null) return; sceneLoader.AsyncSceneLoader.allowSceneActivation = true; } /// <summary> /// 异步场景是否加载完毕 /// </summary> /// <returns></returns> public bool IsSceneDone() { if (sceneLoader == null) return true; return sceneLoader.AsyncSceneLoader.isDone; } public object Current { get { return curLoader.loader; } } public bool IsDone() { return moveIndex > assets.Count; } public void Reset() { if(sceneLoader != null) sceneLoader.Reset(); sceneLoader = null; for (int i = 0; i < assets.Count; i++) { assets[i].loader.Reset(); } assets.Clear(); curLoader = null; nextLoader = null; } } } |
3. 测试代码
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 |
using System.Collections; using System.Collections.Generic; using GEX.Resource; using UnityEngine; public class TestAssetLoader : MonoBehaviour { private BaseLoadOperation curLoader; // Use this for initialization void Start () { } // Update is called once per frame void Update () { } void OnGUI() { GUILayout.Label("Parllerl Or Serial Loader:"); if (GUILayout.Button("SerialLoader")) { TestSerialLoader(); } if (GUILayout.Button("ParllerlLoader")) { TestParallelLoader(); } if(curLoader != null) GUILayout.Label("Progress:" + curLoader.progress); } private void TestSerialLoader() { SerialLoader loader = new SerialLoader(); curLoader = loader; loader.AddLoader("Models/GO1.prefab").OnFinish = InstanceGameObject; loader.AddLoader(new LoadDelayAsync(0.5f)).OnFinish = PrintTime; loader.AddLoader("Models/GO2.prefab").OnFinish = InstanceGameObject; loader.AddLoader(new LoadDelayAsync(1.0f)).OnFinish = PrintTime; loader.AddLoader("Models/GO3.prefab").OnFinish = InstanceGameObject; loader.AddLoader(new LoadDelayAsync(1.5f)).OnFinish = PrintTime; loader.AddLoader("Models/GO4.prefab").OnFinish = InstanceGameObject; this.StartCoroutine(loader); } private void TestParallelLoader() { ParallelLoader loader = new ParallelLoader(); curLoader = loader; loader.AddLoader("Models/GO1.prefab").OnFinish = InstanceGameObject; loader.AddLoader(new LoadDelayAsync(0.5f)).OnFinish = PrintTime; loader.AddLoader("Models/GO2.prefab").OnFinish = InstanceGameObject; loader.AddLoader(new LoadDelayAsync(1.0f)).OnFinish = PrintTime; loader.AddLoader("Models/GO3.prefab").OnFinish = InstanceGameObject; loader.AddLoader(new LoadDelayAsync(1.5f)).OnFinish = PrintTime; loader.AddLoader("Models/GO4.prefab").OnFinish = InstanceGameObject; loader.AddLoader("Models/GO1.prefab").OnFinish = InstanceGameObject; loader.AddLoader("Models/GO2.prefab").OnFinish = InstanceGameObject; loader.AddLoader("Models/GO3.prefab").OnFinish = InstanceGameObject; loader.AddLoader("Models/GO4.prefab").OnFinish = InstanceGameObject; this.StartCoroutine(loader); } private void InstanceGameObject(BaseLoadOperation loadOpt) { GameObject.Instantiate(loadOpt.GetAsset<GameObject>()); } private void PrintTime(BaseLoadOperation loadOpt) { Debug.Log("Time:" + Time.realtimeSinceStartup); } } |