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

news2024/11/15 6:49:13

你的类可以在不继承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 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("等待一个协程完成,这里我等待的协程是等待两秒的协程");
    }
}

示例结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2130628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

labview禁用8080端口

需求背景 最近电脑上安装了labview全家桶,发现idea的8080端口项目启动报错,一直提示8080端口被占用。最简单的办法就是找到8080端口的服务,然后关闭这个服务。但是我不想这么做,我想把labview的web服务器的端口给修改了。 操作教程 1、cmd查看8080端口 2、windows进程 同…

ICM20948 DMP代码详解(17)

接前一篇文章&#xff1a;ICM20948 DMP代码详解&#xff08;16&#xff09; 前一篇文章讲到了inv_icm20948_set_chip_power_state函数中尚需解析的3个函数中的第1个函数&#xff1a;inv_icm20948_write_single_mems_reg_core。并没有完全讲完&#xff0c;本回继续解析。为了便于…

搭建本地DVWA靶场教程 及 靶场使用示例

1. DVWA简介 DVWA&#xff08;Damn Vulnerable Web Application&#xff09;一个用来进行安全脆弱性鉴定的PHP/MySQL Web 应用平台&#xff0c;旨在为网络安全专业人员测试自己的专业技能和工具提供合法的环境&#xff0c;帮助web开发者更好的理解web应用安全防范的过程。 DVW…

利用MR设备实现弹吉他教学:实战案例详解

随着混合现实(Mixed Reality, MR)技术的发展,越来越多的应用开始探索如何将这种沉浸式的体验融入到教育与娱乐中。特别是在音乐教育领域,MR技术为乐器学习提供了全新的可能性。本文将通过分析一个基于Unity开发的吉他教学应用案例,探讨如何利用MR设备,如Oculus Quest或Ap…

Boot中使用Redis缓存

除了RedisTemplate,Spring Cache 还有如下方式 即使不写Repository也可以自动注入 只要extends CrudRepository 最好不要写Repository有可能冲突 自动注入用Autowired或Resource都可

Chainlit集成Langchain并使用通义千问实现和数据库交互的网页对话应用增强扩展(text2sql)

前言 我在上一篇文章中《Chainlit集成Langchain并使用通义千问实现和数据库交互的网页对话应用&#xff08;text2sql&#xff09;》 利用langchain 中create_sql_agent 创建一个数据库代理智能体&#xff0c;但是实测中发现&#xff0c;使用 create_sql_agent 在对话中&#x…

yolo学习 (一) 安装yolov8及训练

随便搞个python环境&#xff0c;直接装或者anaconda都行&#xff0c;python版本最低3.8以上 一、安装yolov8 &#xff08;cpu版本&#xff09; pip install ultralytics yolov8安装版本比较省事&#xff0c;不过这里默认装的是CPU版本 import torch print(torch.__version_…

基于stm32单片机使用 RT-Thread 系统的 ADC 外设

一、ADC 介绍 来源&#xff1a;RT-Thread 文档中心   ADC(Analog-to-Digital Converter) 指模数转换器。是指将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号&#xff0c;例如温度、压力、声音或者图像等&#xff0c;需要转换成更容易储存、处理和发射…

手机玩机常识-------谷歌系列机型解锁bl详细步骤 其他机型可以借鉴参考

谷歌公司自从在2005年收购了Android公司之后一直在开发一款手机操作系统&#xff0c;谷歌的这一举动正是为了推出自己的手机而作准备.目前。谷歌Pixel 系列为很多玩家所持有。其独特的安装原生系统为很多粉丝所青睐。今天我们来看看谷歌Pixel 系列机型解锁bl的相关常识 谷歌Pi…

CTF(misc)

1、隐写3 题目链接 观察这个图片感觉图片高度有问题&#xff0c;010editor打开&#xff0c;查看CRC python脚本求宽高 import os import binascii import struct crcbp open("dabai.png","rb").read() for i in range(1024):for j in range(1024):data …

操作系统 ---- 调度器、闲逛进程

一、调度器/调度程序&#xff08;scheduler&#xff09; 2、③由调度程序引起&#xff0c;调度程序决定: 让谁运行?――调度算法运行多长时间?―一时间片大小 调度时机――什么事件会触发“调度程序”? 创建新进程进程退出运行进程阻塞I/O中断发生&#xff08;可能唤醒某…

大受欢迎的游戏却又意外被作者下架的《Flappy Bird》将重返iPhone

据"Flappy Bird 基金会"官网称&#xff0c;标志性的侧卷轴滚动游戏《Flappy Bird》将很快回归 iPhone。《Flappy Bird》于 2013 年发布&#xff0c;很快就获得了数千万次下载。然而&#xff0c;这款游戏在2014 年突然从 App Store 下架&#xff0c;原因是其越南开发者…

SD卡挂载FatFs文件系统

一、简介 实验目的&#xff1a;SD卡挂载FATFS文件系统&#xff0c;并生成.txt文件 MCU&#xff1a;ST32F103ZET6 SD卡&#xff1a;16G&#xff1b;SPI读写模式&#xff1b; 引脚定义&#xff1a;VCC:5V GND:GND MISO:PA6 …

常用环境部署(十八)——CentOS7搭建DNS服务器

一、安装Bind服务器软件并启动 1、安装Bind服务 yum -y install bind bind* 2、 启动服务 systemctl start named 3、开机自启动 systemctl enable named 二、查看named进程是否正常启动 1、检查进程 ps -eaf|grep named 2、检查监听端口 ss -nult|grep :53 三、关闭…

EmguCV学习笔记 C# 11.6 图像分割

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 EmguCV是一个基于OpenCV的开源免费的跨平台计算机视觉库,它向C#和VB.NET开发者提供了OpenCV库的大部分功能。 教程VB.net版本请访问…

宠物毛发对人体有什么危害?宠物空气净化器小米、希喂、352对比实测

作为一个呼吸科医生&#xff0c;我自己也养猫。软软糯糯的小猫咪谁不爱啊&#xff0c;在养猫的过程中除了欢乐外&#xff0c;也面临着一系列的麻烦&#xff0c;比如要忍耐猫猫拉粑粑臭、掉毛、容易带来细菌等等的问题。然而我发现&#xff0c;现在许多年轻人光顾着养猫快乐了&a…

Vue生命周期钩子在UniApp中的应用

1、Vue 3 生命周期钩子介绍 onMounted()//注册一个回调函数&#xff0c;在组件挂载完成后执行。onUpdated()//注册一个回调函数&#xff0c;在组件因为响应式状态变更而更新其 DOM 树之后调用。onUnmounted()//注册一个回调函数&#xff0c;在组件实例被卸载之后调用。onBefor…

西部数据发布的一款西数硬盘检测修复工具-支持WD-L/WD-ROYL板,能进行硬盘软复位,可识别硬盘查看或清除-供大家学习参考

使用方法: 1、运行WDR5.3正式版.exe 2、导入WDR5.3.key 3、PORTTALK.SYS放入系统目录 第一步:注册完打开软件 第二步:设置维修盘端口:点击设置--》端口--》会出现主要端口 ,次要端口 ,定制端口 USB。一般如果不是USB移动硬盘都选择“定制端口”

WebGL入门(019):WebGLSync 简介、使用方法、示例代码

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

轨迹预测(2)CVPR-24:基于社交互动角度表示的行人轨迹预测(代码已开源)

一&#xff0e;写在前面 今天要分享的是CVPR 2024上的一篇精彩的行人轨迹预测论文。这篇文章的灵感来源于海洋动物&#xff0c;它们通过回声定位来感知水下同伴的位置。研究者们构建了一种名为 SocialCircle 的全新基于角度的社交互动表示模型&#xff0c;用于动态反映行人相对…