Unity笔记:协程

协程

协程有IEnumerator类型返回值,所以总要有至少一个yield return xxx

基础入门直接看这个就够了:gamedevbeginner - Unity 中的协程(如何以及何时使用它们)

考虑使用协程的典型情况:

  • 将对象移动到某个位置。
  • 为对象提供要执行的任务列表(如前面的待办事项列表示例中所示)。
  • 淡入淡出的视觉或音频。
  • 等待资源加载。

协程的特点:

  1. 无法显式检查协程是否已在运行
  2. 协程的执行持续的时间是不确定的。协程允许开发者在多个帧上执行游戏逻辑(这里就涉及到Unity如何依靠IEnumerator将协程拆分成多个帧执行)
  3. 协程的执行顺序也是不确定的,它是由Unity引擎控制的,它可能会在主循环的不同阶段执行。

在Unity中,协程的执行是由Unity引擎来管理的。当你启动一个协程时,Unity会在后台为该协程创建一个迭代器(iterator,这要求协程的返回值是IEnumerator),并在每一帧中逐步执行该迭代器。

  1. 按顺序执行:Unity会按照协程启动的顺序来执行它们。如果有多个协程在同一帧启动,它们将按照启动的顺序进行排列执行。
  2. 帧更新执行:协程的执行是在Unity的每一帧更新中进行的。每当Unity引擎执行一帧时,它会检查当前正在执行的所有协程,并根据协程的当前状态来决定是否继续执行、暂停执行或者完成执行。
  3. 暂停和继续:当协程执行到 yield return 语句时,Unity引擎会暂停该协程的执行,并将执行控制权交还给引擎。在等待的条件满足或者时间间隔过去后,Unity引擎会重新激活协程,并从 yield return 语句之后的代码开始继续执行。
  4. 时间管理:当协程使用 yield return new WaitForSeconds(time) 等待一段时间时,Unity引擎会在等待时间结束后再次激活该协程。这样可以确保协程在指定的时间间隔后再次执行。
  5. 性能优化:虽然协程可以在多个帧中执行,但过多的协程同时执行也可能会影响性能。因此,在使用协程时应注意控制协程的数量,避免不必要的性能消耗。

协程的启动

  1. 以字符串传入协程名字启动
  2. 直接传递协程方法的引用

这种方法必须要求协程和启动写在一个文件里。如果不在一个文件里,Unity会在当前对象所附加的脚本中查找对应的协程方法,并且启动方法只能是直接传递协程方法的引用

注:当使用字符串启动协程时,只能传入一个参数

// 以字符串传入协程名字启动
StartCoroutine("MyCoroutine");
// 以字符串传入协程名字启动最多一个参数
StartCoroutine("MyCoroutine", 1);
IEnumerator MyCoroutine(int value)
{// Code goes here...
}
// 直接传递协程方法的引用
StartCoroutine(MyCoroutine());
// 传递协程方法的引用在别的脚本
StartCoroutine(OtherScriptName.MyCoroutine());

协程与yield

任何协程暂停的代码都需要从yield return开始,yield 表示该方法是一个迭代器,它将在多个帧上执行,而 return 与常规函数一样,在该点终止执行并将控制权传递回调用方法。不同之处在于,使用协程时,Unity 知道从中断的地方继续该方法。yield return 后面的内容将指定 Unity 在继续之前等待多长时间。

等待到下一帧再执行

通过yield return null通常可以实现帧计数:

IEnumerator MyCoroutine()
{// 执行一些操作yield return null; // 暂停协程,等待下一帧// 在暂停之后是从这里继续执行
}

等待一定时间

暂停协程的执行一段时间,然后在指定的时间后继续执行。

IEnumerator MyCoroutine()
{// 执行一些操作yield return new WaitForSeconds(2.0f); // 暂停协程,等待2秒// 继续执行
}

如果要反复delay(如在while中),缓存 WaitforSeconds 对象的性能会稍好一些。

WaitForSeconds delay = new WaitForSeconds(1);

WaitForSecondsRealtime(5);会在游戏暂停也执行,这是和WaitForSeconds在游戏暂停时等待时间不流逝是不一样的

等待帧结束

这个用法会暂停协程的执行直到当前帧结束后继续执行。通常用于在当前帧渲染完成后执行一些操作,典型用途是截取屏幕截图。

IEnumerator MyCoroutine()
{// 执行一些操作yield return new WaitForEndOfFrame(); // 暂停协程,等待当前帧结束// 继续执行
}

等待某bool表达式为true

下面这个则是等待值为true才执行后面的

IEnumerator CheckFuel()
{yield return new WaitWhile(() => fuel > 0);print("tank is empty");
}

