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) float 2.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的委托) () => playerHealth > 50 每帧检查条件,条件为true时继续 安检闸机:必须满足条件才放行
yield return new WaitWhile(条件) Func(返回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) float 3f 等待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(协程) IEnumerator StartCoroutine(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 AsyncOperation AsyncOperation对象 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.9
            UpdateLoadingUI(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 有本质区别:

对比项 IEnumerator Coroutine
归属 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语句分割执行步骤,避免真死循环。

你可能感兴趣的:(unity,学习,游戏引擎,c#)