Unity深入了解协同Coroutine

目标

如果你不是很清楚协同(Coroutine),或者在使用中发现它不能按照你预想的方式工作时你应该读下这篇引导。

协同(Coroutine)适用于以下场合:

  • 需按步骤完成并可间断执行的任务
  • 编写延时执行的例程
  • 编写需要等待某一其它操作结束后才执行的例程

比如你可以利用协同实现一组精心组织的场景镜头组合,或者简单的让敌人播放死亡动画,暂停然后复活。

协同是Unity的强大功能之一,但很多初学者可能对其有许多误解,这篇教程会帮你认识协同的强大功能和灵活性,同时也对其运作方式有更深了解。

如果只是想让某一操作延时几秒执行,你可以考虑使用Invoke,当然也可以使用coroutines,但对初学者而言,Invoke可能更简单易用些。

线程和协同

首先,我们需要清楚认识到:协同不是线程,它不能异步执行。(如果你熟悉Symbian中的活动对象AO的话,你就能更快理解Coroutine,两者的机制非常相似)

线程是进程里与其它线程彼此独立运行的处理过程,在多处理器的设备上,一个线程甚至可以跟其它线程同时执行指令。多线程会是个很复杂的机制,因为某个线程在修改共享数据的时候可能另外一个线程正在读取它,导致脏数据问题,因此你必须检查是否有共享内存,或者对其加锁以建立资源互斥访问区,以保证同一时刻只有一个线程可以访问它。

什么是协同Coroutine?

Ok,我们已经明确了协同不是线程,那么就意味着协同只能运行于主线程中(如果协同内部发生了阻塞,那么一样会阻塞主线程),而且同一时刻只有一个协同在执行。事实上,这也是主游戏逻辑在那个时间段唯一做的事情。

所以你不需要担心协同的同步问题和共享资源的锁定,你能够完全控制流程直到代码执行到yield。

这里有协同的定义:

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.

协同(coroutine)是一个可以部分执行的函数,一旦合适的条件出现,可以在未来的某个时间点恢复执行,直到任务结束。

Unity会在每帧中每个活动状态的游戏对象(GameObject)处理协同,类似于调度器,这个处理过程发生在Update()方法之后,或者LateUpdate()方法之前,但也有特例:

一旦协同被激活,那它会一直运行到下一个yield声明,然后它会暂停直到(满足条件后)被再次恢复。你能在上图中看到它在何处恢复,不过也得看你yield了什么。

让我们看一个简单的协同示例:

IEnumerator TestCoroutine()
{
      while(true)
      {
           Debug.Log(Time.time);
           yield return null;
      }
}

这个协同拥有一个无限循环,很显然,会永远运行下去,它把当前时间输出日志然后再yield。一旦它被恢复,仍然会回到循环中,再次输出时间然后yield。

这个循环内部的代码非常像Update()方法。每帧它都会在这个对象(GameObject)的Update后运行(如果脚本里有的话)。

当你调用StartCoroutine(TestCoroutine())后,代码会立即执行直到第一次yield后暂停,然后在Unity处理这个对象的协同时,又会重新被激活,继续恢复执行。

如果你在Unity正在处理游戏对象时启动了一个协同,比如在Start(),Update(),OnCollisionEnter()内部启动协同,那这个协同会立即运行直到碰到第一个yield,然后 在当前帧恢复执行(如果yield return null的话),这有时候可能会导致难以预料的结果。

无限和超越

还有个情况-上面这个测试协同看似可以无限循环下去,但如果你在这个游戏对象中调用了终止协同,或者这个游戏对象被销毁,那么这个循环会终止。另外,如果脚本被失效,或者调用SetActiveRecursively(false)后,这个循环也会被暂停。

各种Yield

Unity processes coroutines every frame of the game for every object that has one or more running.

Unity会在每一帧中,处理每一个活动状态游戏对象的所有协同例程。

现在我们知道可以yield不同的值(来描述特定的条件需求)以暂停当前协同,这里是可能用到的yield:

  • null - 协同会在下次继续执行(下次update之后)
  • WaitForEndOfFrame - 协同会在帧的所有的渲染和GUI完成后执行
  • WaitForFixedUpdate - 协同会在下一个物理循环,即所有的物理计算完成后执行
  • WaitForSeconds - 协同在指定的时间过后再执行(延时执行)
  • WWW - 等待网络请求完成(同WaitForSeconds或null一样,可以被唤醒)
  • Another coroutine - 等待新的协同结束运行后当前协同才可被唤醒

你也可以声明yield break; 这样会立即结束当前协同。

WaitForEndOfFrame可以用来获取纹理信息。(因为摄像机已经完成了渲染,GUI也已经显示完毕)
WaitForSeconds计算的是游戏时间,而不是实际的世界时间,如果Time.timeScale设置成0,那么使用yield return new WaitForSeconds(x)将不会被恢复执行。

当然,协同最大的好处是你可以编写延时运行的函数,或者等待某个外部事件的发生,还有把这些优雅的集成在一个方法里,让代码有更好的可读性,而不用写一大堆函数来检查状态变化。

总结

  1. 协同是创建操作序列的最好方法,这些序列可以按照时间或外部事件等驱动
  2. 协同不是线程且不是异步的
  3. 如果你yield声明的条件满足,你的协同会恢复执行
  4. 在脚本被禁用或游戏对象被销毁时,协同也会终止
  5. yield return new WaitForSeconds取决于游戏时间,受Time.timeScale参数的影响

 

原文地址:http://unitygems.com/coroutines/

本人只负责翻译,版权仍归原作者所有,如需转载,请注明该出处!



你可能感兴趣的:(Unity)