Unity实现自己的协程系统

2024-09-07 08:20
文章标签 实现 系统 unity 协程

本文主要是介绍Unity实现自己的协程系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

概述:自定义Unity协程调度器(不依赖Mono)
 

        实现了一个协程调度器,允许在程序中以非阻塞的方式调度协程。协程可以在满足特定条件后暂停和恢复,如等待特定的帧数、时间、或等待其他协程执行完毕。它的设计思想与Unity的协程机制类似,但它不依赖Unity的YieldInstruction,因此适用于非Unity环境。


协程可以在以下情况下暂停:
"yield null ;" 等一帧;
"yield 一个int值",等待给定的帧数;
"yield 一个float值;",等待给定的秒数;
"yield scheduler.StartCoroutine(Coroutine())",直到另一个协程完成。


支持多个调度器实例.一个调度器中的协程可以等待另一个完全不同的调度器实例中的协程。

不使用Unity的YieldInstruction类,因为无法访问其内部数据进行调度。调度语义与Unity的调度器略有不同。
         例如,在Unity中,如果启动协程,它会立即运行到第一个yield,而在这个调度器中,它直到下一次调用UpdateAllCoroutines才会运行。
这个特性允许在任何时候启动协程,但确保启动的协程只在特定时间运行。

在同一个更新中运行的协程之间不要依赖更新顺序。例如,StartCoroutine(A), StartCoroutine(B), StartCoroutine(C)
        如果A、B、C都为:while(true) { print(A|B|C); yield; },不要期望 "ABC" 或 "CBA" 或任何特定的顺序。


 代码结构:
CoroutineScheduler 类:

 这个类是协程调度器的核心,它可以管理多个协程并根据指定的调度规则执行协程。
StartCoroutine: 启动一个新的协程。协程不会立即执行,而是在下一次调用UpdateAllCoroutines时开始运行。它可以接受IEnumerator或IEnumerable作为输入。

 Rewind: 允许重置并重新运行一个已完成的协程。

 Stop/StopAllCoroutines: 停止单个或所有协程。StopAllCoroutines停止调度器中的所有协程.

 Pause/Resume: 可以暂停和恢复协程的执行。恢复时协程会从暂停时的位置继续。

 UpdateAllCoroutines: 执行调度器中的所有协程,直到它们的下一个yield。需要调用者提供当前帧数和时间来控制协程的进度。这个方法是调度器的核心,它根据不同的条件(如时间、帧数等)来判断协程是否需要继续执行。

 CoroutineNode 类:

 每个协程都被封装在一个CoroutineNode对象中,保存了协程的执行状态、等待的条件(如等待的帧数或时间)以及是否暂停、完成等状态。

 waitForFrame/waitForTime: 协程的等待条件,当协程需要等待一定的帧数或时间时会使用这些字段。

 Reset: 允许重置协程,使其可以从头开始运行。

 Pause/Resume: 这些方法用来控制协程的暂停和恢复。

 IYieldWrapper 接口:

 这个接口允许实现一些自定义的等待条件,比如等待某个Unity对象执行完特定任务后才继续协程。

 工作原理:
 每次UpdateAllCoroutines被调用时,调度器会遍历所有未完成的协程,并根据协程当前的等待条件(帧数、时间或其他协程)决定是否继续执行。如果协程满足其等待条件,就继续执行它直到遇到下一个yield。

 在协程执行的过程中,可以通过yield return指定暂停条件。当满足条件时,调度器会将协程从等待状态中恢复。


这个调度器的设计受Unity协程机制的启发,但它并不依赖于Unity引擎,因此可以在任何基于帧更新或时间驱动的系统中使用。

 这个调度器的灵活性允许你在任意的时间调度和控制协程,并且可以在多个调度器实例之间进行协作。

