Coroutine的参数都是IEnumerator类型的。
它是个interface,有Current属性、MoveNext方法 和 Reset方法。
实际操作类似foreach,即每次 MoveNext 之后得到 Current,执行 Current,然后等下一帧被调用,如此往复,直到 MoveNext 返回 false(表示执行完毕),释放掉IEnumerator即可。
yield 语句可以暂停协同程序的执行,yield 的返回值指定在什么时候 resume 协同程序
- 暂停协同程序,下一帧再继续往下执行
yield return null
- 暂停协同程序,等到下一次调用 FixedUpdate 方法时再继续往下执行
yield return new WaitForFixedUpdate();
yield return new WaitForEndOfFrame();
- 暂停协同程序,等到 n 秒之后再继续往下执行
yield return new WaitForSeconds(n);
yield return new WaitForSecondsRealtime(n);
暂停协同程序,开启其它协同程序(OtherCoroutineMethod),直到 OtherCoroutineMethod 执行完毕再继续往下执行
yield return StartCoroutine("OtherCoroutineMethod")
暂停协同程序,直到条件满足
static bool condition = true;
yield return new WaitUntil(() => condition == true);
- 当条件满足时,暂停协同程序
static bool condition = true;
yield return new WaitWhile(()=> condition == true);
游戏中可以用得到yield的场景:
- 游戏结算分数时,分数从0逐渐上涨,而不是直接显示最终分数
- 人物对话时,文字一个一个很快的出现,而不是一下突然出现
- 10、9、8……0的倒计时
- 某些游戏(如拳皇)掉血时血条UI逐渐减少,而不是突然降低到当前血量
using UnityEngine;
using System.Collections;
public class dialog_yield : MonoBehaviour {
public string dialogStr = "0123456789012345678901234567890123456789";
public float speed = 5.0f;
// Use this for initialization
void Start () {
StartCoroutine(ShowDialog());
}
// Update is called once per frame
void Update () {
}
IEnumerator ShowDialog(){
float timeSum = 0.0f;
while(guiText.text.Length < dialogStr.Length){
timeSum += speed * Time.deltaTime;
guiText.text = dialogStr.Substring(0, System.Convert.ToInt32(timeSum));
yield return null;
}
}
}
注意:协程不是线程,也不是异步执行的。协程和 MonoBehaviour 的 Update函数一样也是在MainThread中执行的。使用协程你不用考虑同步和锁的问题。
UnityGems.com给出了协程的定义:
A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.
协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。Unity在每一帧(Frame)都会去处理对象上的协程。Unity主要是在Update后去处理协程(检查协程的条件是否满足):
从上图的剖析就明白,协程跟Update()其实一样的,都是Unity每帧对会去处理的函数(如果有的话)。如果MonoBehaviour 是处于激活(active)状态的而且yield的条件满足,就会协程方法的后面代码。还可以发现:如果在一个对象的前期调用协程,协程会立即运行到第一个 yield return 语句处,如果是 yield return null ,就会在同一帧再次被唤醒。如果没有考虑这个细节就会出现一些奇怪的问题。
- 协程和Update()一样更新,自然可以使用Time.deltaTime了,而且这个Time.deltaTime和在Update()当中使用是一样的效果(使用yield return null的情况下)
- 协程并不是多线程,它和Update()一样是在主线程中执行的,所以不需要处理线程的同步与互斥问题
- yield return null其实没什么神奇的,只是unity3d封装以后,这个协程在下一帧就被自动调用了
- 可以理解为ShowDialog()构造了一个自己的Update(),因为yield return null让这个函数每帧都被调用了
void Start () {
Debug.Log("start1");
StartCoroutine(Test());
Debug.Log("start2");
}
IEnumerator Test()
{
Debug.Log("test1");
yield return StartCoroutine(DoSomething());
Debug.Log("test2");
}
IEnumerator DoSomething()
{
Debug.Log("load 1");
yield return null;
Debug.Log("load 2");
}
执行结果:
start1
test1
load1
start2
load2
test2
在 GameObject 中 StartCoroutine,在这个 Coroutine 还未执行完毕之前 Destroy 掉这个 GameObject,可以发现 Coroutine 虽然还没执行完,但也没了,因为没人再去调用了。