Version: 2017.1
创建和销毁游戏对象
命名空间

协程

调用函数时,函数将运行到完成状态,然后返回。这实际上意味着在函数中发生的任何动作都必须在单帧更新内发生;函数调用不能用于包含程序性动画或随时间推移的一系列事件。例如,假设需要逐渐减少对象的 Alpha(不透明度)值,直至对象变得完全不可见。

void Fade() {
    for (float f = 1f; f >= 0; f -= 0.1f) {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
    }
}

就目前而言,Fade 函数不会产生期望的效果。为了使淡入淡出过程可见,必须通过一系列帧降低 Alpha 以显示正在渲染的中间值。但是,该函数将完全在单个帧更新中执行。这种情况下,永远不会看到中间值,对象会立即消失。

可以通过向 Update 函数添加代码(此代码逐帧执行淡入淡出)来处理此类情况。但是,使用协程来执行此类任务通常会更方便。

协程就像一个函数,能够暂停执行并将控制权返还给 Unity,然后在下一帧继续执行。在 C# 中,声明协程的方式如下:

IEnumerator Fade() {
    for (float f = 1f; f >= 0; f -= 0.1f) {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        yield return null;
    }
}

此协程本质上是一个用返回类型 IEnumerator 声明的函数,并在主体中的某个位置包含 yield return 语句。yield return 行是暂停执行并随后在下一帧恢复的点。要将协程设置为运行状态,必须使用 StartCoroutine 函数:

void Update() {
    if (Input.GetKeyDown("f")) {
        StartCoroutine("Fade");
    }
}

在 UnityScript 中,情况稍微简单一些。包含 yield 语句的任何函数都视为协程,并且无需显式声明 IEnumerator 返回类型:

function Fade() {
    for (var f = 1.0; f >= 0; f -= 0.1) {
        var c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        yield;
    }
}

此外,为了在 UnityScript 中启动协程,可直接调用协程,就像是普通函数一样:

function Update() {
    if (Input.GetKeyDown("f")) {
        Fade();
    }
}

您会注意到 Fade 函数中的循环计数器能够在协程的生命周期内保持正确值。实际上,在 yield 语句之间可以正确保留任何变量或参数。

默认情况下,协程将在执行 yield 后的帧上恢复,但也可以使用 WaitForSeconds 来引入时间延迟:

IEnumerator Fade() {
    for (float f = 1f; f >= 0; f -= 0.1f) {
        Color c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        yield return new WaitForSeconds(.1f);
    }
}

在 UnityScript 中:

function Fade() {
    for (var f = 1.0; f >= 0; f -= 0.1) {
        var c = renderer.material.color;
        c.a = f;
        renderer.material.color = c;
        yield WaitForSeconds(0.1);
    }
}

This can be used as a way to spread an effect over a period time but it is also a useful optimization. Many tasks in a game need to be carried out periodically and the most obvious way to do this is to include them in the Update function. However, this function will typically be called many times per second. When a task doesn’t need to be repeated quite so frequently, you can put it in a coroutine to get an update regularly but not every single frame. An example of this might be an alarm that warns the player if an enemy is nearby. The code might look something like this:

function ProximityCheck() {
    for (int i = 0; i < enemies.Length; i++) {
        if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
                return true;
        }
    }
    
    return false;
}

如果有很多敌人,那么每帧都调用此函数可能会带来很大开销。但是,可以使用协程,每十分之一秒调用一次:

IEnumerator DoCheck() {
    for(;;) {
        ProximityCheck;
        yield return new WaitForSeconds(.1f);
    }
}

这将大大减少所进行的检查次数,而不会对游戏运行过程产生任何明显影响。

注意:禁用 MonoBehaviour 时,不会停止协程,仅在明确销毁 MonoBehaviour 时才会停止协程。可以使用 MonoBehaviour.StopCoroutine 和 MonoBehaviour.StopAllCoroutines 来停止协程。销毁 MonoBehaviour 时,也会停止协程。

创建和销毁游戏对象
命名空间