Unity里的Coroutine在帮助我们实现序列化事件时尤其方便。可以让事件依次发生,可以让事件A等待事件B结束后才开始执行。
但需要厘清几个基本概念。
Coroutines不是多线程,不是异步技术。Coroutines都在MainThread中执行,且每个时刻只有一个Coroutine在执行。 Coroutine是一个function,可以部分地执行,当条件满足时,未来会被再次执行直到整个函数执行完毕。
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. (
http://unitygems.com/coroutines/)
Unity在每一帧都会去处理GameObject里带有的Coroutine Function,直到CoroutineFunciton被执行完毕。
当一个Coroutine开始启动时,它会执行到遇到yield为止,遇到yieldCoroutine会暂停执行,直到满足yield语句的条件,会开始执行yield语句后面的内容,直到遇到下一个yield为止…… 如此循环,直到整个函数结束。
将函数分到多个帧里去执行,帅嘛!
http://unitygems.com/coroutines/ 这个网址里关于Coroutines的教程很好,强烈推荐好好读读。
一个Coroutines应用的小例子,动画播放到某个指定进度执行某些事情:
using UnityEngine;
using System.Collections;
public class DieSequence : MonoBehaviour {
// Use this for initialization
void Start () {
StartCoroutine(Die ());
}
//Wait for an animation to be a certain amount complete
IEnumerator WaitForAnimation(string name, float ratio, bool play)
{
//Get the animation state for the named animation
var anim = animation[name];
//Play the animation
if(play)
{
animation.Play(name);
animation[name].speed = 0.3f;
}
//Loop until the normalized time reports a value
//greater than our ratio. This method of waiting for
//an animation accounts for the speed fluctuating as the
//animation is played.
while(anim.normalizedTime + float.Epsilon + Time.deltaTime < ratio)
yield return new WaitForEndOfFrame();
}
IEnumerator Die()
{
//Wait for the die animation to be 50% complete
yield return StartCoroutine(WaitForAnimation("anim_die005_360",0.5f, true));
//Drop the enemies on dying pickup. Do sth you need
Debug.Log("Half Death, Drop sth");
yield return StartCoroutine(WaitForAnimation("anim_die005_360",1f, false));
Debug.Log("Whole Anim Ended.");
Destroy(gameObject);
}
}
using System.Collections;
public class DieSequence : MonoBehaviour {
// Use this for initialization
void Start () {
StartCoroutine(Die ());
}
//Wait for an animation to be a certain amount complete
IEnumerator WaitForAnimation(string name, float ratio, bool play)
{
//Get the animation state for the named animation
var anim = animation[name];
//Play the animation
if(play)
{
animation.Play(name);
animation[name].speed = 0.3f;
}
//Loop until the normalized time reports a value
//greater than our ratio. This method of waiting for
//an animation accounts for the speed fluctuating as the
//animation is played.
while(anim.normalizedTime + float.Epsilon + Time.deltaTime < ratio)
yield return new WaitForEndOfFrame();
}
IEnumerator Die()
{
//Wait for the die animation to be 50% complete
yield return StartCoroutine(WaitForAnimation("anim_die005_360",0.5f, true));
//Drop the enemies on dying pickup. Do sth you need
Debug.Log("Half Death, Drop sth");
yield return StartCoroutine(WaitForAnimation("anim_die005_360",1f, false));
Debug.Log("Whole Anim Ended.");
Destroy(gameObject);
}
}
Coroutine里面套Coroutine: StartCoroutine(Die()), Die()里StartCoroutine(WaitForAnimation("anim_die005_360",0.5f, true)),
当Die()里WaitForAnimation的Coroutine完成后(即Animation的播放进度大于ratio了),yield return了,才能在接下来的Frame里,执行Die()后面的东东。
由于WaitForAnimation()里并没有PauseAnimation或者StopAnimation,所以其实Die()函数里没有 yield return StartCoroutine(WaitForAnimation("anim_die005_360",1f, false));这一行,动画也会播放完毕。这一句可以保证在动画播放完毕时去执行某些东东。
使用WaitForSeconds(float time)时需要注意,当Time.timeScale = 0f时,yield return new WaitForSeconds(t) (t>0)永远不会返回,即这行后面的rountine不会被执行到。
如果在游戏里游戏时间静止时,又想用WaitForSeconds类似的功能怎么办?
可以自己实现类似的功能。
例如:
IEnumerator CutSequence()
{
_runningSequence = true;
Time.timeScale = 0;
var originalPosition = Camera.main.transform.position;
foreach(var ball in BallScript.allBalls.ToArray())
{
if (ball != null)
{
Vector3 targetPosition = ball.transform.position - Vector3.forward * 2f;
yield return StartCoroutine(MoveObject(Camera.main.transform, targetPosition, 2));
yield return WaitForRealSeconds(0.5f);
}
}
{
_runningSequence = true;
Time.timeScale = 0;
var originalPosition = Camera.main.transform.position;
foreach(var ball in BallScript.allBalls.ToArray())
{
if (ball != null)
{
Vector3 targetPosition = ball.transform.position - Vector3.forward * 2f;
yield return StartCoroutine(MoveObject(Camera.main.transform, targetPosition, 2));
yield return WaitForRealSeconds(0.5f);
}
}
Coroutine
WaitForRealSeconds(float time)
{
return StartCoroutine(Wait(time));
}
IEnumerator Wait(float time)
{
var current = Time.realtimeSinceStartup;
while(Time.realtimeSinceStartup - current < time)
{
yield return null;
}
}
{
return StartCoroutine(Wait(time));
}
IEnumerator Wait(float time)
{
var current = Time.realtimeSinceStartup;
while(Time.realtimeSinceStartup - current < time)
{
yield return null;
}
}
Coroutines最棒的是函数的执行可以不在一次Frame里完成,可以在多个Frame中完成。
http://docs.unity3d.com/Manual/Coroutines.html官方文档里给了一个很贴切的例子来说明这个问题。我们希望看到物体透明度的改变,如果让color.a的变化在一帧内完成,那么我们是看不出这其中的变化,color.a的变化必须在在多个帧内完成,我们才有可能用肉眼看到这一变化过程。
原来的:
void Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
}
}
新的:
IEnumerator Fade() {
for (float f = 1f; f >= 0; f -= 0.1f) {
Color c = renderer.material.color;
c.a = f;
renderer.material.color = c;
yield return null;
}
}
每一帧Coroutine的状态都有的,请注意
Coroutine.Current()和Coroutine.MoveNext()。 下面给一个例子展示如何使用这两个函数。
public class Fibonacci : MonoBehaviour {
IEnumerator Fib()
{
int Fkm2 = 1;
int Fkm1 = 1;
yield return 1;
yield return 1;
while (Fkm2 + Fkm1 < int.MaxValue)
{
int Fk = Fkm2 + Fkm1;
Fkm2 = Fkm1;
Fkm1 = Fk;
yield return Fk;
}
}
// Use this for initialization
void Start () {
IEnumerator fib = Fib();
for (int i=0; i<10; ++i)
{
if (!fib.MoveNext())
break;
Debug.Log((int)fib.Current);
}
}
}
IEnumerator Fib()
{
int Fkm2 = 1;
int Fkm1 = 1;
yield return 1;
yield return 1;
while (Fkm2 + Fkm1 < int.MaxValue)
{
int Fk = Fkm2 + Fkm1;
Fkm2 = Fkm1;
Fkm1 = Fk;
yield return Fk;
}
}
// Use this for initialization
void Start () {
IEnumerator fib = Fib();
for (int i=0; i<10; ++i)
{
if (!fib.MoveNext())
break;
Debug.Log((int)fib.Current);
}
}
}
在每一帧,Coroutine的状态和value都可以获取。
public class CoroutineExt
{
public Coroutine coroutine;
public static CoroutineExt StartCoroutineExt(MonoBehaviour obj, IEnumerator coroutine)
{
CoroutineExt coroutineObject = new CoroutineExt();
coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine));
return coroutineObject;
}
public IEnumerator InternalRoutine(IEnumerator coroutine){
while(true){
if(!coroutine.MoveNext()){
yield break;
}
object yielded = coroutine.Current;
Debug.Log(yielded);
yield return coroutine.Current;
}
}
}
public class Nothing : MonoBehaviour {
void Start()
{
Debug.Log("Do Sth.");
MonoBehaviour obj = gameObject.GetComponent<MonoBehaviour>();
CoroutineExt.StartCoroutineExt(obj, TestNewRoutineGivesException() );
}
IEnumerator TestNewRoutineGivesException(){
yield return null;
yield return 2;
yield return new WaitForSeconds(2f);
throw new Exception("Bad thing!");
}
}
运行结果如下: