网上经常有些资料对一个概念讲得极其透彻,但对于我这个简单粗暴高效率的程序猿猴来说,只希望知道你这个东西怎么用。对于协程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程序猿的必修课之一吧!