Unity——协程(Coroutine)

本文为问GPT所得


一、在Unity中,协程到底是个啥

在Unity中,协程(Coroutine)是一种特殊的函数,用于在一段时间内暂停执行,并在稍后的时间点继续执行。通常情况下,我们在代码中通过调用协程来实现一些需要延迟执行或需要分步处理的任务

协程在Unity中有如下特点:

  1.  可以在一帧中的不同时间点暂停和恢复执行,而不需要等待整个帧的结束。
  2. 可以方便地实现延迟执行,例如在几秒后播放音效或执行一个动画。
  3. 可以使用协程来实现复杂的异步任务,而不需要使用回调函数或依赖于线程。

使用协程可以简化代码逻辑,使得任务的实现更加清晰和可读。在Unity中,可以使用C#的yield关键字来定义和控制协程的执行流程。例如,可以使用yield return语句来暂停执行,并在满足某个条件后继续执行。

以下是一个简单的示例,演示了一个协程的用法:

using UnityEngine;
using System.Collections;public class CoroutineExample : MonoBehaviour
{IEnumerator Start(){Debug.Log("协程开始");yield return new WaitForSeconds(2f);Debug.Log("等待了2秒后继续执行");yield return new WaitForSeconds(1f);Debug.Log("又等待了1秒后继续执行");}
}

在上面的示例中,Start方法是一个协程。通过yield return语句,我们在协程中暂停执行了2秒和1秒,并在等待结束后继续执行。当我们在MonoBehaviour中启动这个协程时,它将按照我们定义的流程执行。

二、协程的原理

协程(Coroutine)是一种轻量级的并发编程技术,它可以在单个线程内实现多个执行流的并发操作。协程的原理主要涉及到两个概念:上下文切换和yield(让出执行权)

  1.  上下文切换:协程通过上下文切换来实现在不同协程之间的切换执行。当一个协程执行到某个位置时,它会保存当前的上下文(包括程序计数器、堆栈指针等信息),然后切换到下一个要执行的协程,恢复其保存的上下文,并从切换位置继续执行。
  2.  yield(让出执行权):yield 是协程中的一个关键操作,它可以让协程主动让出执行权,将控制权交给其他协程。当一个协程执行到 yield 语句时,它会暂停执行,并将结果返回给调度器(或其他协程),同时保存当前的上下文。接下来,调度器会选择另一个协程来执行,将保存的上下文恢复,然后继续执行。

通过上下文切换和yield操作,协程能够实现非抢占式的协作式调度,也就是协程主动地让出执行权,而不是由操作系统或调度器去强制切换。这样可以更灵活地控制协程的执行顺序和并发度,避免资源竞争和锁机制带来的开销。

在实现上,协程可以由编程语言提供的原生支持(如Python的asyncio、Go的goroutine),也可以通过生成器(Generator)和状态机等手动实现。不同的语言和库对协程的具体实现方式可能略有不同,但核心原理基本相似。

总结起来,协程通过上下文切换和yield操作实现协作式调度,使得在单个线程内能够实现多个执行流的并发操作,提高了程序的并发性和灵活性。

三、程和线程的区别

当谈论协程和线程的区别时,主要有以下几点:

  1. 执行方式:线程是由操作系统调度的最小执行单位,它在操作系统的控制下运行,可以并行执行。协程是由程序控制的执行单位,它由程序自己在协程调度器的管理下运行,可以通过协作方式实现并发。
  2. 并发性:线程可以在多个 CPU 核心上并行执行,利用多核优势提高计算效率。而协程通常在单个线程中运行,通过在不同协程间的切换实现并发,无法利用多核优势。 
  3. 内存和资源消耗:线程在创建时需要分配独立的内存空间,包括堆栈等资源,同时线程切换时需要保存和恢复上下文,消耗较多的内存和资源。协程则可以在一个线程中存在多个,共享同一堆栈空间,切换时只需要保存和恢复少量上下文,消耗较少的内存和资源。
  4. 同步与通信:在线程中,由于存在资源竞争,需要使用锁机制等同步手段来保护共享资源,避免数据冲突。协程通过显式地让出执行权和恢复执行权来避免资源竞争,并通过通道(channel)等机制进行协程间的通信。