等待另一个协程执行完

IEnumerator MyCoroutine()
{print("Coroutine has started");yield return StartCoroutine(MyOtherCoroutine());// 在启动的协程完成后,代码将继续print("Coroutine has ended");
}

什么情况需要使用在当前帧结束完执行协程?主要是那些避免影响游戏性能和用户体验的场景:

  1. UI更新:有时你可能需要等待当前帧渲染完成后再更新UI,以确保UI元素在渲染前已经准备就绪,避免出现闪烁或不良的渲染效果。(那为什么不放在LateUpdate呢?)
  2. 延迟加载资源:在某些情况下,可能需要在当前帧结束后加载资源,以确保不影响当前帧的性能。例如,当某些资源仅在某些条件下才需要加载时,可以在下一帧加载这些资源,而不是立即加载。
  3. 执行复杂计算:如果有复杂的计算或处理需要在游戏逻辑更新之后执行,可以将这些操作放入协程中,在下一帧执行,以避免影响当前帧的性能。
  4. 网络请求或数据库操作:当需要进行网络请求或数据库操作时,可能需要等待当前帧结束后再执行,以避免阻塞主线程,从而提高游戏的流畅性和响应性。
  5. 场景切换后的操作:在场景切换后,可能需要进行一些初始化或清理操作,这些操作最好在当前帧结束后执行,以确保场景切换的流畅性和正确性。

yield的特殊情况

IEnumerator FetchData(){Todo[] todos;User[] users;// USERSvar www = new WWW(USERS_URL);yield return www;if (!string.IsNullOrEmpty(www.error)){Debug.Log("An error occurred");yield break;}var json = www.text;try{var userRaws = JsonHelper.getJsonArray<UserRaw>(json);users = userRaws.Select(userRaw => new User(userRaw)).ToArray();}catch{Debug.Log("An error occurred");yield break;}// TODOSwww = new WWW(TODOS_URL);yield return www;if (!string.IsNullOrEmpty(www.error)){Debug.Log("An error occurred");yield break;}json = www.text;try{var todoRaws = JsonHelper.getJsonArray<TodoRaw>(json);todos = todoRaws.Select(todoRaw => new Todo(todoRaw)).ToArray();}catch{Debug.Log("An error occurred");yield break;}// OUTPUTforeach (User user in users){Debug.Log(user.Name);}foreach (Todo todo in todos){Debug.Log(todo.Title);}}void Start(){StartCoroutine(FetchData());}
}

注意这里可以yield return www是因为在Unity中,WWW类实现了IEnumerator接口,因此可以在协程中使用yield return来暂停协程的执行,直到网络请求完成。一旦请求完成,协程会继续执行下面的代码,完成数据的处理和输出。

协程的停止

协程在执行其代码后自动结束,无需显式结束

  1. 协程内部中断使用yield break
  2. 协程外部中断使用StopCoroutine("MyCoroutine");
  3. 停止 MonoBehaviour 上的所有协程StopAllCoroutines();

StopAllCoroutines();停止由调用它的脚本启动的所有协程,因此它不会影响在其他地方运行的其他协程,但是如果脚本 A 运行的协程启动了脚本B上的一个协程,那从脚本 A 调用StopAllCoroutines();会把这俩都关了,但是从脚本B上调用则一个也不会停止。

销毁或禁用游戏对象将终止从该对象调用的任何协程,也就是说存在一种情况是销毁了游戏对象A但是其上的协程没有终止,因为那个协程是由B调用启动的。

协程与Thread的区别

Unity的协程(Coroutine)和C#的线程(Thread)是用于实现异步操作的两种不同的机制

  • 如果要使用异步执行来表达游戏逻辑,请使用协程。
  • 如果要使用异步执行来利用多个 CPU 内核,请使用线程,但是在子线程不要接触任何Unity原生功能,因为它完全不知道 Unity 主循环目前处于什么状态

