协程的使用,看起来非常有多线程的感觉,至少在学习初期我们会用类似多线程的思想去理解它,但是实际上它并不是多线程,那么到底是怎么实现的了?
首先,我们看一个简单的使用例子:

public Coroutine StartCoroutine(IEnumerator routine)
StartCoroutine接受到的是一个IEnumerator ,下方我们的定义的协程方式也确实返回值为IEnumerator ,所以我们先不管Unity怎么实现协程机制的,我们可以先看这个“IEnumerator ”,这个并不是Unity提供的类型,而是System.Collections.IEnumerator 。
很明显这是个接口,并且字面意思枚举器或迭代器的意思,看下图可以看出这个接口的的目的是一个一个枚举出来,这应该会让你想到Dictionary的foreach

打开字典后,确实可以看到这个接口,所以确实他们是类似的技术

我们自己创建一个类,继承这个接口,来看看如何使用,通过下图我想你应该明白IEnumerator这个接口的意义了,就是可以每次获取一个值出来使用

PS:IEnumerator 这个接口其实对应的是“枚举object类型”,如果使用泛型,就可以约束返回结果
搞清楚了IEnumerator的意义,我们在来看一下协程函数中我们使用的yield return是什么作用
yield是C#的一个关键字,也是一个语法糖,背后的原理会生成一个类,肯定也是一个枚举器,而且不同于return,yield可以出现多次。
yield实际上就是返回一次结果,因为我们要一次一次枚举一个值出来,所以多个yield其实是个状态模式,第一个yield是状态1,第二个yield是状态2,每次访问时会基于状态知道当前应该执行哪一个yield,取得哪一个值。
我们现在来手动调用枚举函数来执行看看

看看控制台的输出结果

相信看到这里,你已经大概猜到Unity如何实现协程的了。
首先看一个使用较多的“暂停XX秒” yield return new WaitForSeconds(0.5f);
其实本质上就是每一帧都来检查一次是否满足了计时,如果满足就说明yield 的状态满足了,进入下一个状态
至于yield return null,为什么会有暂停一帧的效果,因为相当于什么也没做,但是检查需要在下一帧进行
自定义协程
最后我们来看看如何自定义一个协程,我们需要继承 CustomYieldInstruction ,下图会直接提供一个小案例,这里的功能是“等待玩家点击X次鼠标”
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Test : MonoBehaviour
{
void Start()
{
StartCoroutine(Do());
}
IEnumerator Do()
{
yield return new WaitForMouseButtonDown(10);
print("DONE");
}
class WaitForMouseButtonDown : CustomYieldInstruction
{
private int curr; // 当前的鼠标点击次数
private int max; // 鼠标点击次数的上限
public WaitForMouseButtonDown(int max)
{
curr = 0;
this.max = max;
}
public override bool keepWaiting => CheckKeepWaiting(); // 每一帧都会来访问一次
// 检查状态
private bool CheckKeepWaiting()
{
if (curr >= max) // 说明满足了条件
{
return false; // 不需要保持了等待状态了
}
if (Input.GetMouseButtonDown(0))
{
curr++; // 玩家点击了,所以自增curr
}
return true; // 保持等待状态
}
}
}
当然你也可以直接继承IEnumerator来实现自定义协程的目的

本文详细探讨了Unity中协程的工作原理,通过IEnumerator接口和yield关键字的深入剖析,揭示了协程并非多线程,而是利用状态模式逐帧更新。介绍了暂停、自定义协程等实用技巧,并通过实例演示了其核心机制。
258

被折叠的 条评论
为什么被折叠?



