最早接触coroutine是在02年使用unreal engine时。unreal script通过支持latent function和state代码实现了coroutine的思想。由于unreal script属于私有语言,整个语言设计的针对性比较强,所以虽然很好用,但是在语言的通用性上的没有任何的考虑。03年开始接触lua时,被他的coroutine设计吸引住了。把lua的coroutine用在游戏中可谓再合适不过了,而且使用起来更自由一些。Unity中也支持coroutine,虽然思想上和用法上和unreal engine以及lua的大同小异,实现层面还是有本质的区别的。理解Unity中的coroutine的运作机制对于正确的使用coroutine是有很大帮助的。
Unity coroutine的实现机制
先来说说lua coroutine。在lua中,coroutine是通过一个独立的lua state来实现,这个state中保存了coroutine的运行堆栈。在corotine程序中,可以随时暂停执行,保存调用堆栈,也可以在任意时刻恢复执行。lua的coroutine可以说是真正的coroutine。
与lua不同,Unity所使用的mono,也就是.net虚拟机本身并不支持coroutine,coroutine功能是在语言层通过Iterator Blocks模拟出来的。C#将Iterator Block代码转化为一个IEnumerator class代码,并返回这个IEnumerator,外部可以通过调用这个IEnumerator对象的MoveNext()函数来运行一次coroutine。
模拟Unity coroutine
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public interface IYYieldInstruction
{
bool isFinished();
}
public class YCCoroutine : IYYieldInstruction
{
private YCContext c_;
public YCCoroutine(YCContext c)
{
c_ = c;
}
public bool isFinished()
{
return c_.isFinished();
}
}
public class YCWait : IYYieldInstruction
{
private float seconds_;
public YCWait(float sec)
{
seconds_ = sec;
}
public bool isFinished()
{
seconds_ -= Time.deltaTime;
return (seconds_ < 0);
}
}
public class YCContext
{
IEnumerator coroutine_;
IYYieldInstruction curYield_;
bool isFinished_;
public YCContext(IEnumerator c)
{
coroutine_ = c;
curYield_ = coroutine_.Current as IYYieldInstruction;
}
public void step()
{
if(isFinished_)
return;
if(curYield_ != null && curYield_.isFinished())
curYield_ = null;
if(curYield_ == null)
{
if(!coroutine_.MoveNext())
isFinished_ = true;
else
curYield_ = coroutine_.Current as IYYieldInstruction;
}
}
public bool isFinished() { return isFinished_; }
}
public class YCoroutineManager : MonoBehaviour
{
private List<YCContext> contexts_ = new List<YCContext>();
public YCCoroutine startCoroutine(IEnumerator c)
{
YCContext context = new YCContext(c);
YCCoroutine cr = new YCCoroutine(context);
contexts_.Add(context);
return cr;
}
void Update()
{
for (int i = 0; i < contexts_.Count; i++)
{
YCContext c = contexts_[i];
c.step();
if (c.isFinished())
{
contexts_.Remove(c);
i--;
}
}
}
IEnumerator Start()
{
Debug.Log(Time.frameCount);
yield return startCoroutine(c1());
Debug.Log(Time.frameCount);
}
IEnumerator c1()
{
Debug.Log(Time.frameCount);
yield return startCoroutine(c2());
Debug.Log(Time.frameCount);
}
IEnumerator c2()
{
Debug.Log(Time.frameCount);
yield break;
}
}