using System.Collections;
public class CoroutineScheduler
{public CoroutineNode first = null; // 链表的第一个协程节点int currentFrame; // 当前帧数float currentTime; // 当前时间CoroutineNode runTimeNuext; // 执行时的下一个节点/*** 启动一个协程,协程不会立即运行,而是在下一次调用UpdateAllCoroutines时运行。* 协程的执行可以在任何时候使用yield语句暂停。yield返回值指定协程何时恢复。*/public CoroutineNode StartCoroutine(IEnumerator fiber){// 如果函数没有yield,fiber将为null,什么也不做if (fiber == null){return null;}// 创建协程节点并运行,直到到达第一个yieldCoroutineNode coroutine = new CoroutineNode(fiber);AddCoroutine(coroutine);return coroutine;}public CoroutineNode StartCoroutine(IEnumerable srcFiber){if (srcFiber == null){return null;}CoroutineNode coroutine = new CoroutineNode(srcFiber.GetEnumerator());coroutine.srcFiber = srcFiber;AddCoroutine(coroutine);return coroutine;}public void Rewind(CoroutineNode cn){if (cn.srcFiber == null)throw new System.Exception("不是IEnumerable函数");if (cn.finished){AddCoroutine(cn);}cn.Reset(); // 重置协程节点}public void Stop(CoroutineNode cn){RemoveCoroutine(cn);cn.finished = true; // 标记协程已完成}public void Pause(CoroutineNode cn){if (cn.finished) return;if (!cn.isPaused){cn.isPrePause = true;cn.isPaused = true;}}public void Resume(CoroutineNode cn){if (cn.finished) return;if (cn.isPaused){cn.isPaused = false;cn.isPrePause = true;AddCoroutine(cn); // 恢复协程}}/*** 停止所有在该调度器上运行的协程。建议避免使用此方法,* 应找到一种自然的方式让协程自行完成,而不是在它们完成之前被强制停止。* 如果你需要更细粒度的控制来停止协程,你可以使用多个调度器。*/public void StopAllCoroutines(){CoroutineNode cn = first;CoroutineNode next;while (cn != null){next = cn.listNext;cn.listPrevious = null;cn.listNext = null;cn = next;}first = null; // 清空调度器中的所有协程}/*** 如果此调度器有任何协程运行,则返回true。你可以使用它来检查所有协程是否已完成或被停止。*/public bool HasCoroutines(){return first != null;}/*** 运行所有活跃的协程直到它们的下一个yield。调用者必须提供当前的帧数和时间。* 这允许调度器在不同于Unity主游戏循环的帧数和时间制度下运行。*/public void UpdateAllCoroutines(int frame, float time){currentFrame = frame;currentTime = time;CoroutineNode coroutine = this.first;while (coroutine != null){// 在协程完成并从列表中移除之前,存储listNextrunTimeNuext = coroutine.listNext;if (coroutine.isPaused) // 如果协程暂停,直接进入下一个节点{if (coroutine.isPrePause){coroutine.isPrePause = false;if (coroutine.waitForFrame > 0) coroutine.waitForFrame -= currentFrame;else if (coroutine.waitForTime > 0) coroutine.waitForTime -= currentTime;else if (coroutine.waitForCoroutine != null) Pause(coroutine.waitForCoroutine);else if (coroutine.waitForUnityObject != null) coroutine.waitForUnityObject.Pause();}RemoveCoroutine(coroutine);coroutine = runTimeNuext;continue;}else if (coroutine.isPrePause){coroutine.isPrePause = false;if (coroutine.waitForFrame > 0) coroutine.waitForFrame += currentFrame;else if (coroutine.waitForTime > 0) coroutine.waitForTime += currentTime;else if (coroutine.waitForCoroutine != null) Resume(coroutine.waitForCoroutine);else if (coroutine.waitForUnityObject != null) coroutine.waitForUnityObject.Resume();}if (coroutine.waitForFrame > 0 && frame >= coroutine.waitForFrame){coroutine.waitForFrame = -1;InitUpdateCoroutine(coroutine);}else if (coroutine.waitForTime > 0.0f && time >= coroutine.waitForTime){coroutine.waitForTime = -1.0f;InitUpdateCoroutine(coroutine);}else if (coroutine.waitForCoroutine != null && coroutine.waitForCoroutine.finished){coroutine.waitForCoroutine = null;InitUpdateCoroutine(coroutine);}else if (coroutine.waitForUnityObject != null && coroutine.waitForUnityObject.finished){coroutine.waitForUnityObject = null;InitUpdateCoroutine(coroutine);}else if (coroutine.waitForFrame == -1 && coroutine.waitForTime == -1.0f&& coroutine.waitForCoroutine == null &&coroutine.waitForUnityObject == null){// 初始更新InitUpdateCoroutine(coroutine);}coroutine = runTimeNuext;}}/*** 执行协程直到下一个yield。如果协程已完成,标记它为完成并将其从调度器列表中移除。*/void InitUpdateCoroutine(CoroutineNode coroutine){IEnumerator fiber = coroutine.fiber;if (coroutine.fiber.MoveNext()){// System.Object yieldCommand = fiber.Current == null ? (System.Object)1 : fiber.Current;System.Object yieldCommand = fiber.Current;if (fiber.Current == null){coroutine.waitForFrame = 1 + currentFrame;}else if (yieldCommand is int){coroutine.waitForFrame = (int)yieldCommand + currentFrame;}else if (yieldCommand is float){coroutine.waitForTime = (float)yieldCommand + currentTime;}else if (yieldCommand is CoroutineNode){coroutine.waitForCoroutine = (CoroutineNode)yieldCommand;}else if (yieldCommand is IYieldWrapper){coroutine.waitForUnityObject = yieldCommand as IYieldWrapper;}else{throw new System.ArgumentException("CoroutineScheduler: 意外的协程yield类型: " + yieldCommand.GetType());}}else{// 协程完成coroutine.finished = true;RemoveCoroutine(coroutine);}}void AddCoroutine(CoroutineNode coroutine){if (first == null){first = coroutine;first.listPrevious = null;first.listNext = null;}else{var c = first;while (c.listNext != null)c = c.listNext;c.listNext = coroutine;coroutine.listNext = null;coroutine.listPrevious = c;}coroutine.finished = false; // 协程未完成}void RemoveCoroutine(CoroutineNode coroutine){if (this.first == coroutine){// 移除第一个协程节点this.first = coroutine.listNext;if (first != null) first.listPrevious = null;}else{// 不是列表头if (coroutine.listNext != null){// 移除中间的节点coroutine.listPrevious.listNext = coroutine.listNext;coroutine.listNext.listPrevious = coroutine.listPrevious;}else if (coroutine.listPrevious != null){// listNext为空,移除最后一个节点coroutine.listPrevious.listNext = null;}}if (coroutine == runTimeNuext)runTimeNuext = coroutine.listNext;coroutine.listPrevious = null;coroutine.listNext = null;}
}public interface IYieldWrapper
{bool finished { get; }void Pause();void Resume();
}public class CoroutineNode : IYieldWrapper
{public CoroutineNode listPrevious = null;public CoroutineNode listNext = null;public IEnumerable srcFiber;public IEnumerator fiber;public bool finished = true;public int waitForFrame = -1;public float waitForTime = -1.0f;public CoroutineNode waitForCoroutine;public IYieldWrapper waitForUnityObject; //lonewolfwilliamspublic bool isPaused = false;public bool isPrePause = false;public CoroutineNode(IEnumerator _fiber, bool _finished = false){this.fiber = _fiber;this.finished = _finished;}public CoroutineNode(IEnumerable _srcFiber, bool _finished = false){srcFiber = _srcFiber;this.fiber = _srcFiber.GetEnumerator();this.finished = _finished;}public void Reset(){if (srcFiber != null)fiber = srcFiber.GetEnumerator();isPaused = false;isPrePause = false;waitForFrame = -1;waitForTime = -1.0f;waitForCoroutine = null;waitForUnityObject = null;}bool IYieldWrapper.finished{get { return finished; }}public void Pause(){if (finished) return;if (!isPaused){isPrePause = true;isPaused = true;}}public void Resume(){if (finished) return;if (isPaused){isPaused = false;isPrePause = true;}}
}

这篇关于Unity实现自己的协程系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1144560

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo