当前位置: 首页 > news >正文

Unity基础学习(六)Mono中的重要内容(2)协同程序

目录

一、什么是协同程序

二、协程和线程的区别

三、协程怎么使用

四、yield return的不同含义(以交通信号灯示例)

1. 基础等待指令

2. 条件等待指令

3. 时间控制(抗干扰版)

4. 协程流程控制

5. 异步操作等待

6. 自定义等待(高级)

五、协程受对象和组件失活销毁的影响

Coroutine 的本质

六、注意事项


一、什么是协同程序

协程就像你边煮泡面边看剧的机智操作:

当你需要同时做多件事时,协程允许你把一个长任务拆分成多个小步骤
每次执行一步后可以暂停,让主线程继续处理其他事情(如画面渲染)
下次再从暂停的位置继续执行,像自动书签一样记录执行进度

二、协程和线程的区别

协程线程
单车道上的交替通行多条并行的独立车道
在主线程"夹缝"中执行(Update之间)完全独立的新车道
可以直接操作Unity对象不能直接操作Unity组件
适合处理需要分帧的轻量任务适合复杂计算/网络通信

三、协程怎么使用

1. 基本语法框架:核心原理:协程本质是一个分步执行器,IEnumerator(迭代器接口)就像给代码装上了自动书签

// 函数返回类型必须是 IEnumerator
IEnumerator 协程函数名() {// 步骤1代码yield return 暂停条件; // 在此处插入书签// 步骤2代码(恢复后执行)
}

yield return:相当于对Unity说:"先执行到这里,满足条件再翻页继续"

2.举例示意:

步骤① 声明协程函数

IEnumerator AttackAction()
{// 阶段一:攻击前摇(比如举刀动作)PlayAnimation("SwordRaise");yield return new WaitForSeconds(0.3f); // 等待0.3秒// 阶段二:实际攻击判定CheckEnemyInRange(); // 检测敌人是否在攻击范围DealDamage();        // 造成伤害PlaySound("SwordHit");// 阶段三:攻击后摇(收刀动作)PlayAnimation("SwordReturn");yield return new WaitForSeconds(0.2f); isAttacking = false; // 标记攻击结束
}

步骤② 启动协程

// 在角色攻击时调用
void StartAttack()
{if(!isAttacking) {isAttacking = true;StartCoroutine(AttackAction()); // 点火启动协程}
}

注意:不可以在Update中开启协程 

四、关于不同的yield return 的不同含义

四、yield return的不同含义(以交通信号灯示例)

指令等效信号执行时机现实生活场景
yield return null绿灯下一帧立即继续(在Update之后)每天早高峰的常规放行
yield return new WaitForSeconds(2)红灯倒计时2秒等待指定真实时间后继续(受 Time.timeScale 影响)路口红灯倒计时
yield return new WaitForFixedUpdate()物理黄灯等待物理引擎更新后继续(在 FixedUpdate 之后)货车专用通道放行(物理相关操作)
yield return new WaitForEndOfFrame()终极大黄灯当前帧完全渲染完成后继续(画面渲染完毕时)末班地铁后的清场检查
yield break道路封闭立即终止协程突发封路,所有车辆强制调头
yield return new WaitUntil(条件)安检闸机每帧检查条件,直到条件为true时继续必须出示健康码才能通行
yield return new WaitWhile(条件)临时管制每帧检查条件,只要条件为true就卡住前方有事故,持续堵车时禁止通行
yield return new WaitForSecondsRealtime(秒)防干扰红灯等待指定真实时间(不受 Time.timeScale 影响)现实世界钟表倒计时(游戏暂停时依然有效)
yield return StartCoroutine(其他协程)子协程专用道等待另一个协程完全执行完毕后再继续先让VIP车队通过,再放行普通车辆
yield return new WWW(url)物流等待(旧版)等待网络资源加载完成(已过时,Unity推荐用 UnityWebRequest等快递到达才能拆箱使用
yield return new AsyncOperation异步收费站等待异步操作完成(如场景加载)排队进高速公路收费站
yield return CustomYieldInstruction自定义信号灯继承 CustomYieldInstruction 类自定义条件(需实现 keepWaiting 属性)交警手动指挥交通

来,咱们逐个来解释说明!

1. 基础等待指令
指令参数类型参数示例返回效果说明
yield return null-暂停协程,在下一帧的Update后继续执行每天早高峰按固定频率放行
yield return new WaitForSeconds(n)float2.5f等待n秒(受游戏时间缩放影响)后继续红灯倒计时n
yield return new WaitForFixedUpdate()-等待物理引擎更新(FixedUpdate周期)后继续物理货车专用通道放行
yield return new WaitForEndOfFrame()-等待当前帧完全渲染后继续末班车后的安全检查
IEnumerator FlashEffect() {while(true) {sprite.color = Color.red;yield return null; // 等一帧sprite.color = Color.white;yield return null; // 再等一帧}
}

就是执行完当前操作 然后等一会 然后往下继续执行,这里只是简单的用等一帧进行实例;其余的你看API名字应该都能理解。

2. 条件等待指令
指令参数类型参数示例返回效果说明
yield return new WaitUntil(条件)Func<bool>(返回bool的委托)() => playerHealth > 50每帧检查条件,条件为true时继续安检闸机:必须满足条件才放行
yield return new WaitWhile(条件)Func<bool>(返回bool的委托)() => isGamePaused每帧检查条件,条件为false时继续临时管制:条件解除后恢复通行

示例:

// Lambda表达式形式(最常用)
yield return new WaitUntil(() => player.isAlive); // 方法名形式(需定义返回bool的方法)
bool CheckPlayerReady() => player.isReady;
yield return new WaitUntil(CheckPlayerReady);
3. 时间控制(抗干扰版)
指令参数类型参数示例返回效果说明
yield return new WaitForSecondsRealtime(n)float3f等待n秒(不受Time.timeScale影响)后继续例如现实世界钟表倒计时

例如:

// 参数示例:等待3秒真实时间(即使游戏暂停也计时)
IEnumerator ShowPauseTip() 
{// 游戏暂停时弹出提示ShowPopup("游戏已暂停,3秒后显示帮助信息");// 等待3秒真实时间(不受Time.timeScale影响)yield return new WaitForSecondsRealtime(3f);// 3秒后显示帮助(比如玩家按暂停时依然会倒计时)ShowHelpMessage("按ESC键恢复游戏");
}
4. 协程流程控制
指令参数类型参数示例返回效果说明
yield break-立即终止协程,后续代码不执行突发封路,强制终止
yield return StartCoroutine(协程)IEnumeratorStartCoroutine(PlayAnimation())等待传入的协程完全执行完毕后继续VIP车队优先通行
//指令1:yield break 无参数,直接终止
IEnumerator PatrolEnemy() 
{while(true) {// 敌人巡逻逻辑MoveToNextWaypoint();yield return new WaitForSeconds(2f);// 如果敌人死亡,立即终止巡逻if(isDead) {yield break; // 直接跳出协程}}
}
//指令2:yield return StartCoroutine(协程)
//属于是无限套娃啦
// 子协程:播放动画
IEnumerator PlayOpeningAnimation() 
{PlayAnimation("CameraZoomIn");yield return new WaitForSeconds(2f);PlayAnimation("LogoFadeIn");
}// 主协程:等待子协程完成
IEnumerator GameStartSequence() 
{// 先等片头动画完全播放完yield return StartCoroutine(PlayOpeningAnimation());// 再触发后续事件StartGameMusic();SpawnPlayer();
}
5. 异步操作等待
指令参数类型参数示例返回效果说明
yield return new AsyncOperationAsyncOperation对象SceneManager.LoadSceneAsync("Level2")等待异步操作(如场景加载)完成排队进入高速公路收费站
yield return new WWW(url)(已过时)string(URL地址)"http://example.com/data"等待网络资源加载完成(Unity 2018后推荐使用UnityWebRequest旧版物流等待

指令1:yield return new AsyncOperation(场景加载)

using UnityEngine;
using UnityEngine.SceneManagement;public class SceneLoader : MonoBehaviour 
{IEnumerator LoadLevelAsync() {// 参数示例:异步加载场景"Level2"AsyncOperation asyncOp = SceneManager.LoadSceneAsync("Level2");asyncOp.allowSceneActivation = false; // 禁止自动跳转// 更新加载进度条while (!asyncOp.isDone) {float progress = Mathf.Clamp01(asyncOp.progress / 0.9f); // Unity加载进度最大到0.9UpdateLoadingUI(progress); // 更新UI进度条yield return null; // 每帧更新一次}// 手动激活场景(比如点击"继续"按钮后)asyncOp.allowSceneActivation = true;}void UpdateLoadingUI(float progress) {Debug.Log($"当前加载进度: {progress * 100}%");}
}

 指令2:yield return new WWW(url)(已过时 → 替代方案)

using UnityEngine;
using UnityEngine.Networking;public class WebLoader : MonoBehaviour 
{// 旧版(已过时)IEnumerator OldDownload() {WWW www = new WWW("http://example.com/data.json");yield return www; // 等待下载完成Debug.Log(www.text);}// 新版替代方案(UnityWebRequest)IEnumerator NewDownload() {using (UnityWebRequest www = UnityWebRequest.Get("http://example.com/data.json")) {yield return www.SendWebRequest(); // 等待下载完成if (www.result == UnityWebRequest.Result.Success) {Debug.Log(www.downloadHandler.text);}else {Debug.LogError($"下载失败: {www.error}");}}}
}
6. 自定义等待(高级)
指令参数类型参数示例返回效果比喻说明
yield return CustomYieldInstruction继承CustomYieldInstruction的子类new WaitAdFinish()自定义条件,通过实现keepWaiting属性控制等待交警手动指挥交通

示例:(广告播放等待)

主要是必须继承一个类就可以使用了

using UnityEngine;
using UnityEngine.Advertisements; // 需安装广告包// 自定义广告等待类
public class WaitAdFinish : UnityEngine.CustomYieldInstruction 
{public override bool keepWaiting {get {// 当广告未播放完毕时,协程持续等待return !Advertisement.isFinished;}}
}public class AdManager : MonoBehaviour 
{IEnumerator ShowRewardAd() {if (Advertisement.IsReady("rewardedVideo")) {Advertisement.Show("rewardedVideo");// 使用自定义等待类yield return new WaitAdFinish();// 广告播放完毕后发放奖励GiveReward(100);}}void GiveReward(int coins) {Debug.Log($"获得 {coins} 金币!");}
}

请务必注意,上述都只是讲解协程是怎么写的,并没有讲怎么使用,使用就是要通过 StartCoroutine来开启。

五、协程受对象和组件失活销毁的影响

操作协程状态比喻说明
物体销毁Destroy(gameObject) → 所有协程立即终止整栋楼被爆破,所有供电中断
物体失活gameObject.SetActive(false) → 所有协程暂停整栋楼拉总闸断电
组件失活enabled = false → 协程继续执行单个房间关灯,但其他设备(协程)仍运行
脚本销毁Destroy(this) → 协程立即终止拆除某个房间的电路,关联设备停电
停止协程StopCoroutine / StopAllCoroutines → 指定或全部协程终止手动关闭某个或所有电器

补充知识点: Unity 中 Coroutine 类的解析

Coroutine 的本质

   Coroutine 是 Unity 引擎对 协程逻辑的封装对象,本质上是一个由引擎内部管理的 协程句柄。它与 C# 的 IEnumerator 有本质区别:

对比项IEnumeratorCoroutine
归属C# 语言原生接口Unity 引擎封装类
作用定义协程逻辑(通过 yield 分步)管理协程的生命周期(启动/停止/状态追踪)
内存管理由 GC 自动回收需通过 StopCoroutine 或销毁对象释放
访问方式直接通过迭代器操作通过 StartCoroutine/StopCoroutine

主要功能基本都在IEnumerator中实现了,这里主要是讲解上面API有些会具备返回值,例如:

// 启动协程并获取 Coroutine 对象
Coroutine myCoroutine = StartCoroutine(MyCoroutine());

这个返回值就是用在关闭协程使用,或者其他地方调用。例如

// 启动协程并获取 Coroutine 对象
Coroutine myCoroutine = StartCoroutine(MyCoroutine());

// 停止特定协程
StopCoroutine(myCoroutine);

// 停止所有协程
StopAllCoroutines();

六、注意事项

1、多线程使用限制
禁止在子线程中调用任何Unity API,需通过主线程队列传递操作指令。
必须手动终止线程,否则应用关闭后线程仍驻留内存,导致资源泄漏。
避免使用 Thread.Sleep 阻塞主线程,优先选择异步任务或协程计时。

2、协程启停规范
协程必须通过 StartCoroutine 启动,直接调用无效。
停止协程需明确调用 StopCoroutine(单个)或 StopAllCoroutines(全部)。
物体销毁时协程自动终止,物体失活时协程暂停,组件失活不影响协程运行。

3、Yield 使用要点
yield return null 表示下一帧继续,需配合循环实现每帧执行。
频繁创建 WaitForSeconds 会引发内存压力,建议提前缓存对象复用。
物理相关操作使用 WaitForFixedUpdate,渲染完成后操作使用 WaitForEndOfFrame。

4、生命周期管理
在 OnDestroy 中统一清理协程和线程,避免残留逻辑引发异常。
协程内访问对象前需检查 null,防止对象已销毁时产生空引用。

5、性能优化
大量协程需分帧处理,避免单帧卡顿(如循环内 yield return null)。
耗时计算交予子线程,结果返回主线程处理,维持帧率稳定。

6、异常处理
协程和线程逻辑需包裹 try-catch,防止未处理异常导致程序崩溃。
使用 yield break 可主动终止协程,类似普通函数的 return。

7、混合使用原则
协程管理主线程轻量任务(如UI动画),线程处理计算密集型任务(如网格生成)。
避免协程嵌套复杂线程操作,优先通过事件或回调解耦逻辑。

 常见问题:协程也是在主线程执行任务的吗?是的话,如果协程是死循环,那么会程序卡死吗?

回答:

        协程确实在主线程执行。Unity 的协程本质是通过迭代器实现的 分时分步任务调度,所有协程代码都在主线程运行,与 Update 等生命周期函数共享同一线程。

        协程在Unity中运行于主线程,本质是通过迭代器实现的任务分时分步调度。若协程包含yield的死循环(如while(true)中未使用yield return),将完全阻塞主线程,导致画面冻结、操作无响应,程序实质卡死;而yield的循环(如while(true) { yield return null; })则会在每帧执行一次循环体后让出主线程控制权,允许Unity正常处理渲染、输入等任务,虽逻辑上循环无限,但程序仍能保持流畅响应。因此,协程中所有循环必须搭配yield语句分割执行步骤,避免真死循环。

相关文章:

  • XXE(外部实体注入)
  • 我店模式系统开发打造本地生活生态商圈
  • 【深度学习-Day 15】告别“盲猜”:一文读懂深度学习损失函数
  • 2. Java 基础语法通关:变量、数据类型与运算符详解​
  • CST求解器
  • HarmonyOS 鸿蒙应用开发基础:父组件调用子组件方法的几种实现方案对比
  • Linux Docker下安装tomcat
  • 首次使用倍福工控机修改IP
  • Redis--SpringDataRedis详解
  • 基于 Free2AI 的企业知识库搭建全流程实战:从数据采集到智能问答
  • 笔记:将一个文件服务器上的文件(一个返回文件数据的url)作为另一个http接口的请求参数
  • DDoS攻击应对指南:提升网站安全性的有效策略
  • 【Django ORM】三万字了解Django ORM的基本概念和基本使用
  • 华为云Flexus+DeepSeek征文 | 基于ModelArts Studio和Cherry Studio快速构建午餐管家助手
  • CentOS7挂载hgfs文件夹(VMware 共享文件夹)及网卡的自启动。
  • 【分治】归并排序:递归版 非递归版
  • Python后端框架新星Robyn:性能与开发体验的双重革命
  • 01. Qt介绍及Qt开发环境搭建(2025.05最新官网下载方式)
  • mysql可重复读隔离级别下的快照读和当前读
  • 物理定律的数学结构基础及AI推理
  • win2003 wordpress/淘宝标题优化网站
  • 协会网站设计方案/网页设计用什么软件
  • 南宫做网站/百度收录规则
  • 自己做网站网站资源哪里来/营销推广方案设计
  • 和小男生做的网站/sem专员
  • 公司有必要建设网站吗/株洲百度seo