unity协程coroutine 简明教程

article/2025/10/31 22:44:21

本篇内容基于 https://gamedevbeginner.com/coroutines-in-unity-when-and-how-to-use-them/
以及官方教程

为什么使用协程

协程非常适合设置需要随时间发生变化的游戏逻辑。很自然我们会想到update,update里指出每一帧unity会执行什么操作。协程则可以将代码从update中解放出来,至于为什么要这样做,请看例子:

假设我们有一辆坦克,当点击地面时,我希望坦克转向我点击的位置,朝该位置移动,到达后,等待一秒再开火。像这样:
请添加图片描述
那么我们可以给坦克列一个行动清单:

  • 转向特定角度
  • 移动到特定点
  • 等待一秒后开火

这样一个看起来简单的逻辑,如果要再update中执行,将变得非常混乱且难以理解,因为我们要想这三个步骤按照顺序(而非同时)发生,我们要设置很多额外的变量,代码如下:

// Please don't do this
bool tankMoving;
bool facingRightWay; 
bool tankInPosition;
float timer;
void Update()
{if(Input.GetMouseButtonDown(0)){tankMoving = true;}if (tankMoving){MoveTank();}
}
void MoveTank(){if (facingRightWay == false){if (angleIsWrong){TurnTank();}else if (angleIsCorrect){facingRightWay = true;}}if (facingRightWay && tankInPosition == false){if (positionIsWrong){MoveToPosition();}else if (positionIsCorrect){tankInPosition = true;}}if (facingRightWay && tankInPosition && timer < 1) {timer += Time.deltaTime;}else if (facingRightWay && tankInPosition && timer > 1){FireTank();facingRightWay = false;tankInPosition = false;tankMoving = false;timer = 0;}}

可以看到,要判断哪些事情已经发生了,当前帧可以做哪些事情,非常混乱且易出错,如果用协程?

void Update()
{if(Input.GetMouseButtonDown(0)){StartCoroutine(MoveTank());}
}
IEnumerator MoveTank(){while(facingWrongWay){TurnTank();yield return null;}while (notInPosition){MoveToPosition();yield return null;}yield return new WaitForSeconds(1);Fire();    }

这次,代码更像一个代办事项清单,每一个动作都在最后一个动作完成后执行。Unity处理每一个while循环时,直到其条件不为真,然后处理下一个。
实际上这样与unity执行常规函数的方式并没有不同,只是这样逻辑是在多帧而不是一帧上执行的。
因为yield关键字,它告诉unity:“这一帧就执行到这里停下!下一帧再从这儿开始!”
如果理解不了,举一个例子,把每一帧想象成你打了一天的游戏,晚上这个yield出现了,它会叫你睡觉,存下进度,明天接着这里再玩。之前呢,我们是一天从早到晚不吃不喝地玩(所有的操作都在一帧里执行)现在呢,我们是每天玩一点,分多天去玩(操作分在多帧里)人的负担是不是小了很多。

何时在unity中使用协程

当你想要创建需要暂停的动作、按顺序执行一系列步骤、或者想要运行长时间任务(这个任务所要花费的时间比一帧长,举例就是这个游戏你一天通不了关,,)这些个时候,你就要考虑用协程。举例包括:

  • 将对象移动到某个位置
  • 为对象提供要执行的任务列表(比如前面的坦克)
  • 淡入淡出的视觉效果或音频
  • 等待资源加载

那么,如何编写协程?

如何编写协程

协程的结构和常规函数基本一致,但有几个关键区别。

首先,协程中的返回类型是IEnumerator,like this:

IEnumerator MyCoroutine()
{// Code goes here!
}

先不用管这个IE什么的,现在只要知道,这是unity将函数执行拆分到多个帧的实现方式。
就像一般函数一样,我们可以将参数传递给协程:

IEnumerator MyCoroutine(int number)
{number++;// Code goes here.
}

只要协程没有被终止,在协程中的变量都会保持值(即变量的值会带到下一帧去,没有人每天都会从头开始打游戏吧?)
此外,和常规函数不同的是,协程允许我们在代码执行当中,用yield语句暂停代码。

如何在unity中暂停协程(yield)

在我们希望函数终端的时候,使用关键字yield return。
yield表面方法是一个迭代器,并且它将执行超过一帧;而return与常规函数一样,会在该点终止执行,并将控制权交给调用它的方法。
yield return之后的内容,将指定unity在继续执行钱,等待多久,我们有哪些选择呢?

