Unity 协同Coroutine

API列表 :

  1. StartCoroutine    启动指定协同程序

  2. StopCoroutine    终止指定协同程序

  3. StopAllCoroutines    终止所有协同程序

  4. WaitForSeconds    等待若干秒

  5. WaitForFixedUpdate    等待知道下一次FixedUpdate调用

说明 :

一般用来在脚本中增加延时效果。因为在Start()或者Update()中是不能直接延时的(WaitForSecond())等待某个操作结束之后再执行代码字符串做为参数

协同程序,简称“协程”. 在脚本运行过程中,需要额外的执行一些其他的代码,这个时候就可以将“其他的代码”以协程的形式来运行,类似于开启了一个线程,但是协程不是线程。

开启协同:StartCoroutine(string methodName):字符串作为参数可以开启线程并在协程结束前终止线程;开启协程时最多只能传递一个参数,并且性能消耗会更大一点

  1. StartCoroutine(IEnumerator routine):只能等待协程的结束而不能随时终止,除非使用StopAllCoroutines()方法中止协同

  2. StopCoroutine(string methodName):中止一个协同,只能终止该MonoBehaviour中的协同程序

  3. StopAllCoroutines():中止所有协同,只能终止该MonoBehaviour中的协同程序

协同(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语句。

yield的返回值控制何时恢复协同程序向下执行。

注意 :将协同程序所在gameobject的active属性设置为false,当再次设置active为ture时,协同程序并不会再开启。

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

null   协同会在下次继续执行(下次update之后)

WaitForEndOfFrame   协同会在帧的所有的渲染和GUI完成后执行

WaitForFixedUpdate  协同会在下一个物理循环,即所有的物理计算完成后执行

WaitForSeconds   协同在指定的时间过后再执行(延时执行)

WWW  等待网络请求完成(同WaitForSeconds或null一样,可以被唤醒)

Another coroutine   等待新的协同结束运行后当前协同才可被唤醒

WaitForEndOfFrame 可以用来获取纹理信息。(因为摄像机已经完成了渲染,GUI也已经显示完毕)

WaitForSeconds 计算的是游戏时间,而不是实际的世界时间,如果Time.timeScale设置成0,那么使用yield return new WaitForSeconds(x)将不会被恢复执行。

yield不可单独使用 , 需要与return配合使用 , 例如:

//等0帧

yield return 0;

//等待1帧

yield return 1;

//等待3秒

yield return WaitForSeconds(3.0);

//立即返回调用点

yield return null;

//立即终止协同

yield break;

所有使用yield的函数必须将返回值类型设置为IEnumerator类型,例如:

IEnumerator DoSomething() {

    Debug.Log("do something…”);

}

C#实例 :

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class TestScript : MonoBehaviour {

    void Start () {

        print ("begin");

        StartCoroutine ("Test");

    }

    IEnumerator Test()

    {

        yield return new WaitForSeconds (2);

        print("wait 2 seconds print");

    }

}

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

总结

  1. 协同是创建操作序列的最好方法,这些序列可以按照时间或外部事件等驱动

  2. 协同不是线程且不是异步的

  3. 如果你yield声明的条件满足,你的协同会恢复执行

  4. 在脚本被禁用或游戏对象被销毁时,协同也会终止

  5. yield return new WaitForSeconds取决于游戏时间,受Time.timeScale参数的影响

猜你喜欢

转载自blog.csdn.net/qq_18427785/article/details/102621801