协程的定义

协程就是一种特殊的函数,它可以主动的请求暂停自身并提交一个唤醒条件,Unity会在唤醒条件满足的时候去重新唤醒协程。

协程是单线程的

开始一个协程时,执行被调用方法的起始处开始;然后,接下来,每次协程被调用,从协程返回的位置接着执行

开启协程的三种方式

我们通过方法 StartCoroutine()开启协程,通过 StopCoroutine()停止协程。

注意的是:开启协程的那个方法的返回值必须是IEnumerator类型的,且那个方法必须是 yeild return 来返回

using System.Collections;
using UnityEngine;
public class StudyCor : MonoBehaviour
{
    IEnumerator enumerator;
    private void Start()
    {
        // StartCoroutine(CountI()); // 方式一:开启协程
        // enumerator = CountI();
        // StartCoroutine(enumerator); // 方式二:开启协程
        StartCoroutine("CountI"); // 方式三:开启协程
    }

    IEnumerator CountI()
    {
        for(int i = 0; i < 10; i++)
        {
            Debug.Log("i = " + i);
            // yield的功能是暂停或等待的意思
            yield return new WaitForSeconds(1);
        }
    }

    private void Update()
    {
        // 如果按下空格,停止协程
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // StopCoroutine(enumerator); // 停止协程:方式二
            StopCoroutine("CountI"); // 停止协程;方式三
        }
    }
}

协程开启后对失活、销毁的影响

在一个脚本A上开启协程,这个脚本挂载到物体B上

  • 将脚本A和物体B销毁,则协程不会执行了

  • 将物体B失活,则协程不会执行了

  • 将脚本A失活,则协程会执行

unity对yeild return所定义的规则

  • yield return 数字;

等待一帧执行

  • yield return null;

等待一帧后执行

  • yield return new WaitForSeconds(秒);

等待指定秒后执行

  • yield return new WaitForFixedUpdate();

下一个固定物理帧执行,在FixedUpdate和碰撞检测相关函数后执行

  • yield return new WaitForEndOfFrame();

等待摄像机和GUI渲染完成后执行,LateUpdate之后

  • yield break;

跳出协程

协程执行原理

在Unity运行时,调用协程就是开启了一个IEnumerator(迭代器),协程开始执行,在执行到yield return之前和其他的正常的程序没有差别,但是当遇到yield return之后会立刻返回,并将该函数暂时挂起。在下一帧遇到FixedUpdate或者Update之后判断yield return 后边的条件是否满足,如果满足向下执行。

注:WaitForSends()受Time.timeScale影响,当Time.timeScale = 0f时,yieldreturn new WaitForSecond(X)将不会满足。

以上面的代码为例子,根据unity生命周期理解执行原理。

协程的特殊用法

1. 在Start里开启协程

其实理解了上面的协程原理,就可以理解下面的代码。

Start协程中开启了CountI的协程,但是CountI里面的yield并不影响Start的执行。

所以说下面的代码会同时输出

using System.Collections;
using UnityEngine;
public class StudyCor : MonoBehaviour
{
    IEnumerator enumerator;
    // 在Start里也可以开启协程,但是返回值必须是IEnumerator
    IEnumerator Start()
    {
        StartCoroutine("CountI"); // 方式三:开启协程
        for(int i = 0; i < 10; i++)
        {
            Debug.Log("Start i = " + i);
            yield return new WaitForSeconds(1);
        }
    }
    IEnumerator CountI()
    {
        for(int i = 0; i < 10; i++)
        {
            Debug.Log("CountI i = " + i);
            yield return new WaitForSeconds(1);
        }
    }
}

2. yield可以等待任意一个返回值为IEnumerator的函数

我们改一下上面的代码

using System.Collections;
using UnityEngine;
public class StudyCor2 : MonoBehaviour
{
    IEnumerator Start()
    {
        yield return CountI(); // 让yield 等待
        for (int i = 0; i < 10; i++)
        {
            Debug.Log("Start i = " + i);
            yield return new WaitForSeconds(1);
        }
    }

    IEnumerator CountI()
    {
        for(int i = 0; i < 10; i++)
        {
            Debug.Log("CountI i = " + i);
            yield return new WaitForSeconds(1);
        }
    }
}

如下图所示,它会先输出CountI 里面的函数,等执行完成后,在输出Start里面函数