【Unity3D】协程Coroutine的运用

来源:互联网 发布:程序员加油站 编辑:程序博客网 时间:2024/06/15 10:25

网上经常有些资料对一个概念讲得极其透彻,但对于我这个简单粗暴高效率的程序猿猴来说,只希望知道你这个东西怎么用。对于协程Coroutine这个东西,在我之前的博客或多或少有用过,但其实都用不好,原因是我没确切了解协程Coroutine到底是什么东西。直到我再次苦于如何打断某段代码的执行,希望回调地执行某段代码的时候,重新审视起Coroutine这个单词。实质是Co合作+routine一系列的事物(也可以用来指各种“日常”)=Coroutine协同,再掏空脑绪地研读各种长篇大论,终于恍然大悟原来Unity3D中协程Coroutine,就是线程Thread!再配合之前写过的协程Coroutine,发现Unity3D中协程Coroutine的运用有三种:(1)延迟执行某段代码(2)每隔几秒执行某段代码(3)同步。并且,灵活运用这东西,你能够解决Unity3D,等待动画表演,等待结算等一系列坑爹问题。

零、问题提出

首先,在Unity3D的Update()的函数是不能被打断的,也就是说如下代码,如果绑定在任何一个对象上面,你的游戏将会被卡死,只能Ctrl+Alt+Delete立即结束了:

using UnityEngine;using System.Collections;public class MyCoroutine : MonoBehaviour{    private int a;    void Start()    {        a = 0;    }    void Update()    {        Debug.Log("0");        while (a == 0)        {            //去做些事情,然后做完让a!=0。        }        Debug.Log("1");    }}

本来我是打断,Update()函数你等我一下,然后处理一些事情,你读下面的代码,而我做完这些事情的标志就是让a不等于0。

可惜事与愿违,且不说Update()函数,每帧都被读取,也就说时刻在执行里面的代码,这个机制。

单单是Unity3d是要读完Update()函数的代码,才会给你刷新一帧这个机制,已经足以让这游戏瞬间崩溃。

因此,也启发了我,Update()尽可能地不要扔些循环给它做,里面顶多就放些条件判断好了,这样你的游戏才会流畅,才是所谓的“优化好”。

那么,爷确实有些比较耗时的任务,这怎办?那就通通开个子线程——协程Coroutine,别都写在主线程,Update()函数。

一、延迟执行某段代码

比如,我要打印完0,1,要3秒后才打印2,这怎么办呢?已经无法在Update()函数写个计时循环了啊!那你可以这样写:

using UnityEngine;using System.Collections;public class MyCoroutine : MonoBehaviour{    void Start()    {        Debug.Log("0");        Debug.Log("1");        StartCoroutine(Thread1());    }    void Update()    {    }    IEnumerator Thread1()    {        yield return new WaitForSeconds(3.0f);        Debug.Log("2");    }}

运行结果如下:


yield return new WaitForSeconds(3.0f);这一句就是中断这线程3秒的意思,也就是在这行停3秒。并且,中断线程的语句,只能写在IEnumerator Thread1(){}这些协程里面,而不能写在Update()里面,因为Update()这个主线程根本不能别中断。

而开子线程Thread1,或者按照Unity3d的术语,应该说是 开协程Thread1的语句StartCoroutine(Thread1());应该放在只在开始执行一次的Start()里面,不然在Update()每帧都执行一次,子线程Thread1里面的程度,得开多少次啊?

另外,IEnumerator Thread1(){}在读完所有代码,自动死亡,会被系统的线程回收机制自动回收,因此和其余编程一样,如《【Python】线程的创建、执行、互斥、同步、销毁》(点击打开链接)等,我们自管开线程就行,其余的不用管!

二、每隔几秒执行某段代码

如果我不想每帧都执行某些代码,而是比如想每1秒i+1,初始=0的i,i++到10即停止,这又怎么办呢?你可以这样写:

using UnityEngine;using System.Collections;public class MyCoroutine : MonoBehaviour{    private int i;    void Start()    {        i = 0;        StartCoroutine(Thread1());    }    void Update()    {    }    IEnumerator Thread1()    {        while (true)        {            Debug.Log("i=" + i);            i++;            if (i > 10)            {                break;            }            yield return new WaitForSeconds(1.0f);        }    }}

运行结果如下:


这一段也很好理解,就是在Thread1中上个死循环,但死循环里面的代码并不是这么好读,读到 yield return new WaitForSeconds(1.0f);就要停顿1秒。读其余代码的时间可以忽略不计,因此,协程Coroutine配合一个有条件break的死循环,可以做到每隔几秒执行某段代码的效果。

但还是那句话,这一切通通都只能写到协程IEnumerator Thread1()里面,因为Update()不能停顿,游戏和动画一样,都是每一帧不停被刷新的页面。

三、同步

比如我想执行完某段代码,立即执行另一段代码,做到回调的效果,那该怎么办呢?

当然最简单就是在写完一段代码,在下一行写另一段代码。可是,如果这些代码不是立即完成的,需要等待,就要用到协程的同步。

比如,协程1需要耗时X秒,我并不知道,而协程2则需要在协程1之后马上执行,这又该怎么办呢?你可以这样写:

using UnityEngine;using System.Collections;public class MyCoroutine : MonoBehaviour{    private int i;    void Start()    {        i = 0;        StartCoroutine(Thread1());    }    void Update()    {    }    IEnumerator Thread1()    {        while (true)        {            Debug.Log("i=" + i);            i++;            if (i > 3)            {                break;            }            yield return new WaitForSeconds(1.0f);        }        Debug.Log("线程1已经完成了");        StartCoroutine(Thread2());    }    IEnumerator Thread2()    {        Debug.Log("线程2开始");        yield return null;//这句必须有,C#要求每个协程都要有yield return,虽然这句话看起来并没有什么卵用,但你就是要写-_-!    }}

运行结果如下:


可以,看到Thread2在Thread1数到3之后,马上就开始了。


以上就是Unity3D协程Coroutine个人总结的三大用法,如果说对于每个对象分别赋予脚本,有利于以类来创作游戏,并管理游戏对象,协程的灵活运用有利于从时间轴上、标志性的事件上管理游戏对象。Unity3D协程Coroutine可谓随处可见,大概是通往熟练Unity3d程序猿的必修课之一吧!