区别如下:

  1. 执行上下文

    • 协程在 Unity 中是基于协程管理器(Coroutine Manager)的,运行在主线程中。因此,协程可以访问 Unity 的 API 和对象,但不能进行跨线程操作。
    • 线程是独立的执行线程,与主线程分离。线程可以在后台执行任务,允许进行耗时操作,但在访问 Unity 的 API 和对象时,需要使用线程安全的方式,例如通过主线程调用 UnityMainThreadDispatcher 或使用 UnityMainThreadDispatcher 等工具。
  2. 开销

    • 协程相对于线程来说,开销较小。因为协程是基于迭代器的,不需要像线程那样创建和管理额外的线程。
    • 线程的创建和销毁需要更多的系统资源,因此在某些情况下,线程可能会导致更大的性能开销。
  3. 并发性

    • 协程在 Unity 中是顺序执行的,并不能真正实现多线程并发。即使使用了多个协程,它们仍然是在主线程中按顺序执行的。
    • 线程是真正的并发执行机制,可以在多个线程上同时执行不同的任务。这使得线程更适合处理需要同时进行的耗时操作,如网络请求、文件IO等。
  4. 线程安全

    • 协程可以直接访问 Unity 的对象和 API,因此不需要担心线程安全的问题。
    • 线程需要考虑线程安全的问题,需要使用锁、互斥量等机制来保护共享资源,以避免多线程同时访问导致的数据竞争和不确定性。

Unity 的 API 大部分是单线程访问的,这意味着在非主线程上直接访问 Unity 的 API 可能会导致异常或不确定的行为。如果没有特别需要,建议尽量避免在 Unity 中使用线程,而是使用协程等 Unity 提供的异步机制来处理异步操作。

协程更适合处理需要在多个更新周期中分批执行的任务,而线程更适合处理需要利用多核CPU的任务

协程、Invoke与Async

在Unity中,有几种常用的用于处理异步操作的方法:

  1. 协程(Coroutines):协程是Unity中用于处理异步操作的一种技术,它允许在多帧中分解一个操作,以避免阻塞主线程。
  2. Invoke:一种用于延迟调用方法的方法。可以使用Invoke方法在一定时间后调用指定的方法。
  3. Async:C#中用于处理异步操作的关键字,通常与await一起使用。在Unity中,可以使用Async/Await来处理异步操作,比如网络请求、文件读写等。

Async 和 Await 函数与协程之间的最大区别之一是它们可以返回一个值,而通常协程不能这样做。

Unity Documentation - MonoBehaviour.Invoke

Medium - Unity: Leveling up with Async / Await / Tasks

关于Coroutines与Async:

  1. 旧版本Unity可能不支持C#的Async/Await特性
  2. 协程侧重于分帧处理,Async侧重于异步

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

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

相关文章

Spring Boot与Netty的完美结合:打造高性能网络通信

Spring Boot与Netty的完美结合&#xff1a;打造高性能网络通信 引言 在Java生态中&#xff0c;Spring Boot以其快速开发、简洁配置和丰富的生态支持赢得了众多开发者的喜爱。然而&#xff0c;当涉及到高性能、低延迟的网络通信时&#xff0c;传统的Servlet容器&#xff08;如…

向量的内积、长度、正交性

目录 向量的内积 向量的长度&#xff08;模&#xff09; 标准正交基 标准正交化 正交矩阵 向量的内积 向量的长度&#xff08;模&#xff09; 标准正交基 标准正交化 正交矩阵

JavaWeb——014SpringBoot原理(配置优先级、Bean管理、SpringBoot原理)

SpingBoot原理 目录 SpingBoot原理1. 配置优先级2. Bean管理2.1 获取Bean2.2 Bean作用域2.3 第三方Bean 3. SpringBoot原理3.1 起步依赖3.2 自动配置3.2.1 概述3.2.2 常见方案3.2.2.1 概述3.2.2.2 方案一3.2.2.3 方案二 3.2.3 原理分析3.2.3.1 源码跟踪3.2.3.2 Conditional 3.2…

详解深度学习中的教师-学生模型(Teacher- Student Model)

文章目录 基本流程训练方法分类1. 软标签&#xff08;Soft Labels&#xff09;软化概率分布的具体步骤软化有什么好处&#xff1f; 2. 特征匹配&#xff08;Feature Matching&#xff09;3. 注意力转移&#xff08;Attention Transfer&#xff09;4. 知识图谱或规则迁移5. 隐空…

超市小程序有哪些功能 怎么制作

超市小程序是非常有用的工具&#xff0c;可以帮助超市提升用户体验&#xff0c;提高销售额。下面我们来看一下超市小程序可以具备哪些功能&#xff0c;以及如何制作一个高效的超市小程序。 1. **商品展示与搜索功能**&#xff1a;用户可以浏览超市的商品信息&#xff0c;包括价…

一篇搞定分布式缓存

缓存雪崩 缓存雪崩我们可以简单的理解为&#xff1a;由于原有缓存失效&#xff0c;新缓存未到期间所有原本应该访问缓存的请求都 去查询数据库了&#xff0c;而对数据库 CPU 和内存造成巨大压力&#xff0c;严重的会造成数据库宕机。从而形成一系列 连锁反应&#xff0c;造成整…

同等学力申硕专业介绍——管理学硕士

