最早接觸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;
}
}