总的来说,线程适合于 CPU 密集型任务,可以利用多核优势提高计算效率,但在高并发环境下可能存在资源竞争的问题。协程适合于 IO 密集型任务,可以通过协作方式实现高并发,但无法利用多核优势。根据具体的应用场景和需求,选择合适的并发模型可以提高程序的性能和效率。

四、协程的函数与规则 

PartⅠ

  1. 协程函数的声明:协程函数必须是返回类型为 IEnumerator 的方法。在C#中,我们用特殊的语法来声明协程函数,即在方法签名前面添加关键字 `IEnumerator`。
  2. yield return 语句:协程函数中通过使用 `yield return` 语句来控制流程。这个语句可以暂停执行并返回一个值,它的值将在协程继续执行时被使用。可以使用多种类型的 `yield return` 语句,包括 `yield return null`、`yield return new WaitForSeconds(delayTime)` 或者自定义的对象。
  3. 每帧执行:协程函数在不同的时间点暂停和继续。Unity自动管理协程的迭代器,并在每帧中递进执行。这意味着协程函数的每个 yield return 语句将在下一帧或指定的延迟时间后执行。例如,`yield return null` 将会在下一帧继续执行。
  4. 协程生命周期:协程函数的生命周期与启动它的 `MonoBehaviour` 组件密切相关。当 `MonoBehaviour` 组件启用时,协程开始执行;当组件禁用或销毁时,协程停止执行。这意味着协程函数可以在对象激活期间执行,也可以在对象非激活期间暂停执行。
  5. 停止和恢复协程:我们可以使用 `StopCoroutine()` 或 `StopAllCoroutines()` 来停止正在运行的协程。使用 `yield break` 语句可以提前结束协程的执行。如果需要重新启动协程,只需再次调用它即可。

PartⅡ

6. 启动协程:要启动协程,可以使用 `StartCoroutine()` 方法。这个方法可以接受一个协程函数作为参数,并在每帧中执行该函数。可以将协程函数作为参数传递给 `StartCoroutine()`,如下所示:

   StartCoroutine(MyCoroutineFunction());

   注意,返回的 `Coroutine` 对象可以用于控制和监视协程的状态,如停止或暂停。

7. 停止和暂停协程:通过调用 `StopCoroutine()` 或 `StopAllCoroutines()` 方法可以停止正在运行的协程。可以将协程函数作为参数传递给 `StopCoroutine()`,如下所示:

   StopCoroutine(MyCoroutineFunction());

   你还可以使用 `yield break` 语句来提前结束协程的执行,使其立即返回。

8. 嵌套协程:你可以在一个协程中启动另一个协程。这被称为嵌套协程。嵌套协程可以帮助你组织和管理复杂的逻辑。要启动嵌套协程,可以使用 `StartCoroutine()` 方法,并将协程函数作为参数传递给它。

IEnumerator MyCoroutineA(){Debug.Log("协程 A 开始执行");yield return StartCoroutine(MyCoroutineB());Debug.Log("协程 A 继续执行");}IEnumerator MyCoroutineB(){Debug.Log("协程 B 开始执行");yield return new WaitForSeconds(2f);Debug.Log("协程 B 完成执行");}

   在上面的示例中,协程函数 `MyCoroutineA()` 启动了嵌套的协程 `MyCoroutineB()`。协程 `MyCoroutineA()` 在 `MyCoroutineB()` 完成执行后继续执行。

PartⅢ

9. WaitForSeconds和WaitForSecondsRealtime:可以使用 `yield return new WaitForSeconds(delayTime)` 或 `yield return new WaitForSecondsRealtime(delayTime)` 来暂停协程的执行一段指定的时间。其中 `WaitForSeconds` 以游戏时间为基准计时,而 `WaitForSecondsRealtime` 以实际时间为基准计时。这两个等待语句对于实现延迟执行或定时操作非常有用。

  IEnumerator MyCoroutine(){Debug.Log("等待2秒");yield return new WaitForSeconds(2f);Debug.Log("继续执行");}

