# 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.

更新于

请我喝[茶]~( ̄▽ ̄)~*

Solvarg 微信支付

微信支付

Solvarg 支付宝

支付宝

Solvarg 贝宝

贝宝