在 Unity 中,协程(Coroutine)是一种非常强大的工具,它允许我们在不阻塞主线程的情况下,将代码的执行分成多个步骤,在不同的帧中执行。
Unity中协程实现原理
迭代器与状态机:本质上是基于C#的迭代器和状态机实现的。迭代器允许函数暂停和恢复执行,协程函数使用 yield return 语句暂停,保存当前状态,下次被调用时从暂停处继续。Unity在底层通过状态机管理协程状态,记录执行位置和局部变量等。
消息循环与时间管理:Unity的消息循环在每一帧检查协程状态。当协程 yield return 一个条件或等待时间时,Unity记录该条件,在后续帧中检查条件是否满足,满足则恢复协程执行。
执行队列与调度:Unity维护协程执行队列,按添加顺序或优先级调度协程。协程加入队列后,等待Unity根据帧循环和条件判断调度执行。
要实现自定义协程需用到一个暂停指令和协程的MoveNext方法:
1.YieldInstruction:用于实现协程的暂停指令,所有具体的暂停指令都需要继承自该类,并实现IsDone方法,用于判断暂停是否完成。
2.MoveNext方法:通过不断调用该方法,检测协程的暂停条件是否成立,如果条件成立,协程从暂停处继续执行,否则协程不会继续执行。
定义一个暂停指令基类:
public abstract class YieldInstruction
{
public abstract bool IsDone();
}
定义一个暂停指令:重写IsDone函数
public class WaitForFrames : YieldInstruction
{
public override bool IsDone()
{
remainingFrames--;
return remainingFrames <= 0;
}
}
自定义一个协程类:它需要包含暂停指令、需要执行的迭代器函数、还要实现一个MoveNext函数,具体实现如下:
public bool MoveNext()
{
//首先判断暂停指令是否存在
if (currentYield != null)
{
if (!currentYield.IsDone())
{
// 当前 YieldInstruction 未完成,继续等待
return true;
}
// 当前 YieldInstruction 完成,重置
currentYield = null;
}
//如果迭代器的MovenNext方法返回true则协程等待
if (routine.MoveNext())
{
currentYield = routine.Current as YieldInstruction;
return true;
}
// 协程执行完毕
return false;
}
自定义调度器: 需要实现StartCoroutine,StopCoroutine和Update函数
// 存储待执行的协程列表
private List<CustomCoroutine> coroutines = new List<CustomCoroutine>();
// 启动一个协程
public CustomCoroutine StartCoroutine(IEnumerator routine)
{
CustomCoroutine coroutine = new CustomCoroutine(routine);
coroutines.Add(coroutine);
return coroutine;
}
// 更新协程调度器,需要在每一帧调用
public void Update()
{
for (int i = coroutines.Count - 1; i >= 0; i--)
{
coroutines[i].MoveNext();
}
}
public void StopCoroutine(CustomCoroutine routine)
{
coroutines.Remove(routine);
}
public void StopAllCoroutine()
{
coroutines.Clear();
}
调用:
CustomCoroutineScheduler scheduler = new CustomCoroutineScheduler();
void Start()
{
// 启动一个协程
scheduler.StartCoroutine(TestCoroutine());
}
void Update()
{
scheduler.Update();
}
IEnumerator TestCoroutine()
{
Debug.Log("Coroutine started");
yield return new WaitForFrames(3);
Debug.Log("Waited for 3 frames");
Debug.Log("Coroutine ended");
}
结果:
其他有用的链接:
utamaru/unity3d-extra-yield-instructions:Unity3D 协程的其他自定义 yield 指令 (github.com)