Unity里的协程Coroutines

Unity里的Coroutine在帮助我们实现序列化事件时尤其方便。可以让事件依次发生,可以让事件A等待事件B结束后才开始执行。
但需要厘清几个基本概念。
Coroutines不是多线程,不是异步技术。Coroutines都在MainThread中执行,且每个时刻只有一个Coroutine在执行。 Coroutine是一个function,可以部分地执行,当条件满足时,未来会被再次执行直到整个函数执行完毕。
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.  ( http://unitygems.com/coroutines/)

Unity在每一帧都会去处理GameObject里带有的Coroutine Function,直到CoroutineFunciton被执行完毕。
当一个Coroutine开始启动时,它会执行到遇到yield为止,遇到yieldCoroutine会暂停执行,直到满足yield语句的条件,会开始执行yield语句后面的内容,直到遇到下一个yield为止…… 如此循环,直到整个函数结束。
将函数分到多个帧里去执行,帅嘛!

http://unitygems.com/coroutines/ 这个网址里关于Coroutines的教程很好,强烈推荐好好读读。

一个Coroutines应用的小例子,动画播放到某个指定进度执行某些事情:
using UnityEngine;
using System.Collections;

public class DieSequence : MonoBehaviour {
     // Use this for initialization
     void Start () {     
          StartCoroutine(Die ());
     }
     
     //Wait for an animation to be a certain amount complete
     IEnumerator WaitForAnimation(string name, float ratio, bool play)
     {
          //Get the animation state for the named animation
          var anim = animation[name];
          //Play the animation
          if(play) 
          {
               animation.Play(name);
               animation[name].speed = 0.3f;
          }
               
          //Loop until the normalized time reports a value
          //greater than our ratio.  This method of waiting for
          //an animation accounts for the speed fluctuating as the
          //animation is played.
          while(anim.normalizedTime + float.Epsilon + Time.deltaTime < ratio)
               yield return new WaitForEndOfFrame();          
     }

     IEnumerator Die()
     {
          //Wait for the die animation to be 50% complete
          yield return StartCoroutine(WaitForAnimation("anim_die005_360",0.5f, true));
          //Drop the enemies on dying pickup. Do sth you need
          Debug.Log("Half Death, Drop sth");

          yield return StartCoroutine(WaitForAnimation("anim_die005_360",1f, false));
          Debug.Log("Whole Anim Ended.");
          Destroy(gameObject);     
     }
}

Coroutine里面套Coroutine: StartCoroutine(Die()), Die()里StartCoroutine(WaitForAnimation("anim_die005_360",0.5f, true)),
当Die()里WaitForAnimation的Coroutine完成后(即Animation的播放进度大于ratio了),yield return了,才能在接下来的Frame里,执行Die()后面的东东。
由于WaitForAnimation()里并没有PauseAnimation或者StopAnimation,所以其实Die()函数里没有 yield return StartCoroutine(WaitForAnimation("anim_die005_360",1f, false));这一行,动画也会播放完毕。这一句可以保证在动画播放完毕时去执行某些东东。

使用WaitForSeconds(float time)时需要注意,当Time.timeScale = 0f时,yield return new WaitForSeconds(t) (t>0)永远不会返回,即这行后面的rountine不会被执行到。
如果在游戏里游戏时间静止时,又想用WaitForSeconds类似的功能怎么办?
可以自己实现类似的功能。
例如:
     IEnumerator CutSequence()
     {
          _runningSequence = true;
           Time.timeScale = 0;

          var originalPosition = Camera.main.transform.position;
          foreach(var ball in BallScript.allBalls.ToArray())
          {
               if (ball != null)
               {
                    Vector3 targetPosition = ball.transform.position - Vector3.forward * 2f;
                    yield return StartCoroutine(MoveObject(Camera.main.transform, targetPosition, 2));
                    yield return  WaitForRealSeconds(0.5f);
               }
          }

     Coroutine  WaitForRealSeconds(float time)
     {
          return  StartCoroutine(Wait(time));
     }

     IEnumerator  Wait(float time)
     {
          var current = Time.realtimeSinceStartup;
          while(Time.realtimeSinceStartup - current < time)
          {
               yield return null; 
          }
     }


Coroutines最棒的是函数的执行可以不在一次Frame里完成,可以在多个Frame中完成http://docs.unity3d.com/Manual/Coroutines.html官方文档里给了一个很贴切的例子来说明这个问题。我们希望看到物体透明度的改变,如果让color.a的变化在一帧内完成,那么我们是看不出这其中的变化,color.a的变化必须在在多个帧内完成,我们才有可能用肉眼看到这一变化过程。
原来的:
void Fade() {
    for (float f = 1f; f >= 0; f -= 0.1f) {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
    }
}
新的:
IEnumerator Fade() {
    for (float f = 1f; f >= 0; f -= 0.1f) {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        yield return null;
    }
}

每一帧Coroutine的状态都有的,请注意 Coroutine.Current()和Coroutine.MoveNext()。 下面给一个例子展示如何使用这两个函数。
public class Fibonacci : MonoBehaviour {

     IEnumerator Fib()
     {
          int Fkm2 = 1;
          int Fkm1 = 1;
          yield return 1;
          yield return 1;
          while (Fkm2 + Fkm1 < int.MaxValue)
          {
               int Fk = Fkm2 + Fkm1;
               Fkm2 = Fkm1;
               Fkm1 = Fk;
               yield return Fk;
          }
     }

     // Use this for initialization
     void Start () {

          IEnumerator fib = Fib();

          for (int i=0; i<10; ++i)
          {
               if (!fib.MoveNext())
                    break;

               Debug.Log((int)fib.Current);
          }
     }
}

在每一帧,Coroutine的状态和value都可以获取
public class CoroutineExt
{
public Coroutine coroutine;

public static CoroutineExt StartCoroutineExt(MonoBehaviour obj, IEnumerator coroutine)
{
CoroutineExt coroutineObject = new CoroutineExt();
coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine));
return coroutineObject;
}

public IEnumerator InternalRoutine(IEnumerator coroutine){
while(true){
if(!coroutine.MoveNext()){
yield break;
}
object yielded = coroutine.Current;
Debug.Log(yielded);
yield return coroutine.Current;
}
}
}

public class Nothing : MonoBehaviour {

void Start()
{
Debug.Log("Do Sth.");
MonoBehaviour obj = gameObject.GetComponent<MonoBehaviour>();
CoroutineExt.StartCoroutineExt(obj, TestNewRoutineGivesException() );

}
IEnumerator TestNewRoutineGivesException(){
yield return null;
yield return 2;
yield return new WaitForSeconds(2f);
throw new Exception("Bad thing!");
}
}
运行结果如下:


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值