• 协同程序

    协同程序

    当调用一个函数时,在它返回之前,会一直运行到完成。这意味着该函数中的任何动作都必须在一帧内完成;函数调用不能包含过程动画或一段时间内的事件序列。例如有这样一个任务,逐渐降低一个对象的 alpha(不透明度)值,直到它完全不可见。

    1. void Fade() {
    2. for (float f = 1f; f >= 0; f -= 0.1f) {
    3. Color c = renderer.material.color;
    4. c.a = f;
    5. renderer.material.color = c;
    6. }
    7. }

    实际情况是,函数 Fade 不会实现你期望的效果。为了使渐变过程可见,alpha 必须随着桢序列降低,以渲染显示中间值。但是,该函数将在一帧内完整地执行。你将永远不会看到中间值,对象会立即消失。

    可以把代码添加到 Update 函数中,逐桢地执行淡出,来处理这种情况。不过,更方便的方式是使用协程(协同程序)执行这种任务。

    协程就像一个函数,它能够暂停执行并将控制权返回给 Unity,但是在下一桢时,可以在暂停的位置继续执行。在 C# 中,可以像这样声明协程:

    1. IEnumerator Fade() {
    2. for (float f = 1f; f >= 0; f -= 0.1f) {
    3. Color c = renderer.material.color;
    4. c.a = f;
    5. renderer.material.color = c;
    6. yield return null;
    7. }
    8. }

    协程本质上是一个返回类型被声明为 IEnumerator 的函数,并且在函数体的某处包含 yield return 语句。执行过程在 yield return 行暂停,并在下一桢恢复执行。要让协程运行起来,需要使用 StartCoroutine 函数:

    1. void Update() {
    2. if (Input.GetKeyDown("f")) {
    3. StartCoroutine("Fade");
    4. }
    5. }

    在 UnityScript 中,事情稍微简单一些。任何含有 yield 语句的函数都被认为是一个协程,不需要显示声明返回类型 IEnumerator:

    1. function Fade() {
    2. for (var f = 1.0; f >= 0; f -= 0.1) {
    3. var c = renderer.material.color;
    4. c.a = f;
    5. renderer.material.color = c;
    6. yield;
    7. }
    8. }

    此外,在 UnityScript 中,可以通过直接调用协程来启动它,就像它是一个普通的函数一样:

    1. function Update() {
    2. if (Input.GetKeyDown("f")) {
    3. Fade();
    4. }
    5. }

    你将会注意到,在协程的生命周期内,Fade 函数中的循环计数器一直保持正确的值。实际上,yield 之间的任何变量或属性都将正确地保留。

    默认情况下,协程在 yield 之后的桢中恢复,不过也可以使用 WaitForSeconds 延迟恢复:

    1. IEnumerator Fade() {
    2. for (float f = 1f; f >= 0; f -= 0.1f) {
    3. Color c = renderer.material.color;
    4. c.a = f;
    5. renderer.material.color = c;
    6. yield return new WaitForSeconds(.1f);
    7. }
    8. }

    在 UnityScript 中:

    1. function Fade() {
    2. for (var f = 1.0; f >= 0; f -= 0.1) {
    3. var c = renderer.material.color;
    4. c.a = f;
    5. renderer.material.color = c;
    6. yield WaitForSeconds(0.1);
    7. }
    8. }

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

    1. function ProximityCheck() {
    2. for (int i = 0; i < enemies.Length; i++) {
    3. if (Vector3.Distance(transform.position, enemies[i].transform.position) < dangerDistance) {
    4. return true;
    5. }
    6. }
    7. return false;
    8. }

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

    1. IEnumerator DoCheck() {
    2. for(;;) {
    3. ProximityCheck;
    4. yield return new WaitForSeconds(.1f);
    5. }
    6. }

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