目录
[1 协程的概念](#1 协程的概念)
[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 协程的结束
协程终止的两种形式:
- 自然终止:当协程的方法体执行完成后自动结束
- 强制终止:通过调用 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场景异步加载
玩游戏最讨厌的就是游戏加载界面了。游戏加载界面虽然让玩家等待,但在开发过程中却是必要的存在。它能确保所有资源完全加载后再进入游戏,从而避免卡顿问题。
具体实现步骤可以看博主此篇文章: