Unity的协程系统是基于C#的一个简单而强大的接口,IEnumerator,它允许你为自己的集合类型编写枚举器。这一点你不必关注太多。
倒计时器:这是一个简单的脚本组件,只做了倒计时,并且在到达0的时候log一个信息。
using Unity Engine;
using System.Collections;
public class Countdown : MonoBehaviour
{
public float timer = 3;
void Update()
{
timer -= Time.deltaTime;
if(timer <= 0)
Debug.Log("Timer has finished!");
}
}
代码简短实用,但问题是,如果我们需要复杂的脚本组件(像一个角色或者敌人的类),拥有多个计时器呢?
刚开始的时候,我们的代码也许会是这样的:
using UnityEngine;
using System.Collections;
public class MultiTimer : MonoBehaviour
{
public float firstTimer = 3;
public float secondTimer = 2;
public float thirdTimer = 1;
void Update()
{
firstTimer -= Time.deltaTime;
if(firstTimer <= 0)
Debug.Log("First timer has finished!");
secondTimer -= Time.deltaTime;
if(secondTimer <= 0)
Debug.Log("Second timer has finished!");
thirdTimer -= Time.deltaTime;
if(thirdTimer <= 0)
Debug.Log("Third timer has finished!");
}
}
尽管不是太糟糕,但是大多数人不是很喜欢自己的代码中充斥着这些计时器变量,它们看上去很乱,而且当我们需要重新开始计时的时候还得记得去重置它们(很多人忘记这件事)
如果只用一个for循环来做这些,看上去是否会好很多?
for(float timer = 3; timer >= 0; timer -= Time.deltaTime)
{
//Just do nothing...
}
Debug.Log("This happens after 5 seconds!");
协程可以做的正是这一点
下面看一下协程版本1
using UnityEngine;
using System.Collections;
public class CoroutineCountdown : MonoBehaviour
{
void Start()
{
StartCoroutine(Countdown());
}
IEnumerator Countdown()
{
for(float timer = 3; timer >= 0; timer -= Time.deltaTime)
yield return 0;
Debug.Log("This message appears after 3 seconds!");
}
}
StartCoroutine(Countdown()); 这一行用来开始我们的Countdown程序,注意,我并没有给它传入参数,但是这个方法调用了它自己(这是通过传递Countdown的return返回值来实现的)。
在Countdown方法中其他的都很好理解,除了两个部分:
1、IEnumerator 的返回值
2、for循环中的yield return 0;
为了能在连续的多帧中(在这个例子中,3秒钟等同于很多帧)调用该方法,Unity必须通过某种方式来存储这个方法的状态,这是通过IEnumerator 中使用yield return语句得到的返回值,当你“yield”一个方法时,你相当于说了,“现在停止这个方法,然后在下一帧中从这里重新开始!”。
注意:用0或者null来yield的意思是告诉协程等待下一帧,直到继续执行为止。当然,同样的你可以继续yield其他协程。
协程版本2:
using UnityEngine;
using System.Collections;
public class CoroutineCountdown : MonoBehaviour
{
void Start()
{
StartCoroutine(Countdown());
}
IEnumerator Countdown()
{
yield return new WaitForSeconds(3f);
Debug.Log("This message appears after 3 seconds!");
}
}
协程的返回值类型
null:等待一帧
0:等待一帧(1,2,3....100等数字都无实际意义,和0一样)
waitForSeconds():等待指定秒数
waitForEndOfFrame():等待摄像机渲染和OnGUI执行完毕
waitForEndOfFixedUpdate():等待FixedUpdate执行完毕
waitUntil():等待条件满足
waitWhile():等待条件不满足
www:等待网络数据请求完毕
Coroutine:等待另一个协程,用于协程嵌套
协程的停止方法
using UnityEngine;
using System.Collections;
public class CoroutineCountdown : MonoBehaviour
{
void Start(){
//1.使用字符串方式
StartCoroutine("Countdown");
StopCoroutine("Countdown");
//2.保存方法引用
IEnumerator coroutine = Countdown();
StartCoroutine(coroutine);
StopCoroutine(coroutine);
//3.全部停止
StartCoroutine(Countdown());
StopAllCoroutines();
}
IEnumerator Countdown(){
while (true){
yield return null;
Debug.Log("Update()");
}
}
}
协程的嵌套和传参
using UnityEngine;
using System.Collections;
public class CoroutineCountdown : MonoBehaviour
{
void Start()
{
StartCoroutine(SaySomeThings());
}
//第一层协程,yield return参数是自定义协程
IEnumerator SaySomeThings()
{
Debug.Log("The croutine has started");
yield return StartCoroutine(Wait(1.0f));
Debug.Log("1 second has passed");
}
//第二层协程的方法可以加参数
IEnumerator Wait(float duration)
{
for (float timer = 0; timer < duration; timer += Time.deltaTime)
yield return 0;
}
}
- 注意事项
1、在程序中调用StopCoroutine(Method())方法不能终止协程;
2、多个协程可以同时运行,它们会根据各自的启动顺序来更新;
3、协程可以嵌套任意多层(在这个例子中我们只嵌套了一层);
4、如果你想让多个脚本访问一个协程,那么你可以定义静态的协程;
5、协程不是多线程(尽管它们看上去是这样的),它们运行在同一线程中,跟普通的脚本一样;
6、如果你的程序需要进行大量的计算,那么可以考虑在一个随时间进行的协程中处理它们;
7、IEnumerator类型的方法不能带ref或者out型的参数,但可以带被传递的引用;
8、Unity中没有简便的方法来检测作用于对象的协程数量以及具体是哪些协程作用在对象上。
9、Yield Return+数字:没有实际意义,跟0和null是一样的。
网友评论