Unity协程机制详解
Unity的协程(Coroutine)是一种异步编程的机制,允许在多个帧之间分割代码的执行,而不阻塞主线程。与传统的多线程不同,Unity的协程在主线程中运行,并不会开启新的线程。
什么是协程?
协程是一种能在一定条件下暂停执行,并在稍后恢复执行的函数。它允许将一个任务拆分为多个小任务,每一小段任务可以在多个帧内完成,而不是在一帧内阻塞主线程。
在Unity中,协程通过StartCoroutine方法启动,并可以通过yield关键字来暂停执行,直到满足特定条件(例如等待时间、等待帧、等待另一个协程完成等)后继续运行。
协程的语法
在Unity中,协程的基本语法如下:
using UnityEngine;
using System.Collections;public class CoroutineExample : MonoBehaviour
{void Start(){// 启动一个协程StartCoroutine(MyCoroutine());}IEnumerator MyCoroutine(){Debug.Log("第1步: 开始协程");// 等待3秒yield return new WaitForSeconds(3);Debug.Log("第2步: 等待3秒完成");// 等待一帧yield return null;Debug.Log("第3步: 等待一帧完成");// 等待直到某个条件为真yield return new WaitUntil(() => Time.time > 5);Debug.Log("第4步: 等待直到Time.time > 5");}
}
- StartCoroutine(MyCoroutine()):启动一个协程,执行MyCoroutine()函数。
- yield return new WaitForSeconds(3):暂停执行,等待3秒后恢复执行。
- yield return null:暂停当前协程,等到下一帧再恢复执行。
- yield return new WaitUntil(() => Time.time > 5):暂停,直到Time.time > 5为true时恢复执行。
启动和停止协程
启动协程
StartCoroutine(“方法名”):使用字符串指定协程名称。
StartCoroutine(方法()):传递协程的IEnumerator方法。
示例:
StartCoroutine("MyCoroutine");
StartCoroutine(MyCoroutine());
停止协程
StopCoroutine(方法名或引用):停止指定的协程。
StopAllCoroutines():停止脚本中所有正在运行的协程。
Coroutine myCoroutine;void Start()
{myCoroutine = StartCoroutine(MyCoroutine());
}void StopIt()
{StopCoroutine(myCoroutine); // 停止指定的协程StopAllCoroutines(); // 停止当前脚本中的所有协程
}
注意:使用StopCoroutine时,不能使用StartCoroutine(“MyCoroutine”)的字符串方式,因为无法引用该协程。
协程的yield语法详解
yield的作用是暂停当前协程的执行,直到指定的条件满足。Unity中常见的yield操作有:
- yield return null; 等待一帧
- yield return new WaitForSeconds(t); 等待t秒
- yield return new WaitUntil(predicate); 等待直到条件为真
- yield return new WaitWhile(predicate); 等待直到条件为假
- yield return StartCoroutine(协程); 等待另一个协程完成
- yield break; 终止协程,立即退出
常见的协程应用场景
场景切换中的加载动画
在场景加载的过程中显示加载动画,而不是卡顿的黑屏。
IEnumerator LoadSceneAsync()
{AsyncOperation operation = SceneManager.LoadSceneAsync("SceneName");while (!operation.isDone){Debug.Log($"加载进度:{operation.progress * 100}%");yield return null; // 等待一帧}Debug.Log("场景加载完成");
}
动画和特效的控制
在播放一段动画或特效时,等待动画播放完成再继续后续操作。
IEnumerator PlayAnimation()
{Animator animator = GetComponent<Animator>();animator.Play("Attack");yield return new WaitForSeconds(2); // 等待动画播放2秒Debug.Log("动画播放完成,执行其他操作");
}
定时触发的任务
IEnumerator SpawnEnemies()
{while (true){Instantiate(enemyPrefab, transform.position, Quaternion.identity);yield return new WaitForSeconds(5); // 每隔5秒刷怪}
}
协程和多线程的区别
协程 | 多线程 |
---|---|
运行在主线程中 | 每个线程有独立的执行流 |
使用yield暂停 | 使用Thread.Sleep等 |
不需要上下文切换,效率高 | 需要操作系统的线程调度,性能开销大 |
主要用于异步逻辑操作 | 主要用于计算密集型操作 |
注意事项和最佳实践
- 不要阻塞主线程:协程在主线程中运行,while(true)的无限循环会冻结游戏。
- 避免频繁调用StartCoroutine:如果在Update中频繁调用StartCoroutine,会导致性能下降。
- 避免内存泄漏:未手动停止的协程会一直运行,导致内存泄漏。可用StopCoroutine来手动控制协程的停止。
- 不要用字符串调用协程:StartCoroutine(“MyCoroutine”)的方式不易调试和管理,建议使用StartCoroutine(MyCoroutine())。
- 不要滥用协程:协程适用于需要在多个帧内分段执行的任务。对于简单的帧内任务,尽量不要使用协程。
Unity的协程机制提供了一种轻量的异步任务调度,适合在游戏中实现等待、计时、加载动画、场景切换等操作。与多线程相比,协程的运行成本更低,控制也更简单。通过yield来暂停和恢复代码执行,可以显著提高游戏性能和玩家体验。合理使用协程有助于优化游戏中的异步任务。
Unity是如何实现协程的
Unity的协程原理基于迭代器(Iterator)和C#的IEnumerator接口,而不是多线程。Unity的协程并不会新开一个线程去执行逻辑,而是在主线程中调度,通过“分段执行”的方式控制代码的暂停和恢复。
协程的原理概述
- C#中的迭代器和yield return
- 协程的本质是C#的迭代器(Iterator),而yield return就是一种控制流的中断标志。
- 当执行到yield return时,Unity会将当前协程的“状态”保存起来,并在下一个“可用的时机”恢复执行。
- Unity的调度器(Coroutine Scheduler)
- Unity每一帧都会检查哪些协程需要恢复执行。
- 如果一个协程调用了yield return WaitForSeconds(2),Unity会在2秒后恢复该协程,并从上次中断的位置继续执行。
- 这种调度机制与多线程无关,一切都在主线程中运行。
- 执行流程
- Unity的MonoBehaviour管理协程,通过内部的队列/列表维护所有活跃的协程。
- 每一帧,Unity会遍历这些协程,检查是否满足“恢复执行”的条件(如时间已到、等待的条件达成等)。
- 如果条件满足,Unity会调用该协程的MoveNext()方法,让协程继续运行,直到遇到下一个yield语句。
协程的实现细节
要理解Unity如何实现协程,必须先掌握C#的IEnumerator和yield return的机制。
- C# 的 IEnumerator 和 yield return
当在C#中使用yield return时,编译器会自动将方法“拆分”成状态机(State Machine),而这个状态机可以跟踪函数的当前执行位置。
示例如下:
IEnumerator MyCoroutine()
{Debug.Log("第1步");yield return new WaitForSeconds(1); // 暂停1秒Debug.Log("第2步");yield return new WaitForSeconds(2); // 暂停2秒Debug.Log("第3步");
}
编译器生成的等价状态机代码如下(简化版,省略了复杂的状态机细节):
public class MyCoroutineStateMachine : IEnumerator
{private int state = 0; // 用于跟踪当前执行到的位置public object Current { get; private set; } // yield返回的内容public bool MoveNext(){switch (state){case 0:Debug.Log("第1步");Current = new WaitForSeconds(1);state = 1;return true; // 表示协程还没有结束case 1:Debug.Log("第2步");Current = new WaitForSeconds(2);state = 2;return true;case 2:Debug.Log("第3步");state = -1; // 表示协程结束return false;}return false;}public void Reset() { }
}
解释:
- state:控制当前协程的“步骤状态”,state=0表示正在执行第1步,state=1表示执行第2步。
- Current:表示yield return的结果(如WaitForSeconds(1))。
- MoveNext():每当Unity的调度器在新一帧时调用MoveNext(),协程的控制流就会跳转到当前的state位置。
- 当state为-1时,协程结束,MoveNext()返回false。
这段代码表明,Unity通过“状态机 + 迭代器”的机制,将协程拆分成多个小的执行步骤,这些步骤会在每一帧内被调度器分阶段运行。
- Unity的调度器 (Scheduler)
- Unity在每一帧都会遍历所有的协程,并调用每个协程的MoveNext()方法。
- 如果MoveNext()返回true,Unity会继续等待,否则Unity将把这个协程从队列中删除。
- Unity在执行MoveNext()时会检查Current的返回值:
- 如果是null:表示立即恢复(继续执行)
- 如果是WaitForSeconds:Unity会记录当前的“等待时间”,然后延迟恢复协程。
- 如果是CustomYieldInstruction:例如yield return new WaitUntil(),Unity会检测其条件是否满足。
Unity的协程工作流程图
[Unity Start] ↓
[协程被添加到队列] ↓
[每一帧调用协程的MoveNext()] ↓
[检测Current] └─> 如果是 WaitForSeconds,等待指定时间└─> 如果是 WaitUntil/WaitWhile,检查条件└─> 如果是 null,直接继续执行↓
[如果协程结束,则从队列中移除]
小结
Unity的协程依赖C#的迭代器,通过IEnumerator生成的状态机来分段执行代码。
Unity的调度器每一帧都会检查和恢复协程,基于Current的结果来决定是否继续执行。
协程不会并行执行,只是在主线程上“暂停-恢复”代码。
相关文章:
Unity协程机制详解
Unity的协程(Coroutine)是一种异步编程的机制,允许在多个帧之间分割代码的执行,而不阻塞主线程。与传统的多线程不同,Unity的协程在主线程中运行,并不会开启新的线程。 什么是协程? 协程是一种…...
2024年【高压电工】最新解析及高压电工考试总结
高压电工考试是电力行业从业人员必须通过的资格考试之一,它不仅检验了考生对高压电技术的掌握程度,还考验了考生在实际操作中的安全意识和应急处理能力。为了帮助广大考生更好地备考,本文整理了10道2024年高压电工考试的最新解析及总结试题&a…...
OELOVE 6.0城市列表模板
研究了好久OELOVE6.0源码,一直想将城市列表给单独整出来,做地区排名,但是PHP程序都是加密的,非常难搞,做二开都是要命的处理不了,在这里有一个简单方法可以处理城市列表,并且可以自定义TDK&…...

如何将你的 Ruby 应用程序从 OpenSearch 迁移到 Elasticsearch
作者:来自 Elastic Fernando Briano 将 Ruby 代码库从 OpenSearch 客户端迁移到 Elasticsearch 客户端的指南。 OpenSearch Ruby 客户端是从 7.x 版 Elasticsearch Ruby 客户端分叉而来的,因此代码库相对相似。这意味着当将 Ruby 代码库从 OpenSearch 迁…...

day1数据结构,关键字,内存空间存储与动态分区,释放
小练习 在堆区空间连续申请5个int类型大小空间,用来存放从终端输入的5个学生成绩,然后显示5个学生成绩,再将学生成绩升序排序,排序后,再次显示学生成绩。显示和排序分别用函数完成(两种排序方法࿰…...

1_linux系统网络性能如何优化——几种开源网络协议栈比较
之前合集《计算机网络从入门到放弃》第一阶段算是已经完成了。都是理论,没有实操,让“程序猿”很难受,操作性不如 Modbus发送的报文何时等到应答和 tcp通信测试报告单1——connect和send。开始是想看linux内核网络协议栈的源码,然…...

【问题记录】07 MAC电脑,使用FileZilla(SFTP)连接堡垒机不成功
项目场景: 使用MAC电脑,以子账号(非root)的形式登录,连接堡垒机CLB(传统型负载均衡),使用FileZilla(SFTP)进行FTP文件传输。 问题描述: MAC电脑…...

前端报错npm ERR cb() never called问题
环境使用node版本v14.21.3,npm版本6.14.18 1.问题描述 1.1使用npm install后报错 npm ERR! cb() never called!npm ERR! This is an error with npm itself. Please report this error at: npm ERR! ? ? <https://npm.community>npm ERR! A complete log…...

康谋方案 | 多源相机数据采集与算法集成测试方案
目录 一、相机组成 二、多源相机采集与测试方案 三、应用案例分享 四、结语 在智能化技术快速发展当下,图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进…...

Graspness 端到端抓取点估计 | 环境搭建 | 模型推理测试
在复杂场景中实现抓取检测,Graspness是一种端到端的方法; 输入点云数据,输出抓取角度、抓取深度、夹具宽度等信息。 开源地址:https://github.com/rhett-chen/graspness_implementation?tabreadme-ov-file 论文地址࿱…...
交换机是如何避免数据碰撞的(详细解释 + 示例)
交换机是如何避免数据碰撞的(详细解释 示例) 1. 独立冲突域 交换机的每个端口都形成一个独立的冲突域。这意味着通过交换机连接的每个设备都拥有自己的通信通道,互不干扰。 示例: 假设一个交换机有4个端口,分别连接…...
魅族手机刷官方系统
从魅族官网下载固件 https://flyme.cn/firmware.html 找到自己的型号,里面有历史版本、最新版,按照需求下载。 下载的是update.zip,改名就不能升级了 方法1 直接点击下载的update.zip包就可以升级。 方法2 将文件移动到文件管理的根目录&a…...
女人想要的,是那份懂她的情绪价值
女人想要的,是那份懂她的情绪价值 在情感的世界里,我们常常听到这样的声音:“我不需要你帮我解决问题,我只希望你能懂我。”这句话,简单却深刻,它揭示了女性在情感需求上的一个独特面向——她们渴望的&…...
[python SQLAlchemy数据库操作入门]-10.性能优化:提升 SQLAlchemy 在股票数据处理中的速度
哈喽,大家好,我是木头左! 当处理大量数据时,如股票数据,默认的ORM操作可能会显得效率低下。本文将探讨如何通过一些技巧和策略来优化SQLAlchemy ORM的性能,从而提升其在股票数据处理中的速度。 1. 选择合适的数据类型 在定义模型时,选择合适的数据类型对于性能至关重要…...

【网络取证篇】取证实战之PHP服务器镜像网站重构及绕密分析
【网络取证篇】取证实战之PHP服务器镜像网站重构及绕密分析 在裸聊敲诈、虚假理财诈骗案件类型中,犯罪分子为了能实现更低成本、更快部署应用的目的,其服务器架构多为常见的初始化网站架构,也称为站库同体服务器!也就是说网站应用…...
[python]使用 Pandas 处理 Excel 数据:分割与展开列操作
在数据处理的过程中,时常需要对 Excel 表格中的数据进行清洗与转换,下面介绍使用 Python 中的 Pandas 库对 Excel 文件中的数据进行操作,具体包括分割列、展开数据、清除空格以及格式转换等操作。 目标: 读取一个没有表头的 Exc…...
单片机的选择因素
在选择单片机型号时,需要根据具体的应用需求来选择合适的单片机。单片机(Microcontroller Unit, MCU)是一种将计算机的主要部分集成在一个芯片上的微型计算机,它通常包括处理器、存储器、输入/输出接口等。随着技术的发展…...
软件测试兼容性测试丨分布式测试与多设备管理
本文将从分布式测试的概念、重要性以及实施方法入手,紧接着探讨多设备管理的必要性和管理策略,最后分析其对软件测试行业的前景与影响。 分布式测试简介 什么是分布式测试? 分布式测试是指将测试任务分散到不同的计算机或者设备上进行&…...

Linux驱动开发(13):输入子系统–按键输入实验
计算机的输入设备繁多,有按键、鼠标、键盘、触摸屏、游戏手柄等等,Linux内核为了能够将所有的输入设备进行统一的管理, 设计了输入子系统。为上层应用提供了统一的抽象层,各个输入设备的驱动程序只需上报产生的输入事件即可。 下…...

微服务篇-微服务保护:使用 Sentinel 来实现请求限流、线程隔离、服务熔断和 Fallback 备用方案的使用
🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 微服务保护 1.1 请求限流方案 1.2 线程隔离方案 1.3 服务熔断方案 2.0 Sentinel 2.1 Sentinel 安装 2.2 微服务整合 3.0 Sentinel-请求限流 4.0 Sentinel-线程隔离…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...

解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...

02.运算符
目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&:逻辑与 ||:逻辑或 !:逻辑非 短路求值 位运算符 按位与&: 按位或 | 按位取反~ …...