协同程序

当调用一个函数时,在它返回之前,会一直运行到完成。这意味着该函数中的任何动作都必须在一帧内完成;函数调用不能包含过程动画或一段时间内的事件序列。例如有这样一个任务,逐渐降低一个对象的 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);
    }
}

协程可以把某些效果分散在一段时间内,也可以有效地优化性能。游戏中的许多任务需要定期执行,最明显的方式是将它们包含在 Update 函数中执行。但是 Update 函数通常每秒调用多次。当任务不需要如此频繁地重复时,你可以把它放入协程定期更新,而不是每桢都更新。一个例子是在敌人靠近玩家时触发警告。代码看起来可能像这样:

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

    return false;
}

如果有很多敌人,每桢都调用该函数可能会带来很大的开销。不过,你可以使用协程每秒调用该函数 10 次:

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

这将大大减少执行检测的次数,而且不会对游戏性产生任何显著影响。

results matching ""

    No results matching ""