【Unity基础详解】(5)Unity核心:Coroutines协程

目录

[1 协程的概念](#1 协程的概念)

2.协程原理

[3 协程的使用](#3 协程的使用)

[3.1 协程的语法](#3.1 协程的语法)

[3.2 协程的开启](#3.2 协程的开启)

[3.3 协程的结束](#3.3 协程的结束)

[4 yield 协程回复条件语句](#4 yield 协程回复条件语句)

[4.1 yield语句查询表](#4.1 yield语句查询表)

[4.2 进行计时器工作](#4.2 进行计时器工作)

[5 实现Unity场景异步加载](#5 实现Unity场景异步加载)


1 协程的概念

协程是Unity开发中至关重要的编程概念。在Unity游戏开发中,开发者通常(请注意是通常)需要避免直接使用多线程机制。那么如何处理需要在主任务之外执行的需求呢?Unity提供了协程这一解决方案。

为什么Unity开发中通常不考虑多线程?

这是因为Unity的核心功能(如获取游戏对象、组件和方法调用)都必须在主线程中执行。如果脱离主线程,Unity的诸多功能将无法正常运作,这大大降低了多线程的实际价值。

线程与协程的主要区别:

执行方式:

  • 协程:同一时刻只能执行一个协程
  • 线程:支持并发执行,多个线程可同时运行

内存机制:

  • 两者都共享堆内存
  • 各自维护独立的栈内存空间

核心差异:

  • 线程在多核CPU上可实现真正的并行执行
  • 协程始终是串行执行的

协程从字面上可以理解为协助主程序运行的机制。在主任务执行过程中,有时需要其他分支任务协同配合才能实现最终效果。

举个形象的例子:当主任务中需要执行一个高资源消耗的操作时,如果在一帧内完成这个操作会导致游戏卡顿。这时就可以使用协程,将这个操作分散到多帧中完成,从而保证主任务能流畅运行。

协程的工作原理是:在每帧结束时检查yield条件是否满足,若条件成立则继续执行yield return之后的代码。

2.协程原理

协程本质上并非线程,其执行过程始终运行在主线程内。核心实现原理是通过迭代器机制完成的,使用IEnumerator 接口定义迭代方法(注意不是IEnumerable):

二者关键区别:

  • IEnumerator:实现迭代功能的底层接口
  • IEnumerable:基于IEnumerator的封装接口,提供GetEnumerator()方法返回IEnumerator实例

其中yield关键字是迭代器的核心要素,也是实现协程功能的关键所在。通过yield可以:

  • 暂停协程执行
  • 保存当前状态
  • 记录下次启动的时间和执行位置

3 协程的使用

3.1 协程的语法

协程的实现需要在Unity中继承MonoBehaviour并使用C#的迭代器IEnumrator,格式如下:

IEnumrator 函数名(形参表) //最多只能有一个形参

{

yield return xxx; //恢复执行条件

//方法体

}

在IEnumerator类型的方法中写入需要执行的操作,遇到yield后会暂时挂起,等到yield return后的条件满足才继续执行yield语句后面的内容。

3.2 协程的开启

启动协程的方法,可以使用以下两种格式:

  • StartCoroutine(协程名());
  • StartCoroutine("协程名");

带单参数的协程调用:

  • StartCoroutine(协程名(参数));
  • StartCoroutine("协程名", 参数);

多参数协程的调用方式:

推荐使用: StartCoroutine(协程名(参数1, 参数2,...));

或采用如下方式:

cs 复制代码
void StartCoroutine() // 协程启动函数
{
    IEnumerator coroutine = Test(5, 6);
    StartCoroutine(coroutine);
}

public IEnumerator Test(int a, int b) // 协程实现
{
    yield return new WaitForEndOfFrame(); // 等待帧结束
    a = 2;
    b = 3;
}
 

**重要提示:**使用字符串形式("协程名")启动时,不能传递多个参数。

3.3 协程的结束

协程终止的两种形式:

  1. 自然终止:当协程的方法体执行完成后自动结束
  2. 强制终止:通过调用 StopCoroutine() 方法手动中止协程执行

终止协程的具体方法:

1.终止所有协程:

StopAllCoroutines();

2.通过实例对象终止指定协程:

cs 复制代码
Coroutine c;

void Start()
{
    c = StartCoroutine(CountSeconds());
}

void Update()
{
    if (Input.GetKeyDown(KeyCode.J))
    {
        StopCoroutine(c);
    }
}
 

3.通过协程名称终止指定协程:

cs 复制代码
StartCoroutine("CoroutineName");
StopCoroutine("CoroutineName");

// 可带参数形式
StartCoroutine("CoroutineName", parameter);
 

注意事项

  • 仅当协程是通过字符串名称启动时(StartCoroutine("协程名")),才能使用字符串名称终止(StopCoroutine("协程名")
  • 不支持通过直接调用协程方法来终止协程,例如 StopCoroutine(CoroutineName(parameter)) 这种形式是无效的

4 yield 协程回复条件语句

4.1 yield语句查询表

|------------------------------------------------|---------------------------------------------------------------------------|
| yield语句 | 功能 |
| yield return null; | 下一帧再执行后续代码 |
| yield return 0; | 下一帧再执行后续代码 |
| yield return 6;(任意数字) | 下一帧再执行后续代码 |
| yield break; | 直接结束该协程的后续操作 |
| yield return asyncOperation; | 等异步操作结束后再执行后续代码 |
| yield return StartCoroution("协程方法名"); | 调用执行其它协程后再执行后续代码,开启一个协程(嵌套协程) |
| yield return WWW(); | 等待WWW操作完成后再执行后续代码 |
| yield return new WaitForEndOfFrame(); | 等待帧结束,等待直到所有的摄像机和GUI被渲染完成后,在该帧显示在屏幕之前执行 |
| yield return new WaitForSeconds(0.3f); | 等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间会受到Time.timeScale的影响); |
| yield return new WaitForSecondsRealtime(0.3f); | 等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间不受到Time.timeScale的影响) |
| yield return WaitForFixedUpdate(); | 等待下一次FixedUpdate开始时再执行后续代码 等到下一个固定帧数更新 |
| yield return new WaitUntil(); | 将协同执行直到当输入的参数(或者委托)为true的时候 |
| yield return new WaitWhile(); | 将协同执行直到 当输入的参数(或者委托)为false的时候 |
| yield return GameObject; | 当游戏对象被获取到之后执行 |
| yield return new WaitForEndOfFrame(); | 等到所有相机画面被渲染完毕后更新 |

测试:

cs 复制代码
void Update()
{
    Debug.Log("001");
    StartCoroutine("Demo");
    Debug.Log("003");

}
private void LateUpdate()
{
     Debug.Log("005");
}

IEnumerator Demo()
{
    Debug.Log("002");

    yield return 0;
    Debug.Log("004");
}

通过分析可以明确:虽然协程在Update中启动,但yield return null之后的代码会在下一帧运行。这些代码会在当前帧的Update执行完毕后才会执行,但会在LateUpdate开始之前完成。

4.2 进行计时器工作

如果我们需要计时器有很多其他更好用的方式,但是你可以了解是存在这样的操作的,要实现这样的效果,需要通过yield return new WaitForSeconds()的延时执行的功能。

IEnumerator Test()

{

Debug.Log("开始");

yield return new WaitForSeconds(3);

Debug.Log("输出开始后三秒后执行我");

}

5 实现Unity场景异步加载

玩游戏最讨厌的就是游戏加载界面了。游戏加载界面虽然让玩家等待,但在开发过程中却是必要的存在。它能确保所有资源完全加载后再进入游戏,从而避免卡顿问题。

具体实现步骤可以看博主此篇文章:

【Unity小帮手】协程实现异步加载场景

相关推荐
野奔在山外的猫2 小时前
【解决】解决方案内存在对应命名空间,但程序引用显示无该命名空间问题
unity
野奔在山外的猫5 小时前
【案例】程序化脚本生成
unity
xiaotao1317 小时前
unity hub在ubuntu 22.0.4上启动卡住
ubuntu·unity·游戏引擎
小句9 小时前
通过图表和详细流程解释XXL-JOB中任务从创建到执行的完整过程
unity·游戏引擎
!chen1 天前
Unity颜色曲线ColorCurves
unity·游戏引擎
B0URNE1 天前
【Unity基础详解】(4)Unity核心类:MonoBehaviour
unity·游戏引擎
AA陈超1 天前
虚幻引擎5 GAS开发俯视角RPG游戏 P06-29 属性信息委托
c++·游戏·ue5·游戏引擎·虚幻
AA陈超2 天前
虚幻引擎5 GAS开发俯视角RPG游戏 P06-31 映射标签到属性
c++·游戏·ue5·游戏引擎·虚幻
gshh__2 天前
SuperMap Hi-Fi 3D SDK for Unreal 使用蓝图接口加载多源数据
ue5·游戏引擎·supermap