你的类可以在不继承Mono的脚本使用协程,但本质仍然需要借助其他Mono对象的Update来调度
实现了一个有执行顺序的协程的调度器,用于在 Unity 中管理多个协程的执行。通过 ICoroutineNodeOrder
和 ICoroutineWaitCondition
两个接口,可以定义每个协程的执行状态、等待条件以及暂停/恢复操作。系统引入了一个 ICoroutineContext
接口,作为扩展点,使等待条件能够根据外部上下文动态判断是否满足条件。核心调度逻辑由 CoroutineSchedulerOrder
类负责,它管理协程队列,并在每帧更新时根据协程的等待条件决定是否推进协程的执行。
核心组件:
-
ICoroutineNodeOrder 接口:
- 定义了协程节点的基本结构,每个协程节点包括
Fiber
(协程主体)、IsFinished
(协程是否完成)、IsPaused
(是否暂停)等状态。 CanContinue
方法允许节点基于传入的上下文判断是否可以继续执行。
- 定义了协程节点的基本结构,每个协程节点包括
-
ICoroutineWaitCondition 接口:
- 定义了等待条件的结构,
IsConditionMet
方法用于判断条件是否满足,可以实现不同类型的等待条件(如等待时间、等待帧数、等待其他协程完成等)。
- 定义了等待条件的结构,
-
CoroutineNodeOrder 类:
ICoroutineNodeOrder
接口的具体实现,管理单个协程的状态、等待条件以及执行逻辑。
-
CoroutineSchedulerOrder 类:
- 负责管理所有协程节点的调度,包括添加、暂停、移除协程。
- 每帧通过
UpdateCoroutines
方法来推进队列中的协程。 - 如果协程节点具有等待条件,则会检查条件是否满足;如果没有等待条件,则继续执行协程。
-
等待条件类:
WaitForFrameCondition
:等待指定帧数后执行。WaitForTimeCondition
:等待指定时间后执行。WaitForCoroutineCondition
:等待另一个协程完成后执行。
工作流程:
- 协程通过
EnqueueCoroutine
方法被添加到调度器中,形成一个先进先出的队列。 - 在每一帧,
UpdateCoroutines
方法会检查队列中的第一个协程是否满足条件执行(通过CanContinue
方法判断),如果条件满足,协程会继续执行。 - 如果协程返回一个等待条件对象(
ICoroutineWaitCondition
),调度器会将该条件附加到协程节点,并在随后的帧中检查条件是否满足。 - 协程可以暂停、恢复,或者被标记为完成,并从队列中移除。
- 通过
RemoveCoroutine
或RemoveAllCoroutines
方法,协程可以被主动移除。
拓展性:
- 通过实现
ICoroutineWaitCondition
,可以轻松添加新的等待条件,例如基于游戏事件或玩家输入的等待条件。 ICoroutineContext
是一个扩展点,未来可以通过增加上下文信息(如游戏状态、外部环境等)来增加条件的判断逻辑。
源码
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
// ICoroutineNodeOrder 接口:定义了一个协程节点的基础结构
public interface ICoroutineNodeOrder
{
// 是否完成
bool IsFinished { get; set; }
// 是否暂停
bool IsPaused { get; set; }
// 枚举器,代表协程的主体
IEnumerator Fiber { get; }
/// <summary>
/// 协程等待条件
/// </summary>
ICoroutineWaitCondition WaitCondition { get; }
// 判断协程是否可以继续执行
bool CanContinue(ICoroutineContext context);
// 添加一个等待条件
void AddWaitCondition(ICoroutineWaitCondition condition);
// 暂停协程
void Pause();
// 恢复协程
void Resume();
}
// ICoroutineWaitCondition 接口:定义等待条件的结构
public interface ICoroutineWaitCondition
{
// 判断等待条件是否满足
bool IsConditionMet(ICoroutineContext context);
// 暂停等待条件
void Pause();
// 恢复等待条件
void Resume();
}
/// <summary>
/// 这个接口预留,作为拓展使用
/// </summary>
public interface ICoroutineContext
{
//添加内容例如
//当前Unity运行的帧数,运行时间
}
// CoroutineNodeOrder 类:具体的协程节点实现
public class CoroutineNodeOrder : ICoroutineNodeOrder
{
// 协程主体(Fiber)
public IEnumerator Fiber { get; private set; }
// 是否完成
public bool IsFinished { get; set; }
// 是否暂停
public bool IsPaused { get; set; }
// 当前节点的等待条件
private ICoroutineWaitCondition waitCondition = null;
public ICoroutineWaitCondition WaitCondition => waitCondition;
// 构造函数,传入一个协程(Fiber)
public CoroutineNodeOrder(IEnumerator fiber)
{
Fiber = fiber;
IsFinished = false;
IsPaused = false;
}
// 添加等待条件
public void AddWaitCondition(ICoroutineWaitCondition condition) => waitCondition = condition;
// 检查等待条件是否满足,决定协程是否可以继续执行
public bool CanContinue(ICoroutineContext context) => waitCondition.IsConditionMet(context);
// 暂停等待条件
public void Pause() => waitCondition.Pause();
// 恢复等待条件
public void Resume() => waitCondition.Resume();
}
// CoroutineScheduler 类:调度器,管理协程的生命周期和调度
public class CoroutineSchedulerOrder
{
// 用于存储所有协程的队列
private Queue<ICoroutineNodeOrder> coroutineQueue = new Queue<ICoroutineNodeOrder>();
private ICoroutineNodeOrder frozenCoroutineNodeOrder = null;
// 向调度器中添加协程
public ICoroutineNodeOrder EnqueueCoroutine(IEnumerator fiber)
{
if (fiber == null)
{
return null;
}
ICoroutineNodeOrder coroutine = new CoroutineNodeOrder(fiber); // 创建协程节点
coroutineQueue.Enqueue(coroutine); // 将节点加入队列
return coroutine;
}
// 停止一个特定的协程,这将阻塞后续的协程
public ICoroutineNodeOrder PauseCoroutine(ICoroutineNodeOrder coroutine)
{
coroutine.IsPaused = true;
return coroutine;
}
/// <summary>
/// 移除一个协程,视为该协程完成了
/// </summary>
/// <param name="coroutine"></param>
/// <returns></returns>
public ICoroutineNodeOrder RemoveCoroutine(ICoroutineNodeOrder coroutine)
{
coroutine.IsFinished = true;
var coroutineList = coroutineQueue.ToList();
coroutineList.Remove(coroutine);
coroutineQueue = new Queue<ICoroutineNodeOrder>(coroutineList);
return coroutine;
}
// 移除所有协程
public void RemoveAllCoroutines() => coroutineQueue.Clear();
// 更新协程状态,在每帧调用
public void UpdateCoroutines(ICoroutineContext context=null)
{
int queueSize = coroutineQueue.Count;
if (queueSize == 0) return;
ICoroutineNodeOrder coroutine = coroutineQueue.Peek(); // 获取队首协程
// 如果协程已完成,从队列中移除
if (coroutine.IsFinished)
{
coroutineQueue.Dequeue();
return;
}
// 如果协程暂停,执行暂停操作,并跳过本帧处理
if (coroutine.IsPaused)
{
if (frozenCoroutineNodeOrder != null) return;
if (coroutine.WaitCondition != null)
{
coroutine.Pause();
frozenCoroutineNodeOrder = coroutine; // 记录冻结的协程
}
return;
}
else if (frozenCoroutineNodeOrder != null && frozenCoroutineNodeOrder == coroutine)
{
coroutine.Resume(); // 如果之前被冻结,现在恢复协程
frozenCoroutineNodeOrder = null;
}
if (coroutine.WaitCondition == null)
{
//什么也不用做,走到MoveNextCoroutine进行初始化
}
else if (!coroutine.CanContinue(context)) return; // 检查协程是否满足继续执行的条件
MoveNextCoroutine(coroutine);
}
private void MoveNextCoroutine(ICoroutineNodeOrder coroutine)
{
// 如果协程可以继续执行,调用 MoveNext() 继续执行协程
if (coroutine.Fiber.MoveNext())
{
System.Object yieldCommand = coroutine.Fiber.Current; // 获取当前协程的返回值
var coroutineWaitCondition = yieldCommand as ICoroutineWaitCondition;
// 如果返回的是等待条件,添加等待条件到协程节点
if (coroutineWaitCondition != null)
coroutine.AddWaitCondition(coroutineWaitCondition);
else
throw new System.Exception("yield return type error");
}
else
{
coroutine.IsFinished = true; // 标记协程已完成
coroutineQueue.Dequeue(); // 将完成的协程移出队列
}
}
}
//结构体实现节约性能
public struct CoroutineContext : ICoroutineContext
{
//添加字段作为拓展
}
#region 等待条件
// 等待帧的条件类
public class WaitForFrameCondition : ICoroutineWaitCondition
{
private int waitFrame; // 目标帧数
public WaitForFrameCondition(int frame)
{
if (frame <= 0)
{
throw new ArgumentException("Frame must be greater than 0.", nameof(frame));
}
waitFrame = frame;
}
// 检查是否达到目标帧数
bool ICoroutineWaitCondition.IsConditionMet(ICoroutineContext context)
{
waitFrame--;
return waitFrame < 0;
}
// 无需实现
void ICoroutineWaitCondition.Pause() { }
// 无需实现
void ICoroutineWaitCondition.Resume() { }
}
// 等待时间的条件类
public class WaitForTimeCondition : ICoroutineWaitCondition
{
private float waitTime; // 等待时间
public WaitForTimeCondition(float time)
{
waitTime = time;
}
// 检查是否达到目标时间
bool ICoroutineWaitCondition.IsConditionMet(ICoroutineContext context)
{
waitTime -= Time.deltaTime;
return waitTime < 0;
}
// 无需实现
void ICoroutineWaitCondition.Pause() { }
// 无需实现
void ICoroutineWaitCondition.Resume() { }
}
// 等待其他协程完成的条件类
public class WaitForCoroutineCondition : ICoroutineWaitCondition
{
private ICoroutineNodeOrder coroutine; // 被依赖的协程节点
public WaitForCoroutineCondition(ICoroutineNodeOrder coroutine)
{
this.coroutine = coroutine;
}
// 检查依赖的协程是否已经完成
bool ICoroutineWaitCondition.IsConditionMet(ICoroutineContext context) => coroutine.IsFinished;
// 暂停依赖的协程
void ICoroutineWaitCondition.Pause() => this.coroutine.Pause();
// 恢复依赖的协程
void ICoroutineWaitCondition.Resume() => this.coroutine.Resume();
}
#endregion
示例
using UnityEngine;
using System.Collections;
public class CoroutineSchedulerOrderTest : MonoBehaviour
{
CoroutineSchedulerOrder coroutineSchedulerOrder = new CoroutineSchedulerOrder();
private void Start()
{
coroutineSchedulerOrder.EnqueueCoroutine(TestFrame());
var t = coroutineSchedulerOrder.EnqueueCoroutine(TestTime());
coroutineSchedulerOrder.EnqueueCoroutine(TestCoroutine(t));
}
private void Update()
{
coroutineSchedulerOrder.UpdateCoroutines();//这里默认传了空,但也可以传结构体
}
IEnumerator TestFrame()
{
yield return new WaitForFrameCondition(1);
Debug.Log("等待一帧");
}
IEnumerator TestTime()
{
yield return new WaitForTimeCondition(2);
Debug.Log("等待两秒");
}
IEnumerator TestCoroutine(ICoroutineNodeOrder c)
{
yield return new WaitForCoroutineCondition(c);
Debug.Log("等待一个协程完成,这里我等待的协程是等待两秒的协程");
}
}
示例结果