协程一般可以分为两种,一种是只执行一次,比如加载资源或请求网络,一种是某些操作流程或动画,可以执行多次,协程管理器主要管理的就是后者,对可复用的协程进行缓存和统一管理。
懒汉模式Mono单例
协程管理器使用懒汉模式比较好。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| using UnityEngine;
public class LazyMonoSing<T> : MonoBehaviour where T : MonoBehaviour { private static T instance; public static T Instance { get { if (instance == null) { GameObject go = new GameObject(typeof(T).Name); instance = go.AddComponent<T>(); DontDestroyOnLoad(go); } return instance; } } }
|
协程管理
CoroutineMgr
协程管理器只负责协程的缓存逻辑,并对外提供调用接口,具体的执行交给CoroutineController
和CoroutineItem
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
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class CoroutineMgr : LazyMonoSing<CoroutineMgr> { private Dictionary<int,CoroutineController> _controllers = new Dictionary<int,CoroutineController>(); public int RegisterCoroutine(IEnumerator coroutine,bool autoStart = true) { CoroutineController controller = new CoroutineController(this,coroutine);
_controllers.Add(controller.ID, controller);
if (autoStart) StartCo(controller.ID);
return controller.ID; } public void ExecuteOnce(IEnumerator coroutine) { CoroutineController controller = new CoroutineController(this,coroutine); controller.Start(); } public void ReStartCo(int id) { var controller = GetController(id); controller?.Restart(); } public void StartCo(int id) { var controller = GetController(id); controller?.Start(); } public void StopCo(int id) { var controller = GetController(id); controller?.Stop(); } public void PauseCo(int id) { var controller = GetController(id); controller?.Pause(); } public void ContinueCo(int id) { var controller = GetController(id); controller?.Continue(); } private CoroutineController GetController(int id) { CoroutineController controller; if (_controllers.TryGetValue(id, out controller)) { return controller; } else { Debug.LogError("当前协程ID不存在:" + id); } return null; } }
|
CoroutineController
负责管理协程的流程,每一个协程都会被CoroutineController
和CoroutineItem
管理。MonoBehaviour
提供的协程函数只负责开启和关闭
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
| using System.Collections; using UnityEngine;
public class CoroutineController { private static int _id; public int ID { get; private set; } private CoroutineItem _item; private MonoBehaviour _mono; private IEnumerator _coroutine; private Coroutine _monoCoroutine; public CoroutineController(MonoBehaviour mono,IEnumerator coroutine) { _item = new CoroutineItem(); _mono = mono; _coroutine = coroutine; ResetData();
} public void Start() { _item.CurState = CoroutineItem.CoroutineState.RUNNING; _monoCoroutine = _mono.StartCoroutine(_item.Body(_coroutine)); } public void Pause() { _item.CurState = CoroutineItem.CoroutineState.PAUSED; } public void Stop() { _item.CurState = CoroutineItem.CoroutineState.STOP; } public void Continue() { _item.CurState = CoroutineItem.CoroutineState.RUNNING; } public void Restart() { if(_monoCoroutine != null) _mono.StopCoroutine(_monoCoroutine);
Start(); } private void ResetData() { ID = _id++; } }
|
注意协程的执行逻辑,每一次yield return都会给IL一个“标记”,告诉满足了“yield return”后的条件后,下一次执行函数的入口,下一次把“入口”后的逻辑都执行完毕后,整个协程会从IEnumerator
函数入口重新开始,直到没有任何逻辑需要执行为止。
CoroutineItem
负责提供每个具体协程的状态。Unity的协程函数只有开启和关闭等简单的调用,我们必须自己包装自己的协程,来提供每个协程运行的具体状态。
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
| using System.Collections;
public class CoroutineItem { public enum CoroutineState { WAITTING, RUNNING, PAUSED, STOP } public CoroutineState CurState { get; set; } public IEnumerator Body(IEnumerator routine) { while(CurState == CoroutineState.WAITTING) { yield return null; } while (CurState == CoroutineState.RUNNING) { if(CurState == CoroutineState.PAUSED) { yield return null; } else { if(routine != null && routine.MoveNext()) { yield return routine.Current; } else { CurState = CoroutineState.STOP; } } } } }
|
使用Body
方法包装好每一个具体执行的协程,并提供了CurState
属性。注意Body
函数内部的写法,我们需要明白协程调用的逻辑才会明白为什么这么写。
注意迭代器IEnumerator
接口内部MoveNext
、Current
的含义,在C#运行时,它的协程属于无栈协程,每次都要设定MoveNext
来指定下一次调用的入口。
注意这里if(CurState == CoroutineState.PAUSED)
写在了while (CurState == CoroutineState.RUNNING)
内部,这是因为一般情况下,这个协程会一直在Running状态下跑直到Stop状态,如果我们想Pause协程,写在Running内部并yield return null会让协程下次的入口在Body
函数开始的部分等待,而且也不会进入函数,更加节省计算。