协程一般可以分为两种,一种是只执行一次,比如加载资源或请求网络,一种是某些操作流程或动画,可以执行多次,协程管理器主要管理的就是后者,对可复用的协程进行缓存和统一管理。

懒汉模式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

协程管理器只负责协程的缓存逻辑,并对外提供调用接口,具体的执行交给CoroutineControllerCoroutineItem

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

负责管理协程的流程,每一个协程都会被CoroutineControllerCoroutineItem管理。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; }//通过全局唯一的静态变量_id自增来获取当前Contoller的ID
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接口内部MoveNextCurrent的含义,在C#运行时,它的协程属于无栈协程,每次都要设定MoveNext来指定下一次调用的入口。

注意这里if(CurState == CoroutineState.PAUSED)写在了while (CurState == CoroutineState.RUNNING)内部,这是因为一般情况下,这个协程会一直在Running状态下跑直到Stop状态,如果我们想Pause协程,写在Running内部并yield return null会让协程下次的入口在Body函数开始的部分等待,而且也不会进入函数,更加节省计算。