Unity Coroutine

调用函数时,函数将运行到完成状态,然后返回。这实际上意味着在函数中发生的任何动作都必须在单帧更新内发生;函数调用不能用于包含程序性动画或随时间推移的一系列事件。例如,假设需要逐渐减少对象的 Alpha(不透明度)值,直至对象变得完全不可见。

错误做法:函数将在单个帧更新中执行。这种情况下,永远不会看到中间值,对象会立即消失。

void Fade()
{Color c = renderer.material.color;for (float alpha = 1f; alpha >= 0; alpha -= 0.1f){c.a = alpha;renderer.material.color = c;}
}

可以通过向 Update 函数添加代码(此代码逐帧执行淡入淡出)来处理此类情况。但是,使用协程来执行此类任务通常会更方便。

ps:协程能做的Update都能做,那为什么我们需要协程呢? 答:使用协程,我们可以把一个跨越多帧的操作封装到一个方法内部,代码会更清晰。

正确做法:

协程就像一个函数,能够暂停执行并将控制权返还给 Unity,然后在下一帧继续执行。在 C# 中,声明协程的方式如下:

IEnumerator Fade()
{Color c = renderer.material.color;for (float alpha = 1f; alpha >= 0; alpha -= 0.1f){c.a = alpha;renderer.material.color = c;yield return null;}
}

此协程本质上是一个用返回类型 IEnumerator 声明的函数,并在主体中的某个位置包含 yield return 语句。yield return null 行是暂停执行并随后在下一帧恢复的点。要将协程设置为运行状态,必须使用 StartCoroutine 函数:

void Update()
{if (Input.GetKeyDown("f")){StartCoroutine(Fade());}
}

 如何使用

MonoBehaviour.StartCoroutine()方法可以开启一个协程,这个协程会挂在该MonoBehaviour下。

MonoBehaviour生命周期的UpdateLateUpdate之间,会检查这个MonoBehaviour下挂载的所有协程,并唤醒其中满足唤醒条件的协程。

要想使用协程,只需要以IEnumerator为返回值,并且在函数体里面用yield return语句来暂停协程并提交一个唤醒条件。然后使用StartCoroutine来开启协程。

IEnumerator CoroutineA(int arg1, string arg2)
{Debug.Log($"协程A被开启了");yield return null;Debug.Log("刚刚协程被暂停了一帧");yield return new WaitForSeconds(1.0f);Debug.Log("刚刚协程被暂停了一秒");yield return StartCoroutine(CoroutineB(arg1, arg2));Debug.Log("CoroutineB运行结束后协程A才被唤醒");yield return new WaitForEndOfFrame();Debug.Log("在这一帧的最后,协程被唤醒");Debug.Log("协程A运行结束");
}
​
IEnumerator CoroutineB(int arg1, string arg2)
{Debug.Log($"协程B被开启了,可以传参数,arg1={arg1}, arg2={arg2}");yield return new WaitForSeconds(3.0f);Debug.Log("协程B运行结束");
}

默认情况下,协程将在执行 yield 后的帧上恢复,但也可以使用 WaitForSeconds 来引入时间延迟

应用

创建补间动画

补间动画指的是在给定若干个关键帧中插值来实现的动画。 如:给定两个时间点的Alpha值,可以插值出一个淡入淡出的动画效果。

创建补间动画更常用的做法是使用Dotween插件。

打字机效果

很多游戏的人物对话界面中,文字并不是一开始就显示在对话框中的,而是一个一个显示出来的。这种将文本一个一个字的显示出来的效果称之为打字机(Typewriter)。

