Unity协程的效果
协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。
Unity在每一帧(Frame)都会去处理对象上的协程。Unity主要是在Update后去处理协程(检查协程的条件是否满足)
协程跟Update()其实一样的,都是Unity每帧对会去处理的函数(如果有的话),至少是每帧的LateUpdate()后去运行。
在Unity的脚本声明周期中示意如下:
Unity协程的声明和使用
示例如下:
using UnityEngine;
using System.Collections;
public class Example : MonoBehaviour {
// 保持一个执行脚本的引用
private IEnumerator coroutine;
void Start () {
print("Starting " + Time.time);
//方法1:保存协程的引用,开启协程;
{
coroutine = WaitAndPrint(3.0f);
StartCoroutine(coroutine);
}
//方法2:把协程的方法作为字符串,开启协程
{
StartCoroutine("WaitAndPrint",3.0f);
}
print("Done " + Time.time);
}
// 每隔3秒,打印一次
// 由yield来挂起(暂停)函数
public IEnumerator WaitAndPrint(float waitTime) {
while (true) {
yield return new WaitForSeconds(waitTime);
print("WaitAndPrint " + Time.time);
}
}
}
可见,协程是通过IEnumerator实现的,每当yield return后就会挂起直到满足条件才继续执行后面的代码。
IEnumerator内部的实现
例如对于语句:
using System;
using System.Collections;
class Test
{
static IEnumerator GetCounter()
{
for (int count = 0; count < 10; count++)
{
yield return count;
}
}
}
C#编译器会对应生成:
internal class Test
{
// Note how this doesn't execute any of our original code
private static IEnumerator GetCounter()
{
return new <GetCounter>d__0(0);
}
// Nested type automatically created by the compiler to implement the iterator
[CompilerGenerated]
private sealed class <GetCounter>d__0 : IEnumerator<object>, IEnumerator, IDisposable
{
// Fields: there'll always be a "state" and "current", but the "count"
// comes from the local variable in our iterator block.
private int <>1__state;
private object <>2__current;
public int <count>5__1;
[DebuggerHidden]
public <GetCounter>d__0(int <>1__state)
{
this.<>1__state = <>1__state;
}
// Almost all of the real work happens here
private bool MoveNext()
{
switch (this.<>1__state)
{
case 0:
this.<>1__state = -1;
this.<count>5__1 = 0;
while (this.<count>5__1 < 10)//这里针对循环处理
{
this.<>2__current = this.<count>5__1;
this.<>1__state = 1;
return true;
Label_004B:
this.<>1__state = -1;
this.<count>5__1++;
}
break;
case 1:
goto Label_004B;
}
return false;
}
...//省略了一些方法
}
}
状态机实现代码分步执行
如何实现“当下次再调用MoveNext()方法时,我们的方法会继续从上一个yield return语句处开始执行。”?
根据上部分C#编译器生成的代码我们可以总结到:通过状态机。
对于没有for、while等循环的语句,是通过构造多个switch case语句,通过状态切换来达到分部执行代码。
private bool MoveNext()
{
switch(this.<>1_state)
{
case 0:
this.<>1_state = -1;
Console.WriteLine("第一个yield return 前的代码);
this.<>2_current = 1;
this.<>1_state = 1;
case 1:
this.<>1_state = -1;
Console.WriteLine("第二个yield return 前的代码);
this.<>2_current = 2;
this.<>1_state = 2;
case 2:
this.<>1_state = -1;
Console.WriteLine("第二个yield return 后的代码);
break;
}
return false;
}
而对于循环语句,使用goto语句来实现状态切换:
private bool MoveNext()
{
switch(this.<>1_state)
{
case 0:
this.<>1_state = -1;
this.<i>5_1 = 0;
while(this.<i>5_1 < 10)
{
this.<>2_current = this.<i>5_1;
this.<>1_state = 1;
return true;
Label_0046:
this.<>1_state = -1;
this.<i>5_1++;
}
break;
case 1:
goto Label_0046;
}
return false;
}
参考前文中的图Unity脚本生命周期,Unity在每帧都会调用某个脚本上的协程,即调用协程的MoveNext函数,当MoveNext函数返回false则结束协程,否则如果返回 true ,就从当前位置继续往下执行。
参考
- 《Unity 3D脚本编程 使用C#语言开发跨平台游戏》第八章
- Unity3D协程进阶-原理剖析: https://blog.csdn.net/yupu56/article/details/52791952