写在开始:本篇文章不讨论yield return 语句与StartCoroutine 语句的比较深层的东西,网上相应的文章不胜枚举,大家还请自己去看。
这里我只想说一下以上两个语句在实际中的使用,以及需要注意的情况。
C#中yield return 语句的用处很广,它的主要作用是在合适的时间给你一定的返回结果,之后当再次进入该方法时,在上一个中断的地方进行,在这里我给出一个有限状态机的例子进行说明:
private IEnumerator FSM(){
while(_alive){
switch(_state){
case State.Init:
Init ();
break;
case State.Setup:
Setup ();
break;
case State.Search:
Search ();
break;
case State.Attack:
Attack ();
break;
}
yield return null;
}
}
public void OnTriggerEnter(Collider other){
if (other.CompareTag("Player")){
target = other.transform;
_alive = true;
FSM ();
}
}
public void OnTriggerExit(Collider other){
if (other.CompareTag("Player")){
target = null;
_alive = false;
}
}
以上是一个敌人的AI片段,当Player进入触发器时,FSM()开始执行;
进行状态判别,进行相应的处理;
最后 使用yield return 语句来终止当前循环的执行,该条语句保存了当前的执行情况;
而 null 则是yield return之后的 参数的几种情况之一,yield return null 会使该函数在游戏的下一帧继续执行。
yield return 语句更有用的地方体现在 事件延时 上,以下
还有其他几种参数,官方文档如下(中文为个人翻译):
yield return null; The coroutine will continue after all Update functions have been called on the next frame.
该Cotoutine 会在下一帧所有的Update()函数调用过之后继续
yield return WaitForSeconds(2); Continue after a specified time delay, after all Update functions have been called for the frame.
在指定的时间延迟之后,在该帧(延迟过后的那一帧)所有update()函数调用完后执行。
yield return WaitForFixedUpdate(); Continue after all FixedUpdate has been called on all scripts.
在所有的FixedUpdate()函数(所有的脚本)调用之后继续
yield return WWW ;Continue after a WWW download has completed
在WWW对象下载完成后进行。
以上的各种参数,为简便见,我们可以将其看做一个事件,而yield return 的作用就是 :
①注册该事件 (告诉程序这里有一个事件需要处理);
②从该函数跳出,返回到上一级函数(调用该函数的函数)处继续执行。
③在每一帧特定时间处,查询参数是否已经满足了条件,如(过了2秒,WWW对象已经下载完了),如果满足条件,程序会从yield return XXX之后继续执行,知道完成或者遇见 另一个 yield return
此处的特定时间,不同的博客有不同的说法,有的说在update()之后,也有的说亲测在LateUpdate()之后,可能版本不同的问题,不过大多数时候对使用都无影响
这里随便说几种使用情况:比如炸弹延时爆炸,数字倒计时,HP缓慢下跌
Unity中的协程真的是非常棒的东西,搭配yield return 使用。
通常使用方法是
void XXXFunc(){
StartCoroutine("YYYFunc"); // or StartCoroutine(YYYFunc());
}
IEnumerator YYYFunc(){
XXX;
yield return xxx; // or yield return StartCoroutine(MyFunc);
other;
}
可以使用Coroutine链,即嵌套使用StartCoroutine()函数。↓
yield return StartCoroutine(MyFunc); Chains the coroutine, and will wait for the MyFunc coroutine to complete first.
coroutine 链,等待MyFunc coroutine 完成。
有两种使用的情况:
①主流程函数返回值不是IEnumerator类型的:↓
private float _timer = 0f;
void Start () {
Debug.Log ("start");
StartCoroutine ("test");
StartCoroutine (Wait(3f));
Debug.Log ("start off");
}
IEnumerator Wait(float duration)
{
for(float timer = 0; timer < duration; timer += Time.deltaTime) {
yield return null;
}
Debug.Log ("wait ok");
}
IEnumerator test(){
Debug.Log ("test");
yield return StartCoroutine("test2");
Debug.Log ("test off ");
}
IEnumerator test2(){
Debug.Log ("test2");
yield return new WaitForSeconds(2f);
Debug.Log ("test2 off");
}
测试图如下:
该例简单说一下,执行顺序很明显是test()->test2().执行到 yield return 时,返回至Start()向下执行。
进入Wait(),wait()很快 到yield return null,返回,继续执行 start off。
之后每帧都访问判断 等待事件是否完成,如果完成,继续向下执行,由于wait 3 s,而test2 只需 2s ,所以test2 ->test系列先执行完,然后是wait ok
流程为:start ->test -> test2 -> wait -> start off ->(test2 wait 2 s)->test2 off ->test off -> (Wait wait 3-2 =1 s) -> wait ok
②主流程函数返回值是IEnumerator类型的:↓
private float _timer = 0f;
IEnumerator Start () {
Debug.Log ("start");
yield return StartCoroutine ("test");
yield return StartCoroutine (Wait(3f));
Debug.Log ("start off");
}
// Start()函数改变,其余函数同上
在该例中,我们可以很明显的看出,Start()是等 其中的每一个协程Coroutine执行完之后才去执行该协程后的语句。
流程是这样的:Start ->test -> test2 ->(wait 2 second) -> test2 off ->test off ->wait -> (wait 3 s) ->start off
这也验证了关于协程链Coroutine chains 的介绍:在其中的子协程先执行完后再执行。
至于为什么该结果为什么与上例不同,原因在于该例中顶级函数Start() 也是IEnumerator类型的,这就造成了程序的空等。这个特性或许会有用。
(想要返回到更高级的函数中去执行,结果没有更高级的,所以只有空等,在每一帧特定时间查询协程事件是否完成,如果完成,就可以向后执行)
基本上StartCoroutine的用法就这样了,其中或许还有许多神奇的用法,大家就慢慢自己玩咯~
以下为一些补充,来自 这里
注意 Unity中使用Coroutine需要注意的问题:
1.使用的地方和不能使用的地方:
必须在MonoBehaviour或继承于MonoBehaviour的类中调用 yield coroutine。yield不可以在Update或者FixedUpdate里使用。
2.开启协程:
StartCoroutine(string methodName)和StartCoroutine(IEnumeratorroutine)都可以开启一个协程,
区别:
使用字符串作为参数时,开启协程时最多只能传递一个参数,并且性能消耗会更大一点; 而使用IEnumerator 作为参数则没有这个限制。
3.删除协程:
1).在Unity3D中,使用StopCoroutine(stringmethodName)来终止该MonoBehaviour指定方法名的一个协同程序,使用StopAllCoroutines()来终止所有该MonoBehaviour可以终止的协同程序。
包括StartCoroutine(IEnumerator routine)的。
2).还有一种方法可以终止协同程序,即将协同程序所在gameobject的active属性设置为false,当再次设置active为ture时,协同程序并不会再开启;
如是将协同程序所在脚本的enabled设置为false则不会生效。
4.js和C#中使用区别:
在C#中要使用 yield return而不是yield。
C#中yield(中断)语句必须要在IEnumerator类型里,C#方法的返回类型为IEnumerator,返回值如(eg:yield return new WaitForSeconds(2); 或者 yield returnnull);
5.协程函数返回值和参数类型,组合的设计模式:
协同程序的返回类型为Coroutine类型。在unity3d中,Coroutine类继承于YieldInstruction,所以,协同程序的返回类型只能为null、等待的帧数(frame)以及等待的时间。
协同程序的参数不能指定ref、out参数。但是,我们在使用WWW类时会经常使用到协同程序,由于在协同程序中不能传递参数地址(引用),也不能输出对象