协同程序
当调用一个函数时,在它返回之前,会一直运行到完成。这意味着该函数中的任何动作都必须在一帧内完成;函数调用不能包含过程动画或一段时间内的事件序列。例如有这样一个任务,逐渐降低一个对象的 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);
}
}
这将大大减少执行检测的次数,而且不会对游戏性产生任何显著影响。