yield return null (等到下一帧再执行)

yield return null告诉unity等到下一帧再执行,将它与while结合起来:

IEnumerator MyCoroutine(){int i = 0;while (i < 10){// Count to Teni++;yield return null;}while (i > 0){// Count back to Zeroi--;yield return null;}// All done!}

unity将完成第一个循环,每一帧累加一个数字,然后是第二个循环,每帧减少一个数字,直到代码块结束。

如果没有 yield return null,所有代码将立即执行,和常规函数一样。

wait for seconds(等待一段时间)

Wait For Seconds或Wait For Seconds Real Time(使用未缩放的时间)允许我们指定确切的等待时间。它只能在 Coroutine 中使用(即它在 Update 中不起作用)。

就像之前一样,我们需要将 Wait for Seconds 与yield return语句一起使用,在这种情况下,需要使用new关键字才能使其工作。

IEnumerator WaitFiveSeconds(){print("Start waiting");yield return new WaitForSeconds(5);print("5 seconds has passed");}

这样的写法适合一次性等待。如果要重复延迟,先缓存一下WaitForSeconds对象好一些(不用每次都new)

WaitForSeconds delay = new WaitForSeconds(1);Coroutine coroutine;void Start(){StartCoroutine("MyCoroutine");}IEnumerator MyCoroutine(){int i= 100;while (i>0){// Do something 100 timesi--;yield return delay;}// All Done!}

Wait for Seconds Real Time执行完全相同的功能,但使用未缩放的时间。这意味着即使更改时间刻度,它仍然可以工作,例如暂停游戏时。

IEnumerator WaitFiveSeconds(){// Pause the gameTime.timeScale = 0;yield return new WaitForSecondsRealtime(5);print("You can't stop me");}

Yield Return Wait Until / Wait While (等待委托)

Wait Until暂停执行,直到委托评估为真,而Wait While等待它为假后再继续。

以下是它在脚本中的使用:

int fuel=500;void Start(){StartCoroutine(CheckFuel());}private void Update(){fuel--;}IEnumerator CheckFuel(){yield return new WaitUntil(IsEmpty);print("tank is empty");}bool IsEmpty(){if (fuel > 0){return false;}else {return true;}}

等待帧结束

此特定指令会等到 Unity 渲染完每个 Camera 和 UI 元素,然后才实际显示帧。一个典型的用途是截屏。

IEnumerator TakeScreeshot(){// Waits until the frame is readyyield return new WaitForEndOfFrame();CaptureScreen();}

等待另一个协程

最后,可以让 yield 直到另一个由 yield 语句触发的协程完成执行。

只需在 yield return 之后使用 Start Coroutine 方法,如下所示:

void Start(){StartCoroutine(MyCoroutine());}IEnumerator MyCoroutine(){print("Coroutine has started");yield return StartCoroutine(MyOtherCoroutine());print("Coroutine has ended");}IEnumerator MyOtherCoroutine(){int i = 3;while (i>0){// Do something 3 timesi--;yield return new WaitForSeconds(1);}print("All Done!");}

在启动的协程完成后,代码将继续执行

如何启动协程

启动协程有两种方法,可以用函数名的字符串启动:

void Start(){StartCoroutine("MyCoroutine");}IEnumerator MyCoroutine(){// Coroutine business...}

也可以捅过引用方法名称来启动协程(和常规函数一样)

void Start(){StartCoroutine(MyCoroutine());}IEnumerator MyCoroutine(){// Do things...}

这两种技术都是 Start Coroutine 的重载方法。多数情况下,它们非常相似,但有几个关键区别。

首先,使用字符串方法而不是名称方法会对性能造成轻微影响。

另外,使用字符串启动协程时,只能传入一个参数,如下所示:

void Start(){StartCoroutine("MyCoroutine", 1);}IEnumerator MyCoroutine(int value){// Code goes here...}

然而,当停止协程时,会注意到使用一种方法与另一种方法之间的最大区别。

如何结束协程

协程在其代码执行后自动结束。您不需要显式结束协程。然而,我们可能希望在协程完成之前手动结束它。这可以通过几种不同的方式来完成。

从协程内部(使用yield break)

添加 yield break 语句将在协程完成之前结束它。这对于作为条件语句的结果退出协程很有用。

IEnumerator MyCoroutine(float value){if (value > 10){// Do one thingyield break;}// Do another}

这允许您创建可以退出协程的条件代码路径。

但是如果你想意外地停止一个协程怎么办。例如,想完全取消协程正在执行的操作。幸运的是,协程也可以在外部停止。

从协程外部结束(使用停止协程)

使用其字符串停止协程

如果是使用其字符串启动协程,则可以使用相同的字符串再次停止它。像这样

StopCoroutine("MyCoroutine");

但是,如果使用相同的字符串启动了多个协程,则在使用此方法时所有协程都将停止。

那么,如果想停止一个特定的协程实例怎么办?

通过引用停止协程

如果您在启动时存储对该 Coroutine 的引用,则可以停止特定的 Coroutine 实例。

bool stopCoroutine;
Coroutine runningCoroutine;void Start(){runningCoroutine = StartCoroutine(MyCoroutine());}void Update(){if (stopCoroutine == true){StopCoroutine(runningCoroutine);stopCoroutine = false;}}IEnumerator MyCoroutine(){// Coroutine stuff...}

停止 MonoBehaviour 上的所有协程

停止协程最简单、最可靠的方法是调用Stop All Coroutines。

像这样:

StopAllCoroutines();

这将停止由调用它的脚本启动的所有协程,因此它不会影响在其他地方运行的其他协程。


http://chatgpt.dhexx.cn/article/U1JrRBy2.shtml

相关文章

浅析Unity协程实现原理

介绍 协程Coroutine在Unity中一直扮演者重要的角色。可以实现简单的计时器、将耗时的操作拆分成几个步骤分散在每一帧去运行等等&#xff0c;用起来很是方便。 但是&#xff0c;在使用的过程中有没有思考过协程是怎么实现的&#xff1f;为什么可以将一段代码分成几段在不同帧执…

Unity协程和线程的区别深入理解(附实验展示)

Unity协程和线程的区别附实验展示 写在前面协程、进程、线程的概念进程与线程的区别协程与线程的区别实验1&#xff1a;协程中执行普通函数实验2&#xff1a;协程中开启另一个协程实验3&#xff1a;协程中开启WWW请求实验4&#xff1a;一个脚本中多个协程访问临界资源实验5&…

Unity 协程 Unity Task UniTask

协程 使用 StartCoroutine 和 IEnumerator yield return null 暂停执行并随后在下一帧恢复 yield return new WaitForSeconds(1f); 延迟1秒 waitfor系列有好几个 WaitForSeconds 和 WaitForSecondsRealtime 的区别 使用缩放时间将协程执行暂停指定的秒数。 实际暂停时间等于…

Unity 协程探究

一、官方手册中的描述 1、Manual/Coroutines 函数在调用时, “从调用到返回” 都发生在一帧之内&#xff0c;想要处理 “随时间推移进行的事务”&#xff0c; 相比Update&#xff0c;使用协程来执行此类任务会更方便。 协程在创建时&#xff0c;通常是一个 “返回值类型 为 …

Unity 协程底层原理解析

1、协程 unity是单线程设计的游戏引擎&#xff0c;unity实际上有多条渲染线程&#xff0c;但对于unity调用我们编写的游戏脚本&#xff0c;都是放在一个主线程当中进行调度的。因此对于我们写的游戏脚本unity是单线程的。 协程不是进程或者线程&#xff0c;它的执行过程更类似…

Unity协程那些事儿

Unity协程那些事儿 1、什么是协程&#xff1f;2、协程的使用3、关于yield4、关于IEnumerator/IEnumerable5、从IEnumerator/IEnumerable到yield6、Unity协程机制的实现原理7、源码分析8、总结 1、什么是协程&#xff1f; 用过Unity的应该都知道协程&#xff0c;今天就给大家来…

Unity⭐️当Unity协程遇到while

文章目录 Unity协程是一个老生常谈的知识点了&#xff0c;但今天博主却差点被一篇文章忽悠了 那就是这句话&#xff1a; 为什么说这句话不对呢 那就是当遇到while时&#xff0c;我们再来检验这句话的正确性&#xff1a; 按上面那句话&#xff0c;应该每次yield return后&…

深入探讨Unity协程及其使用

深入探讨Unity协程及其使用 协程 协程在Unity中是个很重要的东东&#xff0c;相信很多人都使用过&#xff0c;它能够非常方便的进行异步等待操作&#xff0c;可以说&#xff0c;用好协程&#xff0c;可以使你的代码更加优雅&#xff0c;然而&#xff0c;如果用不好&#xff0…

Unity 协程、模拟协程

理解协程 IEnumeratoryield 强大的迭代器 IEnumerator 就是一个函数容器 里面保存了一个一个的函数 IEnumator会依次执行每个函数 而每个函数都有一个返回值 保存在IEnumator.Currect里面 看下面这个例子 IEnuermator start_Coroutine(){Debug.Log("HellWord")…

unity 协程特点

协程 一. Unity中使用协程1. 什么是协程2. 如何使用3. 协程的应用场景创建补间动画打字机效果异步加载资源 4. 注意事项 二. Unity协程的底层原理1. 协程本体&#xff1a;C#的迭代器函数2. 协程调度&#xff1a;MonoBehaviour生命周期中实现3. Unity协程的架构 三. 扩展Unity的…

Unity协程深入及实现

Unity协程深入 文章目录 Unity协程深入Unity协程简介Unity协程的用法协程的原理协程的缺陷自己实现协程参考 Unity协程简介 Unity中的协程能够允许我们将一个任务分散到多个帧中。注意&#xff1a;协程&#xff08;协同执行&#xff09;和多线程有本质区别。 协程不是线程&…

unity 协程

首先声明&#xff1a;协程不是线程&#xff0c;协程在主线程中运行&#xff0c;而线程是单独开辟线程 以下是u3d关于协程的调用机制的解释&#xff1a; “在Unity3D中&#xff0c;使用MonoBehaviour.StartCoroutine方法即可开启一个协同程序&#xff0c;也就是说该方法必须在…

【Unity】Unity协程(Coroutine)的原理与应用

文章目录 前言一、什么是协程二、应用场景1.异步加载资源2.将一个复杂程序分帧执行3.定时器 三、协程的使用注意事项 四、Unity协程的底层原理1. 协程本体&#xff1a;C#的迭代器函数2. 协程调度&#xff1a;MonoBehaviour生命周期中实现 五、参考资料 前言 本文是作者在学习U…

Unity-协程详解

1. 简介 unity的**协程&#xff08;Coroutine&#xff09;**只是在c#的基础上做了一层封装&#xff0c;其实yield是C#的关键字。 unity协程是一个能够暂停协程执行&#xff0c;暂停后立即返回主函数&#xff0c;执行主函数剩余的部分&#xff0c;直到中断指令完成后&#xff…

Unity 协程(Coroutine)原理与用法详解

前言&#xff1a; 协程在Unity中是一个很重要的概念&#xff0c;我们知道&#xff0c;在使用Unity进行游戏开发时&#xff0c;一般&#xff08;注意是一般&#xff09;不考虑多线程&#xff0c;那么如何处理一些在主任务之外的需求呢&#xff0c;Unity给我们提供了协程这种方式…

linux的用户管理

1.linux的用户管理 linux的用户管理和组管理&#xff0c; 每个用户都必须要有一个且仅有一个初始组&#xff0c;可以有多个附加组&#xff0c;使用useradd命令创建用户时&#xff0c;如果没有指定初始组&#xff0c;系统默认会创建一个于其同名的组。 用户和组信息保存在4个文…

Linux用户管理练习

Linux下用户分为3类&#xff1a;超级用户&#xff08;root&#xff09;、系统用户、普通用户。 超级用户的用户名为root&#xff0c;它具有一切操作权力&#xff0c;因此为安全起见&#xff0c;建议不要轻易的在root账户下面对文件进行操作。在Linux操作系统的字符界面&#xf…

Linux用户管理工具

Linux用户管理工具 1. 用户 1.1创建用户 useradd -m username该命令为用户创建相应的帐号和用户目录/home/username&#xff1b; passwd username该命令为用户设置密码 1.2 删除用户 userdel -r username不带选项使用 userdel&#xff0c;只会删除用户。用户的家目录将…

Linux上的用户管理

Linux是一个多用户多任务的系统&#xff0c;任何人想要访问系统资源&#xff0c;必须通过登录账号来访问系统资源 添加用户 useradd&#xff1a;添加用户的命令&#xff08;root才能添加用户&#xff09; 用法&#xff1a;useradd 用户名 例&#xff1a;useradd water 就创建…