10. 异常处理:协程中的异常处理可以通过使用 try-catch 语句来实现。异常会中断协程的执行,并且可以捕获和处理异常。在捕获到异常后,你可以决定是继续执行协程还是提前结束协程。

   IEnumerator MyCoroutine(){try{// 协程逻辑}catch (Exception ex){// 处理异常}

11. 返回值:协程函数可以返回一个值,这个值可以通过 `yield return` 语句返回。这个返回值可以在调用 `StartCoroutine()` 后使用返回的 `Coroutine` 对象的 `Coroutine.Current` 属性访问。 

   IEnumerator MyCoroutine(){yield return 10;}void Start(){Coroutine coroutine = StartCoroutine(MyCoroutine());int value = (int)coroutine.Current;}

12. 停止全部协程:在某些情况下,你可能需要停止场景中所有正在运行的协程。可以使用 `StopAllCoroutines()` 方法来停止全部协程的执行。

   void StopAllCoroutines(){StopAllCoroutines();}

PartⅣ

13. 异步操作和回调:协程可以用于实现异步操作和回调。通过使用 `yield` 和 `yield return` 语句,可以在协程中等待异步操作完成,然后根据需要执行相应的逻辑。这样可以避免使用回调函数,使代码更加清晰和可读。

   IEnumerator MyCoroutine(){// 等待异步操作完成yield return SomeAsyncOperation();// 异步操作完成后执行的逻辑Debug.Log("异步操作完成");}IEnumerator SomeAsyncOperation(){// 异步操作的逻辑yield break;}

14. 迭代器块和状态机:协程本质上是一种特殊类型的迭代器块。迭代器块是一种可中断和恢复的迭代器,它允许在每次迭代之间保持状态,并在需要时从上次离开的位置继续执行。

   在使用协程时,应该了解协程的执行机制。虽然它们看起来像是同步的代码,但实际上它们是在每帧中部分执行的。这意味着在每个 `yield return` 语句之后,协程将暂停执行并返回到调用它的代码,然后在下一帧继续执行。

PartⅤ

15. Coroutine对象的状态检查:可以使用 `Coroutine` 对象的 `Coroutine.Current` 属性来检查协程的当前状态。状态可以是以下三种之一:

  •    Null:协程未启动或已经完成。
  •    Coroutine:协程正在执行中。
  •    CoroutineSuspended:协程已被暂停。

   这个状态检查可以帮助你在需要时对协程进行额外的控制和处理。

16. 协程与主线程交互:在协程中,可以使用 `yield return null` 来让出主线程,在下一帧继续执行协程。这对于需要与主线程进行交互的场合非常有用,如更新UI元素或处理用户输入。

 IEnumerator MyCoroutine(){Debug.Log("协程开始执行");yield return null; // 让出主线程Debug.Log("协程在下一帧继续执行");}

   这样,你可以确保在协程执行过程中及时响应主线程的事件。

17. 协程作为事件驱动处理器:协程可以用作事件驱动处理器,以响应事件并执行相关的逻辑。例如,你可以在协程中监听输入事件、定时事件或其他自定义事件,并在相应的事件发生时执行相应的协程代码。

   IEnumerator InputCoroutine(){while (true){if (Input.GetKeyDown(KeyCode.Space)){Debug.Log("Space键被按下");yield return new WaitForSeconds(1f);}yield return null;}}

   在这个示例中,协程将持续监听 `Space` 键的按下事件,并在事件发生后等待1秒钟。这种方式可以在不使用传统的事件监听器的情况下,以类似事件驱动的方式响应输入。

这些是关于协程函数和规则的进一步信息。协程是一种非常强大和灵活的功能,可用于处理各种异步、延迟和分步的任务。通过熟悉协程的概念和规则,你可以更好地利用它们来编写高效、可读和易于维护的代码。

PartⅥ

18. 协程组:协程组是一种用于管理多个协程的结构。通过将协程添加到协程组中,你可以对整组协程进行统一的管理操作,如启动、停止和暂停。这对于同时管理多个相关的协程非常有用。 

IEnumerator CoroutineA(){yield return new WaitForSeconds(2f);Debug.Log("协程A完成");}IEnumerator CoroutineB(){yield return new WaitForSeconds(3f);Debug.Log("协程B完成");}void Start(){CoroutineGroup coroutineGroup = new CoroutineGroup();coroutineGroup.AddCoroutine(CoroutineA());coroutineGroup.AddCoroutine(CoroutineB());StartCoroutine(coroutineGroup.StartGroup());}

   在上面的示例中,我们创建了一个协程组 `coroutineGroup`,并向其中添加了协程 `CoroutineA` 和 `CoroutineB`。然后通过 `coroutineGroup.StartGroup()` 方法启动协程组中的所有协程。

19. 协程状态回调:协程状态回调可以让你在协程的各种状态下执行特定的逻辑。通过使用 Unity 的 `IEnumerator` 扩展方法 `OnCoroutineComplete()` 和 `OnCoroutineCanceled()`,你可以定义协程完成或取消时的回调函数。 

IEnumerator MyCoroutine(){yield return new WaitForSeconds(2f);Debug.Log("协程完成");}void Start(){StartCoroutine(MyCoroutine().OnCoroutineComplete(OnCoroutineCompleted));}void OnCoroutineCompleted(){Debug.Log("协程完成回调");}

   在上面的示例中,我们使用 `OnCoroutineComplete()` 方法将 `OnCoroutineCompleted()` 方法作为回调函数传递给协程。当协程完成时,将调用回调函数。

20. 超时处理:协程可以配合超时处理机制来处理耗时操作。通过在协程中使用计时器和条件判断,你可以控制协程执行的时间,并在超时时执行相应的逻辑。  

  IEnumerator MyCoroutine(){float startTime = Time.time;float timeout = 5f;while (Time.time - startTime < timeout){// 执行协程逻辑yield return null;}Debug.Log("协程超时");}

   在上面的示例中,我们使用计时器和条件判断来控制协程执行的时间。如果协程的执行时间超过设定的超时时间,则输出相应的信息。

PartⅦ

   在上面的示例中,协程A在其执行过程中启动了协程B,并在协程B完成后继续执行自身的逻辑。这种嵌套可以用于处理任务的顺序和依赖关系,使代码更具可读性和可维护性。

21. 协程的动态启动和停止:除了使用 `StartCoroutine()` 和 `StopCoroutine()` 方法外,协程还可以使用 `yield return StartCoroutine()` 和 `yield return StopCoroutine()` 语句来动态地启动和停止协程。

IEnumerator CoroutineA(){Debug.Log("协程A开始执行");yield return StartCoroutine(CoroutineB()); // 动态启动协程BDebug.Log("协程A继续执行");}IEnumerator CoroutineB(){Debug.Log("协程B开始执行");yield return StartCoroutine(CoroutineC()); // 动态启动协程CDebug.Log("协程B完成");}IEnumerator CoroutineC(){Debug.Log("协程C开始执行");yield return new WaitForSeconds(2f);Debug.Log("协程C完成");}

   在上面的示例中,协程A启动了协程B,协程B启动了协程C。这种动态启动和停止的方式可以根据需要控制协程的执行流程。

五、举例

using UnityEngine;
using System.Collections;public class CoroutineExample : MonoBehaviour
{private WaitForSeconds waitTime;private void Start(){waitTime = new WaitForSeconds(1f);// 启动协程StartCoroutine(SpawnObjects());}private IEnumerator SpawnObjects(){while (true){// 生成一个随机位置的立方体Vector3 randomPosition = new Vector3(Random.Range(-5f, 5f), 0f, Random.Range(-5f, 5f));GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);cube.transform.position = randomPosition;// 每隔一秒生成一个新的立方体yield return waitTime;}}
}

在上面的示例中,我们定义了一个 CoroutineExample 的脚本,其中包含了一个协程 SpawnObjects。在 Start() 方法中,我们通过 StartCoroutine() 启动了这个协程。

协程 SpawnObjects 持续生成随机位置的立方体,并在每隔一秒的时间间隔后生成一个新的立方体。通过使用 yield return waitTime 语句,协程将等待一秒钟后继续执行。

这个示例展示了如何利用协程来延迟执行和重复执行一段逻辑,从而实现在游戏中按一定时间间隔生成对象的效果。

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

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

相关文章

Android 之 传感器专题 (4) —— 其他传感器了解

本节引言&#xff1a; 在上一节的结尾说了&#xff0c;传感器部分因为笔者没怎么玩过&#xff0c;本节就简单的把剩下的几个常用的 传感器介绍一遍&#xff0c;当作科普&#xff0c;以后用到再慢慢研究~ 1.磁场传感器(Magnetic field sensor) 作用&#xff1a;该传感器主要用…

spring中LocalDateTime 转成字符串的时候注意事项

ApiOperation("查询课程发布信息") ResponseBody GetMapping("/r/coursepublish/{courseId}") public CoursePublish getCoursepublish(PathVariable("courseId") Long courseId) { CoursePublish coursePublish coursePublishService.getC…

Spring之HandlerInterceptor和RequestBodyAdvice

一个请求在Spring中处理流程是有多种方式拦截处理的&#xff0c;而且&#xff0c;请求是可以拆分为进入和响应2个操作的&#xff0c;进入我们通常会对请求参数做处理&#xff0c;而响应我们通常会对响应参数做处理&#xff0c;Spring提供了多种方式给开发者。 一、HandlerInte…

nowcoder NC236题 最大差值

目录 题目描述&#xff1a; 示例1 示例2 题干解析&#xff1a; 暴力求解&#xff1a; 代码展示&#xff1a; 优化&#xff1a; 代码展示&#xff1a; 题目跳转https://www.nowcoder.com/practice/a01abbdc52ba4d5f8777fb5dae91b204?tpId128&tqId33768&ru/exa…

由于找不到VCOMP140.DLL,无法继续执行代码。重新安装程序可能会解决此问题。

问题描述&#xff1a;最近使用奥比中光的Gemini2深度相机识别物体的深度信息&#xff0c;先是安装了OrbbecViewer软件(地址&#xff1a;https://vcp.developer.orbbec.com.cn/resourceCenter?defaultSelectedKeys107)。 我发现在我的台式机电脑上&#xff0c;可以运行&#xf…

Typora mac版本安装

提示&#xff1a;文章介绍&#xff0c;Typora在Mac系统中免费安装使用 文章目录 一、官网下载二、安装 一、官网下载 官网地址&#xff1a;https://www.typoraio.cn/ 二、安装 安装好后按 command 空格键&#xff0c;找到 Typora的安装路径 /Applications/Typora.app/Con…

Kubernetes(七)修改 pod 网络(flannel 插件)

一、 提示 需要重启服务器 操作之前备份 k8s 中所有资源的 yaml 文件 如下是备份脚本&#xff0c;仅供参考 # 创建备份目录 test -d $3 || mkdir $3 # $1 命名空间 # $2 资源名称&#xff1a; sts deploy configMap svc 等 # $3 资源备份存放的目录名称for app in kubec…

oauth2.0第2季 分布式认证与授权实现单点登录

一 oauth介绍 1.0 疑问汇总 1.使用jwttoken进行令牌传输&#xff0c;资源服务器在本地怎么验证token&#xff1f; 1.1 oauth的基础内容 1.1.1 oauth是什么 1.1.2 oauth的角色 1.1.3 oauth的认证流程 1.1.4 oauth的4种模式 1.2 为何要用oauth2.0 1.介绍单体架构 使用ses…

k8s节点pod驱逐、污点标记

一、设置污点&#xff0c;禁止pod被调度到节点上 kubectl cordon k8s-node-145 设置完成后&#xff0c;可以看到该节点附带了 SchedulingDisabled 的标记 二、驱逐节点上运行的pod到其他节点 kubectl drain --ignore-daemonsets --delete-emptydir-data k8s-node-145 显示被驱逐…

Android需要掌握的shell脚本基础

linux中sh是链接到bash上的&#xff0c;所以sh与bash在功能上是没有区别的&#xff0c;相当于bash解析器是sh的增强版本&#xff0c;所以安卓开发者可以在 git bash中 测试脚本 1&#xff0c;shell脚本运行与输出指令 $ cat test.sh echo 测试 【输出】$ /bin/bash test.…

LeetCode第26~30题解

CONTENTS LeetCode 26. 删除有序数组中的重复项&#xff08;简单&#xff09;LeetCode 27. 移除元素&#xff08;简单&#xff09;LeetCode 28. 找出字符串中第一个匹配项的下标&#xff08;简单&#xff09;LeetCode 29. 两数相除&#xff08;中等&#xff09;LeetCode 30. 串…

qt设计界面

widget.h #ifndef WIDGET_H #define WIDGET_H //防止文件重复包含#include <QWidget> //QWidget类所在的头文件&#xff0c;父类头文件 #include<QIcon> #include<QPushButton> …

VS的调试技巧

Visual Studiohttps://visualstudio.microsoft.com/zh-hans/vs/ 目录 1、什么是调试&#xff1f; 2、debug和release 3、调试 3.1、环境 3.2、 快捷键 3.2.1、F10和F11 3.2.2、ctrlF5 3.2.3、F5与F9 3.2.3.1、条件断点 3.3、监视和内存观察 3.3.1、监视 3.3.2、内存 …

[集创赛海云捷讯杯]全国二等奖经验分享

[集创赛海云捷讯杯]全国二等奖经验分享 一.前言二.我们的作品三.小结 一.前言 笔者是研一在校生&#xff0c;从五月份开始和本科生一起卷集创赛&#xff0c;经历初赛&#xff0c;分赛区决赛&#xff0c;全国总决赛&#xff0c;认识了很多一起做比赛的朋友收获颇丰。今年海云杯…

java反射机制并实现任意对象转JSON格式

目录 java反射机制 获取构造方法 获取属性 获取成员方法 使用反射机制将对象转为JSON形式 java反射机制 在运行状态中&#xff0c;任意一个类&#xff0c;都可以知道它的任意属性和方法&#xff0c;任意一个对象&#xff0c;都可以调用它的任意一个方法和属性。 获取反射…

RabbitMQ工作模式-发布订阅模式

Publish/Subscribe&#xff08;发布订阅模式&#xff09; 官方文档&#xff1a; https://www.rabbitmq.com/tutorials/tutorial-three-python.html 使用fanout类型类型的交换器&#xff0c;routingKey忽略。每个消费者定义生成一个队列关绑定到同一个Exchange&#xff0c;每个…

性能测试jmeter连接数据库jdbc(sql server举例)

一、下载第三方工具包驱动数据库 1. 因为JMeter本身没有提供链接数据库的功能&#xff0c;所以我们需要借助第三方的工具包来实现。 &#xff08;有这个jar包之后&#xff0c;jmeter可以发起jdbc请求&#xff0c;没有这个jar包&#xff0c;也有jdbc取样器&#xff0c;但不能发…

音视频 fmpeg命令裁剪和合并视频

一、生成测试文件 找三个不同的视频每个视频截取10秒内容 ffmpeg -i 沙海02.mp4 -ss 00:05:00 -t 10 -codec copy 1.mp4 ffmpeg -i 复仇者联盟3.mp4 -ss 00:05:00 -t 10 -codec copy 2.mp4 ffmpeg -i 红海行动.mp4 -ss 00:05:00 -t 10 -codec copy 3.mp4如果音视频格式不统一…

如果只发布terraform的部分功能,我们该怎么发布

如果你想在一个较大的 Terraform 配置中只发布其中的一部分功能&#xff0c;你可以考虑以下几种方法来实现&#xff1a; 使用 Workspaces&#xff1a; Terraform Workspaces 是一种将同一个配置文件拆分为多个环境的方式。你可以为不同的功能创建不同的 Workspace&#xff0c;然…

外观模式:简化复杂子系统的访问与使用

文章目录 1. 简介2. 外观模式的基本结构3. 外观模式的实现步骤4. 外观模式的应用与实例4.1 图形界面库的外观模式应用4.2 文件压缩与解压缩的外观模式应用4.3 订单处理系统的外观模式应用 5. 外观模式的优缺点5.1 优点5.2 缺点 6. 总结 1. 简介 外观模式是一种结构型设计模式&…