您的位置:首页 > 教育 > 锐评 > Unity实现自己的协程系统(协程有顺序)

Unity实现自己的协程系统(协程有顺序)

2024/10/6 20:28:35 来源:https://blog.csdn.net/R3333355726856/article/details/142182245  浏览:    关键词:Unity实现自己的协程系统(协程有顺序)

你的类可以在不继承Mono的脚本使用协程,但本质仍然需要借助其他Mono对象的Update来调度

        实现了一个有执行顺序的协程的调度器,用于在 Unity 中管理多个协程的执行。通过 ICoroutineNodeOrderICoroutineWaitCondition 两个接口,可以定义每个协程的执行状态、等待条件以及暂停/恢复操作。系统引入了一个 ICoroutineContext 接口,作为扩展点,使等待条件能够根据外部上下文动态判断是否满足条件。核心调度逻辑由 CoroutineSchedulerOrder 类负责,它管理协程队列,并在每帧更新时根据协程的等待条件决定是否推进协程的执行。

核心组件:

  1. ICoroutineNodeOrder 接口

    • 定义了协程节点的基本结构,每个协程节点包括 Fiber(协程主体)、IsFinished(协程是否完成)、IsPaused(是否暂停)等状态。
    • CanContinue 方法允许节点基于传入的上下文判断是否可以继续执行。
  2. ICoroutineWaitCondition 接口

    • 定义了等待条件的结构,IsConditionMet 方法用于判断条件是否满足,可以实现不同类型的等待条件(如等待时间、等待帧数、等待其他协程完成等)。
  3. CoroutineNodeOrder 类

    • ICoroutineNodeOrder 接口的具体实现,管理单个协程的状态、等待条件以及执行逻辑。
  4. CoroutineSchedulerOrder 类

    • 负责管理所有协程节点的调度,包括添加、暂停、移除协程。
    • 每帧通过 UpdateCoroutines 方法来推进队列中的协程。
    • 如果协程节点具有等待条件,则会检查条件是否满足;如果没有等待条件,则继续执行协程。
  5. 等待条件类

    • WaitForFrameCondition:等待指定帧数后执行。
    • WaitForTimeCondition:等待指定时间后执行。
    • WaitForCoroutineCondition:等待另一个协程完成后执行。

工作流程:

  1. 协程通过 EnqueueCoroutine 方法被添加到调度器中,形成一个先进先出的队列。
  2. 在每一帧,UpdateCoroutines 方法会检查队列中的第一个协程是否满足条件执行(通过 CanContinue 方法判断),如果条件满足,协程会继续执行。
  3. 如果协程返回一个等待条件对象(ICoroutineWaitCondition),调度器会将该条件附加到协程节点,并在随后的帧中检查条件是否满足。
  4. 协程可以暂停、恢复,或者被标记为完成,并从队列中移除。
  5. 通过 RemoveCoroutineRemoveAllCoroutines 方法,协程可以被主动移除。

拓展性:

        通过实现 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 int CoroutineCount { get => coroutineQueue.Count; }// 向调度器中添加协程public ICoroutineNodeOrder EnqueueCoroutine(IEnumerator fiber){if (fiber == null){return null;}ICoroutineNodeOrder coroutine = new CoroutineNodeOrder(fiber); // 创建协程节点coroutineQueue.Enqueue(coroutine); // 将节点加入队列return coroutine;}// 停止一个特定的协程,这将阻塞后续的协程public void PauseCoroutine(ICoroutineNodeOrder coroutine){coroutine.IsPaused = true;}//恢复一个协程public void ResumeCoroutine(ICoroutineNodeOrder coroutine){coroutine.IsPaused = false;}/// <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(){foreach (var c in coroutineQueue) c.IsFinished = true;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);elsethrow 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("等待一个协程完成,这里我等待的协程是等待两秒的协程");}
}

示例结果

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com