同等学力申硕的专业很多。 目前有十三大门类&#xff0c;分别是医学、法学、管理学、工学、教育学、经济学、艺术学、文学、历史学、理学、哲学、农学、军事学等&#xff0c;每个大门类中都有很多的细分专业。 今天为大家介绍同等学力申硕专业——管理学。 专业介绍 管理学是…

maven 包管理平台-07-plugins 常见插件介绍

拓展阅读 maven 包管理平台-01-maven 入门介绍 Maven、Gradle、Ant、Ivy、Bazel 和 SBT 的详细对比表格 maven 包管理平台-02-windows 安装配置 mac 安装配置 maven 包管理平台-03-maven project maven 项目的创建入门 maven 包管理平台-04-maven archetype 项目原型 ma…

【学习记录】PointLIO代码 update_iterated_dyn_share_modified 中函数指针的用法

最近在看PointLio的代码&#xff0c;有一部分看了半天没看懂&#xff0c;学习整理如下。 1、PointLio在迭代卡尔曼部分的代码 在esekfom.hpp中&#xff0c;有部分代码如下&#xff1a; void init_dyn_share_modified(processModel f_in, processMatrix1 f_x_in, measurement…

上班族真香副业:工资4500,靠steam游戏搬砖项目月入过w

steam游戏搬砖项目已经存在好多年了&#xff0c;这个项目比较冷门且能持续稳定盈利&#xff0c;是一个非常不错的项目。即使你没玩过steam游戏也没关系&#xff0c;这个steam游戏搬砖项目既不需要你会玩游戏&#xff0c;也不需要你懂英语。 steam游戏搬砖项目的盈利点在汇率差和…

【postgresql 基础入门】UPSERT语句,INSERT违反约束条件时可以转变为UPDATE语句,UPDATE与INSERT的合体

upsert插入更新 ​专栏内容&#xff1a; postgresql内核源码分析手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 系列文章 入门准…

leetcode347.前K个高频元素

先使用map&#xff0c;统计每个字符出现的频率&#xff0c;然后使用优先队列根据字符出现频率存储字符&#xff0c;然后弹出堆中元素&#xff0c;弹出K次完成操作&#xff01; 如果看不懂本题CPP语法的&#xff0c;可以参考我的另外一篇博客------------->CPP优先队列priori…

Python的数据库编程基础知识

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd;如果停止&#xff0c;就是低谷&#xf…

【代码随想录算法训练营Day29】 491.递增子序列;46.全排列;47.全排列 II

文章目录 ❇️Day 29 第七章 回溯算法 part05✴️今日内容❇️491.递增子序列自己的思路随想录思路自己的代码 ❇️46.全排列思路代码流程 ❇️47.全排列 II思路代码 ❇️Day 29 第七章 回溯算法 part05 ✴️今日内容 491.递增子序列46.全排列47.全排列 II ❇️491.递增子序…

03.07_111期_C++_string 的增删查改实现笔记

尝试编写的错误总结 1. 对于 insert 的实现&#xff0c;找出下面代码的冗余 第一 使用了一个tmp来承接会被 \0 冲掉的位置的字符&#xff0c;实际上可以直接利用strncpy&#xff0c; 第二 while循环的循环结束调节并没有把pos位置后面的那个字符进行移动&#xff0c; 存在隐藏…

【性能测试】Jmeter+InfluxDB+Grafana 搭建性能监控平台

一、背景 为什么要搭建性能监控平台&#xff1f; 在用 Jmeter 获取性能测试结果的时候&#xff0c;Jmeter自带的测试报告如下&#xff1a; 这个报告有几个很明显的缺点&#xff1a; 只能自己看&#xff0c;无法实时共享&#xff1b;报告信息的展示比较简陋单一&#xff0c;不…

在外包公司搞了2年,出来技术都没了...

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落&#xff01;而我已经在一个企业干了2年的的功能…

网络工程师笔记9

动态路由 RIP路由协议 配置简单 易于维护 适用于小型网络 周期性是30s发一次

MyBatis-Plus如何娴熟运用乐观锁

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 MyBatis-Plus如何娴熟运用乐观锁 前言乐观锁的基本概念基本概念和原理&#xff1a;为何乐观锁是解决并发问题的有效手段&#xff1a; MyBatis-Plus中乐观锁的支持1. Version 注解&#xff1a;2. 配置乐…

每日一篇 3.8

i do not have so many time to write down the words on the paper and then text on the internet.so i decide to just text the words on the internet.besides . has been flooded with work 他被工作淹没了他的工作很多 he is busy at work Recruiting 招募 if we sa…