写于2016-2-7,原创,转载请注明。
另:现在发现一些简单的延时动作可以通过Invoke()的方式实现,没必要全用协程。
以下是正文
…………………………………………………………………………………………………………………………………………………………
(纯干货,无图)
在游戏中,经常会遇到一些计划性的事件,这里举个例子,比如说我们的主角3秒回城后进行5秒的自爆倒计时……
刚开始用Unity的时候总喜欢把这些东西都塞到Update里面去,这样虽然可以达到想要的效果,但是代码量大了就显得紊乱,不好管理。
//3秒回城后进行5秒倒计时自爆的代码(Update)
using UnityEngine;
using System.Collections;
public class Test00 : MonoBehaviour {
float homeTime = 3.0f;
bool isHome = false;
float boomTime = 5.0f;
bool isBoom = false;
void startHome(){
// 回城
Debug.Log("Home");
isHome=true;
}
void startBoom(){
// 自爆
Debug.Log("Boom");
isBoom=true;
}
void Update (){
if (!isHome){
if (homeTime <= 0){
startHome();
}else{
homeTime -= Time.deltaTime;
}
if (isHome&&!isBoom){
if (boomTime <= 0){
startBoom();
}else{
boomTime -= Time.deltaTime;
}
}
}
}
若类似的需求很多的话,Update中的代码将凌乱不堪,而且如果要进行添加或者修改,将变得非常麻烦。
而使用协程功能的话,将会变得十分方便:
//3秒回城后进行5秒倒计时自爆的代码(协程)
using UnityEngine;
using System.Collections;
public class Test01 : MonoBehaviour {
void Start()
{
HomeBoomHandle();
}
void HomeBoomHandle()
{
StartCoroutine(Home());
}
IEnumerator Home()
{
yield return new WaitForSeconds(9.0f);
Debug.Log("Home");
StartCoroutine(Boom());
}
IEnumerator Boom()
{
yield return new WaitForSeconds(10.0f);
Debug.Log("Boom");
}
}
上面代码中,以IEnumerator作为返回值的函数就是协程,调用StartCoroutine()开始协程,在Start函数中调用HomeBoomHandle(),在HomeBoomHandle()内调用了StartCoroutine(Home());,从而开启3秒回城5秒自爆的计划……
可以发现,这次的代码清晰明了易懂,省去了很多不必要的麻烦。
需要注意的事情:
①执行顺序问题
若有协程函数B 使用yield return到上一级函数A后,函数A不是协程函数,那么协程函数B中yield return后的代码将会在函数A之后的代码执行后在执行(或等待)。
举个例子:
using UnityEngine;
using System.Collections;
public class Test02 : MonoBehaviour {
void Start ()
{
StartCoroutine(Test());
Debug.Log("333");
Destroy (this.gameObject);
}
IEnumerator Test()
{
Debug.Log("111");
yield return new WaitForSeconds(3.0f);
Debug.Log("222");
}
}
由于Debug.Log("222");会在Start()执行完毕后再执行,而Start()中销毁了自身,所以Debug.Log("222");就不会被执行
本地图片,请重新上传
解决的办法是把Start ()也变成协成函数:
using UnityEngine;
using System.Collections;
public class Test03 : MonoBehaviour {
IEnumerator Start ()
{
yield return StartCoroutine(Test());
Debug.Log("333");
Destroy(this.gameObject);
}
IEnumerator Test()
{
Debug.Log("111");
yield return new WaitForSeconds(3.0f);
Debug.Log("222");
}
}
这样的话,只有当Test()被彻底执行完毕了,才会接着执行Start()的内容。
网上还有另外一个例子也是说明了这个问题,但是它把执行顺序问题作为一种技巧来使用了,这里摘录一下:
//Do等待2秒后执行后面的语句
IEnumerator Do() {
print("Do now");
yield return new WaitForSeconds(2);
print("Do 2 seconds later");
}
void Awake() {
Do();
print("This is printed immediately");
}
//先执行Do等待都执行结束再执行其他的
IEnumerator Do() {
print("Do now");
yield return new WaitForSeconds(2);
print("Do 2 seconds later");
}
IEnumerator Awake() {
yield return StartCoroutine("Do");
print("Also after 2 seconds");
print("This is after the Do coroutine has finished execution");
}
②Time.Scale的影响
WaitForSeconds是受到Time.timeScale影响的,如果将其置为0,那么协程就无法执行下去了。不过yield return null(等到下一帧执行)不会受到影响,因为游戏中每帧都会执行,只是Time.deltaTime为0。
③停止协程
可以通过使用StopCoroutine()来停止某个协程的运行,以下是从官方里文档里摘的说明和例子:
function StopCoroutine (methodName : string) : void
Description描述
Stops all coroutines named methodName running on this behaviour.
停止这个动作中名为methodName的所有协同程序。
Please note that only StartCoroutine using a string method name can be stopped using StopCoroutine.
请注意只有StartCoroutine使用一个字符串方法名时才能用StopCoroutine停用之.
例子:
using UnityEngine;
using System.Collections;
public class example : MonoBehaviour {
IEnumerator Start() {
StartCoroutine("DoSomething", 2.0F);
yield return new WaitForSeconds(1);
StopCoroutine("DoSomething");
}
IEnumerator DoSomething(float someParameter) {
while (true) {
print("DoSomething Loop");
yield return null;
}
}
}
附:什么是协同程序?
协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈,自己的局部变量,有自己的指令指针(IP,instruction pointer),但与其它协同程序共享全局变量等很多信息。线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程;而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只在必要时才会被挂起。