⾃⼰实现unity的协程功能
本⽂需要有⼀定的C#迭代器基础知识和unity的协程相关的基础知识,如果对这两者不太了解,可以先看下笔者之前的⽂章:、。
好的,话不多说,我们直接开始吧:为了⽅便理解和标识,本例⼦的类采取My + unity的类名的命名⽅式。⾸先我们新建⼀个名叫MyCoroutine的Visual C#控制台应⽤程序,新建两个类:
public class MyYieldInstruction
{
}
public class MyWaitForSeconds : MyYieldInstruction
{
public float seconds;
public MyWaitForSeconds(float seconds)
{
this.seconds = seconds;
}
}
分别对应unity中的YieldInstruction和WaitForSeconds。之所以要有MyYieldInstruction这个空类,是为了在保存数据的时候直接使⽤基类指针,这样⽅便。当然我们这次其他的例如WaitForEndOfFrame、WaitForFixedUpdate之类的就不实现了,因为原理都是⼀样的。
第⼆步我们应该还有个类似于Unity的MonoBehaviour的类,⽤来写我们的StartCoroutine接⼝并对迭代器数据进⾏保存和驱动(其实有个管理类更好,简单起见就写在⼀起了):MyMonoBehaviour。思考⼀下,因为我们要保存迭代器的指针,所以我们的协程容器⾥⾯必定要有⼀个IEnumerator,有了IEnumerator我们还得知道它是什么类型的指令,⽐如是WairForSeconds还是直接yield return null,所以还得保存MyYieldInstruction这个基类指针(为null表⽰⾮MyYieldInstruction⼦类,即下⼀帧执⾏),除了这些之外,我们还需要⼀个数据:MyWaitForSeconds这个指令需要知道上次暂停的时间⽤于计算是否满⾜条件,所以增加了这么个数据类:
public class MyYieldInsInfo
{
}
public class MyWaitForSecondsInfo : MyYieldInsInfo
{
public MyWaitForSecondsInfo(DateTime BeginTime)
{
this.BeginTime = BeginTime;
}
public DateTime BeginTime
{
get;set;
}
}
需要的数据类都有了,我们的协程数据类也就明了了:
public class RoutineInfo
{
public void Reset()
{
objYield = null;
objYieldInfo = null;
}
public MyYieldInstruction objYield { get; set; } = null;
public MyYieldInsInfo objYieldInfo { get; set; } = null;
public IEnumerator routine;
}
List<RoutineInfo> lstRoutine = new List<RoutineInfo>();
数据类有了,那我们就可以Happy的对其填充了:
public void StartCoroutine(IEnumerator routine)
{
if (null == routine)
{
return;
}
routine.MoveNext();
RoutineInfo objRoutineInfo = new RoutineInfo();
SetRoutineInfo(ref objRoutineInfo);
lstRoutine.Add(objRoutineInfo);
}
public void SetRoutineInfo(ref RoutineInfo objRoutineInfo)
{
if (utine.Current is MyYieldInstruction)
{
objRoutineInfo.objYield = utine.Current as MyYieldInstruction;
objRoutineInfo.objYieldInfo = new MyWaitForSecondsInfo(DateTime.Now);
}
}
把调⽤StartCoroutine的时候把感兴趣的数据存储起来。
数据容器定义了,容器也填充了,那就轮到数据判断了,判断的逻辑我们放⼊到MyMonoBehaviour的Update函数中(虽然没⼈会调⽤Update来驱动~~):
public void Update()
{
List<int> lstNeedDelIndex = new List<int>();
for (int i = 0; i < lstRoutine.Count; ++i)
{
RoutineInfo item = lstRoutine[i];
if (null == item)
{
continue;
}
bool bCallMoveNext = item.objYield == null ? true : DealWithYieldInstruction(item);
if (!bCallMoveNext)
{
continue;
}
if (utine.MoveNext())
{
SetRoutineInfo(ref item);
}
else
{
lstNeedDelIndex.Add(i);
}
}
foreach (var item in lstNeedDelIndex)
{
lstRoutine.RemoveAt(item);
}
}
public bool DealWithYieldInstruction(RoutineInfo objRoutineInfo)
{
if (objRoutineInfo.objYield is MyWaitForSeconds)
{
TimeSpan objSpan = DateTime.Now - (objRoutineInfo.objYieldInfo as MyWaitForSecondsInfo).BeginTime;
return objSpan.TotalSeconds > (objRoutineInfo.objYield as MyWaitForSeconds).seconds;
}
return true;
writeline方法的作用}
lstNeedDelIndex的作⽤是标记已经执⾏完的协程索引,然后将其在lstRoutine中从后往前删除(这样能确保之前存储的索引正确)。写到这⾥,我们已经万事具备,只⽋Update的驱动和测试函数了,我们先来看下测试函数:
public void CoroutineTest()
{
StartCoroutine(CoroutineDetail());
}
IEnumerator CoroutineDetail()
{
yield return null;
Console.WriteLine("yield return null:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
yield return new MyWaitForSeconds(1.0f);
Console.WriteLine("wait 1.0 seconds:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
yield return new MyWaitForSeconds(2.0f);
Console.WriteLine("wait 2.0 seconds:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
}
模仿我们在unity中经常⽤到的⽅式写上yield return null 和我们的yield return new MyWaitForSeconds。驱动很简单,在Main函数while循环调⽤MyMonoBehaviour的Update就好了:
class Program
{
static void Main(string[] args)
{
MyMonoBehaviour objMyMonoBehaviour = new MyMonoBehaviour();
Console.WriteLine("Create MyMonoBehaviour" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"));
objMyMonoBehaviour.CoroutineTest();
while (true)
{
objMyMonoBehaviour.Update();
Thread.Sleep(1);
}
}
}
终于到了丰收的时候:
从动图可以看出确实达到了我们预期的效果。当然啦,这只是⼀个⼩例⼦,代码有很多不合理的地⽅,将就着看了,只是说明我们⽤C#现有的功能,实现unity的协程功能并不难,有时候甚⾄可以说是简单。有些看似强⼤的引擎底层功能,离我们也许并不遥远。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。