使用协程,你可以每显示一个字符后等待若干时间,从而实现打字机效果。 b站上有一个基于协程的打字机效果的简单实现(https://www.bilibili.com/video/BV1cJ411Y7F6)

异步加载资源

资源加载指的是通过IO操作,将磁盘或服务器上的数据加载成内存中的对象。资源加载一般是一个比较耗时的操作,如果直接放在主线程中会导致游戏卡顿,通常会放到异步线程中去执行。

举个例子,当你需要从服务器上加载一个图片并显示给用户,你需要做两件事情:

  1. 通过IO操作从服务器上加载图片数据到内存中。
  2. 当加载完成后,将图片显示在屏幕上。

其中,2操作必须等待1操作执行完毕后才能开始执行。

在传统的互联网应用中,一般会使用回调函数来实现类似功能:

//伪代码
​
//提供给用户的接口
void ShowImageFromUrl(string url)
{LoadImageAsync(url, Callback); //开启一个异步线程来加载图像,加载完成后会自动调用回调函数
}
​
//回调函数
void Callback(Image image)
{Show(image);
} 

我们也可以改写成协程的形式:

//伪代码
​
IEnumerator ShowImageFromUrl(string url)
{Image image = null;yield return LoadImageAsync(url, image); //异步加载图像,加载完成后唤醒协程Show(image);
}

使用协程来进行异步加载在Unity中是一个很常用的写法。

定时器操作

当你需要延时执行一个方法或者是每隔一段时间就执行某项操作时,可以使用协程。不过对于这种情况,也可以考虑写一个TickManager来管理定时操作。Electricity项目中的定时器

注意事项

  1. 协程是挂在MonoBehaviour上的,必须要通过一个MonoBehaviour才能开启协程。
  2. MonoBehaviour被Disable的时候协程会继续执行,只有MonoBehaviour被销毁的时候协程才会被销毁。
  3. 协程看起来有点像是轻量级线程,但是本质上协程还是运行在主线程上的,协程不是线程,更不是进程,也不是什么所谓的轻量级的线程或者进程。“协程”更像是Unity专门为某类特殊函数定义的一种「 执行规则 」,它是使用状态机并配合迭代器来模拟“并发”的效果,是一种水平高超的障眼法。协程更类似于Update()方法,Unity会每一帧去检测协程需不需要被唤醒。一旦你在协程中执行了一个耗时操作,很可能会堵塞主线程。这里提供两个解决思路:(1) 在耗时算法的循环体中加入yield return null来将算法分到很多帧里面执行;(2) 如果耗时操作里面没有使用Unity API,那么可以考虑在异步线程中执行耗时操作,完成后唤醒主线程中的协程。

协程的底层原理

unity利用了C#原本的yield(书签机制),当调用迭代器函数时,编译器在背后自动声明的一个嵌套可迭代类(实现了IEnumerable接口)中的枚举器的MoveNext函数,如果返回值为true,枚举器执行一直执行函数内的逻辑直到遇到yield return语句,此时给此处打一个标签(记录暂停位置)枚举器将current设置为当前遍历值,暂停当前函数执行,并返回给调用者current值,此时主线程重新回到调用者手中,对于unity来说是StartCorotine(),对于C#来讲,是回到Foreach的语句

此时Unity开始参与进来

IEnumerator的重点对于Unity看重的不是其遍历函数这一点,而是能暂停一个函数的执行跳转到另一个函数中,在迭代器内部的for循环我们可以删掉“获取上次数组返回的位置,movenext等逻辑”,将其替换为我们自己的功能代码,返回值我们也不需要返回数组元素,返回null就可以。这样就将迭代器的遍历数组功能变成一个副产物

由于DLL格式我们无法看到源代码,通过实验我们发现无论返回我们发现无论我们返回new GameObject、null、999这些未定义类型,unity的处理方式都是视而不见,代码依旧是下一帧调用movenext,和C#自带的没什么区别。Unity.StartCorroutine内部的伪代码应该是

用if语句判断返回值类型,如果返回了Waitforsecond等unity规定的几个返回值,就执行对应

if语句中的逻辑,而未定义的类的处理逻辑放入了else里,而对应的处理就是执行后什么也不做,等待下一帧继续Movenext()。stop for x second 可能是內部维护了一个timer

所以我们知道协程并不是多线程而是单线程,只是利用迭代器的暂停函数执行,并允许我们插入一些代码并执行的性质让我们意味其是一个多线程操作

我们也得到一些启发:可以利用完全不相干的事物的本质去实现另一个事物,迭代器遍历数组其本质是暂停函数执行其他函数的代码,利用这一点Unity做了协程

迭代器 = 状态机 + 枚举器 C# IEnumerator,IEnumerable ,Iterator-CSDN博客

迭代器函数返回值是IEnumerator是给调用者的,这样调用者才能通过迭代器函数使用MoveNext,获取Current。

协程分为两部分,协程与协程调度器:协程仅仅是一个能够中间暂停返回的函数,而协程调度是在MonoBehaviour的生命周期中实现的。 准确的说,Unity只实现了协程调度部分,而协程本身其实就是用了C#原生的”迭代器方法“

扩展Unity协程

扩展协程有两个思路,一种是自己另外写一套,可以有高度的定制性;另一种是对Unity现有的协程系统进行封装,可以兼容Unity现有的WaitForXXX。完整代码 CCoroutineMgr.cs

进程线程协程

进程是操作系统资源分配的基本单位 线程是处理器调度与执行的基本单位

1. 进程(Process)

  • 定义:进程是运行中的程序实例,每一个进程都独立拥有自己的指令和数据,所以称为资源分配的基本单位。其中数据又分布在内存的不同区域。一个运行中的进程所占有的内存大体可以分为四个区域:栈区、堆区、数据区、代码区。其中代码区存储指令,另外三个区存储数据。
  • 特点
    • 进程之间的资源相对独立,互不干扰。
    • 因为进程拥有独立的内存空间,因此进程间的通信(IPC)相对复杂。
    • 进程的创建和销毁以及上下文切换开销较大。

2. 线程(Thread)

  • 定义:线程是进程中的执行单元,通常被称为轻量级进程。一个进程可以包含多个线程,这些线程共享进程的资源。线程是处理器调度和执行的基本单位,一个线程往往和一个函数调用栈绑定,一个进程有多个线程,每个线程拥有自己的函数调用栈,同时共用进程的堆区,数据区,代码区。操作系统会不停地在不同线程之间切换来营造出一个并行的效果,这个策略称为时间片轮转法。
  • 特点
    • 线程之间的切换开销比进程小。
    • 同一进程内的线程可以共享内存、文件描述符等资源。
    • 多线程可以提高程序的并发性和性能,适合处理I/O密集型和计算密集型任务。

3. 协程(Coroutine)

  • 定义: 一切用户自己实现的,类似于线程的轮子,都可以称之为是协程。C#中的迭代器方法是协程; Unity在迭代器的基础上扩展出来的协程模块是协程;
  • 特点
    • 协程不是由操作系统调度,而是由程序控制,因此切换非常高效且开销小。
    • 协程能够在执行过程中自己保存状态,可以在某个点暂停执行,并在稍后恢复。
    • 通常用于需要高并发的场景,如网络编程和游戏开发。

4. 关系

  • 层级关系

    • 进程是最上层的抽象,它包含一个或多个线程。
    • 线程是进程内的执行单元,多个线程共享同一进程的资源。
    • 协程则是一种更高层次的抽象,用于高效地管理线程内的多个执行任务。
  • 应用场景

    • 进程适合需要严格资源隔离的任务,如大型服务或独立的应用程序。
    • 线程适合 CPU 密集型或 I/O 密集型的任务,可以通过多线程提高并发性。
    • 协程更适合需要大量并发但对性能要求极高的场景,尤其是在处理 I/O 操作时。

Reference

漫画秒懂 Unity 的协程与迭代器(Coroutine 与 Enumerator) - 知乎

Unity协程的原理与应用 - 知乎

 Unity官方的异步加载场景的示例 倩女幽魂手游中的资源加载与更新方案

SC102课程

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

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

相关文章

qt QGraphicsProxyWidget详解

1. 概述 QGraphicsProxyWidget 类是 Qt 图形视图框架中的一个关键类,它允许 QWidget 组件被嵌入到 QGraphicsScene 中。QGraphicsProxyWidget 作为一个代理,它在 QGraphicsScene 和 QWidget 之间建立了桥梁,使得 QWidget 可以在 QGraphicsVi…

【数据集】GPM IMERG Daily Precipitation Dataset

目录 数据集概述时空分辨率:数据下载参考数据集概述 Global Precipitation Measurement (GPM) IMERG Daily Precipitation Dataset 简介 Global Precipitation Measurement (GPM) 是由美国国家航空航天局(NASA)和日本宇宙航空研究开发机构(JAXA)联合发起的一项全球降水观…

MySQL 迁移 dm

参考链接 此处为语雀内容卡片,点击链接查看:MySQL 5.7.27 迁移 DM 8 语雀 迁移前准备 以下中的命名,密码都是可修改的,这里给出的就只是一个例子 创建表空间 # 创建表空间名为 dbTest,路径为 Z:\fei\data\dm\dbT…

新版IJidea 如何打开数据库窗口(2024.2.4 版)(连接数据库)

新版IJidea 2024.2.4 如何打开数据库窗口? 方式:使用插件,Database Navigator 1.安装插件,步骤如下: 打开 Settings/Preferences 对话框(快捷键 CtrlAltS)。前往 Plugins 菜单项。在搜索框中…

MySQL:left join后用on与where的区别

一、前言 前几天项目中,写SQL时本想通过 A left B join on and 后面的条件来使查出的两条记录变成一条,奈何发现还是有两条。在此记录一下,on与where的区别。 二、ON 原始数据展示 SELECT t1.*,t2.* FROM t_test_staff t1 left join t_te…

Spark 核心概念与宽窄依赖的详细解析

Spark 的介绍与搭建:从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交:本地与集群模式全解析-CSDN博客 Spark on YARN:Spark集群模式…

【树莓派raspberrypi烧录Ubuntu远程桌面登入树莓派】

提示:本文利用的是Ubuntu主机和树莓派4B开发板,示例仅供参考 文章目录 一、树莓派系统安装下载前准备工作下载安装树莓派的官方烧录软件imagerimager的使用方法 二、主机与树莓SSH连接查看数梅派IP地址建立ssh连接更新树莓派源地址 三、主机端远程桌面配…

MySQL数据库专栏(四)MySQL数据库链接操作C#篇

摘要 本篇文章主要介绍C#链接MySQL数据库的接口介绍,使用实例及注意事项,辅助类的封装及调用实例,可以直接移植到项目里面使用。 目录 1、添加引用 2、接口介绍 2.1、MySqlConnection 2.2、MySqlCommand 2.3、MySqlDataReader…

百度世界2024:AI应用的浪潮时刻

百度AI公式:“技术商业社会”。 作者|金豫 编辑|杨舟 互联网行业正迈入增长瓶颈期,这一点从主要科技巨头,如Meta、Alphabet、腾讯等近年来的表现中可见端倪:广告收入增速放缓,市场渗透率接近饱和。 单纯依赖流量获取…

Linux 通过nmcli配置网络并配置bond(网卡绑定)

Linux 通过nmcli配置网络并配置bond Linux 通过nmcli配置网络并配置bond1. 什么是 Bond 网卡绑定2. Bond 网卡绑定的常见模式3. 通过nmcli配置网络并做网卡绑定 Linux 通过nmcli配置网络并配置bond 1. 什么是 Bond 网卡绑定 Bond 网卡绑定(或 NIC Bonding&#xff…

【Stable Diffusion - Ai】小白入门必看(想控制AI的绘画结果?ControlNet 预处理篇)!真材实料!不卖课!!!

【Stable Diffusion - Ai】小白入门必看(想控制AI的绘画结果?ControlNet 预处理篇)!真材实料!不卖课!!! 对于ControlNet来说,很多人都会感觉到陌生;这个插件…

几何合理的分片段感知的3D分子生成 FragGen - 评测

FragGen 来源于 2024 年 3 月 25 日 预印本的文章,文章题目是 Deep Geometry Handling and Fragment-wise Molecular 3D Graph Generation, 作者是 Odin Zhang,侯廷军,浙江大学药学院。FragGen 是一个基于分子片段的 3D 分子生成模…

【设计模式系列】享元模式(十五)

目录 一、什么是享元模式 二、享元模式的角色 三、享元模式的典型应用场景 四、享元模式在ThreadPoolExecutor中的应用 1. 享元对象(Flyweight)- 工作线程(Worker) 2. 享元工厂(Flyweight Factory)- …

为什么分布式光伏规模是6MW为界点

安科瑞 华楠 近日,能源局发布定义分布式光伏6MW及以上的光伏电站必须自发自用,自行消纳。多省能源局规定大于6MW的电站必须按集中式管理,另外大于6MW(包含)要省级审批,小于则由市级审批,10kV线…

RDD 算子全面解析:从基础到进阶与面试要点

Spark 的介绍与搭建:从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交:本地与集群模式全解析-CSDN博客 Spark on YARN:Spark集群模式…

四万字长文SpringBoot、Spring、SpringMVC等相关面试题(注:该篇博客将会持续维护 最新维护时间:2024年11月12日)

🧸本篇博客重在讲解SpringBoot、Spring、SpringMVC等相关面试题,将会实时更新,欢迎大家添加作者文末联系方式交流 📜JAVA面试题专栏:JAVA崭新面试题——2024版_dream_ready的博客-CSDN博客 📜作者首页&…

02_ElementUI

一.前端工程化 1.1 概述 前端工程化是使用软件工程的方法来单独解决前端的开发流程 中模块化、组件化、规范化、自动化的问题,其主要目的为了 提高效率和降低成本。 1.2 NodeJS的安装 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环 境,可以使 JavaS…

三、运算符、数据类型转换(显式、隐式)、语句(if、三元、switch、while、for)

1. 运算符 1.1 自增自减 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> &…

数据安全、信息安全、网络安全区别与联系

关键字&#xff1a; 信息安全 数据安全 网络安全 [导读] 在 “互联网 ” 被广泛提及的今天&#xff0c;安全问题也越来越多的受到人们关注&#xff0c;然而很多人对于 “信息安全”、“数据安全”、“网络安全” 的概念并不是很清楚。我们汇总了官方机构给这三者的定义&#…

基于单片机的多功能视力保护器设计(论文+源码)

1. 功能设计 本次课题为多功能视力保护器&#xff0c;具体设计功能如下&#xff1a; (1)当使用者的眼睛距离写字台低于25cm时&#xff0c;报警灯闪烁以提醒使用者及时调整坐姿。 (2)学习环境光线自动检测&#xff1a;当光照强度低于1001X时&#xff0c;语音提醒使用者调整光…