首页 > 代码库 > Unity里的协程Coroutines

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; 
          }
     }