用C# 模拟实现unity里的协程
注:需要了解C#的迭代器,不然很难理解。
之前面试有被问到unity协程的原理,以及撇开unity用纯C#去实现协程的方法。后来了解一下,确实可以的。趁这会有空,稍微总结一下。
还是结合代码说事吧:
1 /// <summary>
2 /// 等待接口
3 /// </summary>
4 public interface IWait
5 {
6 /// <summary>
7 /// 每帧检测是否等待结束
8 /// </summary>
9 /// <returns></returns>
10 bool Tick();
11 }
先定义一个等待接口,WaitForSeconds 和 WaitForFrames 实现接口的Tick()方法,每一帧调用Tick()方法检测是否等待结束
1 /// <summary> 2 /// 按秒等待 3 /// </summary> 4 public class WaitForSeconds:IWait 5 { 6 float _seconds = 0f; 7 8 public WaitForSeconds(float seconds) 9 { 10 _seconds = seconds; 11 } 12 13 public bool Tick() 14 { 15 _seconds -= Time.deltaTime; 16 return _seconds <= 0; 17 } 18 }
1 /// <summary> 2 /// 按帧等待 3 /// </summary> 4 public class WaitForFrames:IWait 5 { 6 private int _frames = 0; 7 public WaitForFrames(int frames) 8 { 9 _frames = frames; 10 } 11 12 public bool Tick() 13 { 14 _frames -= 1; 15 return _frames <= 0; 16 } 17 }
定义 WaitForSeconds 和 WaitForFrames ,在构造函数初始化需要等待的时间/帧数
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Text;
6 using System.Threading;
7 using System.Threading.Tasks;
8
9 public class CoroutineManager
10 {
11 private static CoroutineManager _instance = null;
12 public static CoroutineManager Instance
13 {
14 get
15 {
16 if (_instance == null)
17 {
18 _instance = new CoroutineManager();
19 }
20 return _instance;
21 }
22 }
23
24 private LinkedList<IEnumerator> coroutineList = new LinkedList<IEnumerator>();
25
26 public void StartCoroutine(IEnumerator ie)
27 {
28 coroutineList.AddLast(ie);
29 }
30
31 public void StopCoroutine(IEnumerator ie)
32 {
33 try
34 {
35 coroutineList.Remove(ie);
36 }
37 catch (Exception e) { Console.WriteLine(e.ToString()); }
38 }
39
40 public void UpdateCoroutine()
41 {
42 var node = coroutineList.First;
43 while (node != null)
44 {
45 IEnumerator ie = node.Value;
46 bool ret = true;
47 if (ie.Current is IWait)
48 {
49 IWait wait = (IWait)ie.Current;
50 //检测等待条件,条件满足,跳到迭代器的下一元素 (IEnumerator方法里的下一个yield)
51 if (wait.Tick())
52 {
53 ret = ie.MoveNext();
54 }
55 }
56 else
57 {
58 ret = ie.MoveNext();
59 }
60 //迭代器没有下一个元素了,删除迭代器(IEnumerator方法执行结束)
61 if (!ret)
62 {
63 coroutineList.Remove(node);
64 }
65 //下一个迭代器
66 node = node.Next;
67 }
68 }
69 }
这一段代码是这里最重要的部分。这里用一个链表记录了添加的所有“协程”,并在UpdateCoroutine()方法的每一次执行都去遍历这些“协程”以及检测等待是否结束。
1 public class Time
2 {
3 //每帧时间(秒)
4 public static float deltaTime
5 { get { return (float)deltaMilliseconds / 1000; } }
6 //每帧时间(毫秒)
7 public static int deltaMilliseconds
8 { get { return 20; }}
9 }
模拟一帧的时间。
运用测试:
1 public class Program
2 {
3 static void Main(string[] args)
4 {
5 var t1 = Test01();
6 var t2 = Test02();
7 CoroutineManager.Instance.StartCoroutine(t1);
8 CoroutineManager.Instance.StartCoroutine(t2);
9
10 while (true)// 模拟update
11 {
12 Thread.Sleep(Time.deltaMilliseconds);
13 CoroutineManager.Instance.UpdateCoroutine();
14 }
15 }
16
17
18 static IEnumerator Test01()
19 {
20 Console.WriteLine("start test 01");
21 yield return new WaitForSeconds(5);
22 Console.WriteLine("after 5 seconds");
23 yield return new WaitForSeconds(5);
24 Console.WriteLine("after 10 seconds");
25 }
26
27 static IEnumerator Test02()
28 {
29 Console.WriteLine("start test 02");
30 yield return new WaitForFrames(500);
31 Console.WriteLine("after 500 frames");
32 }
33 }
测试结果:
用秒表掐了 一下,好像没什么毛病。