首页 > 其他分享 >Unity实现自己的协程系统(协程有顺序)

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

时间:2024-09-12 17:52:42浏览次数:3  
标签:ICoroutineWaitCondition 顺序 协程 coroutine Unity ICoroutineNodeOrder 等待 public

你的类可以在不继承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();
}
//这个接口是为拓展上下文预留的
public interface ICoroutineContext
{

}

// 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();

    // 更新协程状态,需要借助一个Mono对象的Update调用
    public void UpdateCoroutines(ICoroutineContext context)
    {
        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 class 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(new CoroutineContext());
    }

    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("等待一个协程完成,这里我等待的协程是等待两秒的协程");
    }
}

示例结果

标签:ICoroutineWaitCondition,顺序,协程,coroutine,Unity,ICoroutineNodeOrder,等待,public
From: https://blog.csdn.net/R3333355726856/article/details/142182245

相关文章

  • Unity Apple Vision Pro 开发(九):空间锚点
    XR开发者社区链接:SpatialXR社区:完整课程、项目下载、项目孵化宣发、答疑、投融资、专属圈子课程试看:https://www.bilibili.com/video/BV1JFHgegEb2课程完整版,答疑仅社区成员可见,可以通过文章开头的链接加入社区。空间锚点可以把虚拟物体固定在现实中的一个特定区域,锚定......
  • dotnet 测试 SemaphoreSlim 的 Wait 是否保持进入等待的顺序先进先出
    本文记录我测试dotnet里面的SemaphoreSlim锁,在多线程进入Wait等待时,进行释放锁时,获取锁执行权限的顺序是否与进入Wait等待的顺序相同。测试的结果是SemaphoreSlim的Wait大部分情况是先进先出,按照Wait的顺序出来的,但是压力测试下也存在乱序,根据官方文档说明不应该依......
  • dotnet 测试 Mutex 的 WaitOne 是否保持进入等待的顺序先进先出
    本文记录我测试dotnet里面的Mutex锁,在多线程进入WaitOne等待时,进行释放锁时,获取锁执行权限的顺序是否与进入WaitOne等待的顺序相同。测试的结果是Mutex的WaitOne是乱序的,不应该依赖Mutex的WaitOne做排队顺序以下是测试程序代码vartaskList=newList<Task>();......
  • Unity中的协程
    函数IEnumeratorA(){yieldreturnCurrent;//下一帧执行}开启StartCoroutine()参数可以为函数和函数名字符串,但是字符串的运行开销比较大。协程原理常见用法如果Current是null,就相当于什么也不做。在下一次游戏循环中,就会调用MoveNext。所以yieldreturnnull就起......
  • 【Unity精品源码】Auto Chess: 自走棋策略游戏开发框架
    ......
  • Unity UI 系统:Unity UI package (uGUI) 使用说明
    卡牌游戏UI系统UnityUI基础概念布局(Layout)Unity的屏幕坐标定义为左下角为(0,0),右上角为(1,1)。锚点(Anchor)锚点控制子矩形UI的边相对父矩形对应坐标轴的指定比例边的距离保持不变。AnchorMinX的值value表示:该矩形UI左边的X坐标相对父矩形UI......
  • 数据结构:线性表的顺序表实现
    顺序表的操作:这里采用了结构体和指针的部分知识//自定义结构体typedefstruct{ DataTypelist[Maxsize]; intsize;}SeqList;voidListInitiate(SeqList*L){ L->size=0;}intListLength(SeqListL){ returnL.size;}//插入是从前往后移动intListInsert(Seq......
  • 类的执⾏顺序?
    在C#中,类的执行顺序取决于你如何组织和调用代码。以下是类中代码执行的一般顺序:静态构造函数:如果类包含静态构造函数,它将在类首次被引用时执行,且仅执行一次。静态构造函数常用于初始化静态成员。实例构造函数:当创建类的实例时,实例构造函数将被调用。如果类继承自......
  • 双人3D坦克(unity+C#)
        //TankManager.csusingSystem;usingUnityEngine;[Serializable]publicclassTankManager{publicColorm_PlayerColor;publicTransformm_SpawnPoint;[HideInInspector]publicintm_PlayerNumber;......
  • 一文理解协程----还不明白请来砍我
    说在前头:本文话糙理不糙,用大白话说明协程的核心思想,协程,指的是单个线程里执行多个并发任务,一个协程对应一个任务,重点来了,!!!协程是用户空间的概念,也就是说不管你一个线程里有多少个协程,在操作系统看来,你就是一个单线程,只要你有一处代码阻塞了,那么os就会挂起整个线程,所以说这多......