【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小帮手】协程实现异步加载场景

相关推荐
GLDbalala2 小时前
Unity基于自定义管线实现经典经验光照模型
unity·游戏引擎
心疼你的一切5 小时前
Unity异步编程神器:Unitask库深度解析(功能+实战案例+API全指南)
深度学习·unity·c#·游戏引擎·unitask
呆呆敲代码的小Y7 小时前
【Unity 实用工具篇】 | Book Page Curl 快速实现翻书效果
游戏·unity·游戏引擎·u3d·免费游戏·翻书插件
AC梦19 小时前
unity中如何将UI上的字高清显示
ui·unity
小贺儿开发1 天前
Unity3D 智慧城市管理平台
数据库·人工智能·unity·智慧城市·数据可视化
June bug2 天前
【领域知识】休闲游戏一次发版全流程:Google Play + Apple App Store
unity
星夜泊客2 天前
C# 基础:为什么类可以在静态方法中创建自己的实例?
开发语言·经验分享·笔记·unity·c#·游戏引擎
dzj20212 天前
PointerEnter、PointerExit、PointerDown、PointerUp——鼠标点击物体,则开始旋转,鼠标离开或者松开物体,则停止旋转
unity·pointerdown·pointerup
心前阳光2 天前
Unity 模拟父子关系
android·unity·游戏引擎
在路上看风景2 天前
26. Mipmap
unity