协同程序

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

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