# first
在 UnityMonobehaviour 生命周期中我们可以知道 Coroutine 是每帧都会执行一次的。但是 Unity 的协程的参数传进去的是 IEnumerator
这个类型,这是怎么实现类似于开一个子线程的情况呢?首先先说明一点,Unity Dots 虽然允许多线程,但是 Unity 官方非常不推荐用在移动端,理由是多线程异常的耗电。 然后回到主题,其实 IEnumerator
是一个迭代器,其中的 MoveNext
函数可以获取到下个元素. ---- 这是通常的用法 但是其实如果这个 IEnumerator 没有返回变量的话,我们也完全可以理解为是一个可以暂停的循环,调用一次 MoveNext
循环一次。那么我们如果想要实现协程,就只需要实现两个方法,一个是逐帧执行,一个是判断时间执行。而 Unity 中每帧必执行的方法就是 Update
这个函数
# Code
# IWait.cs
Tick 代表计时,是否满足当前的条件,满足的话就代表当前 IWait 结束了,这个 Tick 是在 Update 协程时同步调用,每次负责更新当前等待的数值
1 2 3 4 5
| public interface IWait { bool Tick(); }
|
# IWaitForFrames 与 IWaitForSeconds
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class IWaitForSeconds : IWait { float _Seconds;
public IWaitForSeconds(float seconds) { _Seconds = seconds; }
public bool Tick() { _Seconds -= Time.deltaTime; return _Seconds < 0f; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| using UnityEngine;
public class IWaitForFrames : IWait { int _Frames;
public IWaitForFrames(int frames) { _Frames = frames; }
public bool Tick() { _Frames -= 1; return _Frames < 0; }
}
|
如上而言,每次 Tick 都会更新剩余秒 / 剩余帧
# CoroutineManager
协程管理器 一个 IEnumerator 函数返回的是一个迭代器类型 (同样是 Ienumerator)
想要获取当前的迭代返回值,就是 yield return IWaitForSeconds(XXX)
那个,就需要调用 (IEnumerator)ie.Current
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
| using System; using System.Collections; using System.Collections.Generic; using UnityEngine;
public class CoroutineManager : MonoBehaviour { private static CoroutineManager _inst; public static CoroutineManager Inst => _inst;
private void Awake() { _inst = this; }
private LinkedList<IEnumerator> coroutineList = new LinkedList<IEnumerator>(); public void MyStartCoroutine(IEnumerator ie) { coroutineList.AddLast(ie); }
public void MyStopCoroutine(IEnumerator ie) { try { coroutineList.Remove(ie); }catch(Exception e) { Debug.LogError(e.ToString()); } }
public void UpdateCoroutine() { var node = coroutineList.First; while (node != null) { IEnumerator ie = node.Value; bool ret = true; if(ie.Current is IWait) { var iwait = (IWait)ie.Current; if (iwait.Tick()) { ret = ie.MoveNext(); } } else { ret = ie.MoveNext(); }
if (!ret) { coroutineList.Remove(node); }
node = node.Next; } }
}
|
判断是否是某一类型用 is 其中 UpdateCoroutine
是放在 MONOBEHAVIOUR 中的 Update
函数中调用的 当然,如果当前返回类型不是 IWait 类型的,这个协程迭代器也是要继续运行下去的。所以 else 的下一个块中仍然是 ie.MoveNext 如果迭代器执行完毕了, ie.MoveNext
就会返回 false, 这时候再断掉这个协程
# RewriteCoroutine 重写协程
有了上面那个 Manager 以后,我们就可以重写协程了
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
| using System.Collections; using System.Collections.Generic; using UnityEngine;
public class RewriteCoroutine : MonoBehaviour { static RewriteCoroutine _inst; private void Awake() { _inst = this;
IEnumerator test01 = Test01(); CoroutineManager.Inst.MyStartCoroutine(test01); }
private void Update() { CoroutineManager.Inst.UpdateCoroutine(); }
static IEnumerator Test01() { Debug.Log("start test 01"); yield return new IWaitForSeconds(5); Debug.Log("after 5 seconds"); yield return new IWaitForSeconds(5); Debug.Log("after 10 seconds"); }
}
|
Done.