调用函数时,函数将运行到完成状态,然后返回。这实际上意味着在函数中发生的任何动作都必须在单帧更新内发生;函数调用不能用于包含程序性动画或随时间推移的一系列事件。例如,假设需要逐渐减少对象的 Alpha(不透明度)值,直至对象变得完全不可见。
void Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
}
}
就目前而言,Fade 函数不会产生期望的效果。为了使淡入淡出过程可见,必须通过一系列帧降低 Alpha 以显示正在渲染的中间值。但是,该函数将完全在单个帧更新中执行。这种情况下,永远不会看到中间值,对象会立即消失。
可以通过向 Update 函数添加代码(此代码逐帧执行淡入淡出)来处理此类情况。但是,使用协程来执行此类任务通常会更方便。
协程就像一个函数,能够暂停执行并将控制权返还给 Unity,然后在下一帧继续执行。在 C# 中,声明协程的方式如下:
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return null;
}
}
此协程本质上是一个用返回类型 IEnumerator 声明的函数,并在主体中的某个位置包含 yield return 语句。yield return null 行是暂停执行并随后在下一帧恢复的点。要将协程设置为运行状态,必须使用 StartCoroutine 函数:
void Update()
{
if (Input.GetKeyDown("f"))
{
StartCoroutine("Fade");
}
}
您会注意到 Fade 函数中的循环计数器能够在协程的生命周期内保持正确值。实际上,在 yield 语句之间可以正确保留任何变量或参数。
默认情况下,协程将在执行 yield 后的帧上恢复,但也可以使用 WaitForSeconds 来引入时间延迟:
IEnumerator Fade()
{
for (float ft = 1f; ft >= 0; ft -= 0.1f)
{
Color c = renderer.material.color;
c.a = ft;
renderer.material.color = c;
yield return new WaitForSeconds(.1f);
}
}
可通过这样的协程在一段时间内传播效果,但也可将其作为一种有用的优化。游戏中的许多任务需要定期执行,最容易想到的方法是将任务包含在 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;
}
如果有很多敌人,那么每帧都调用此函数可能会带来很大开销。但是,可以使用协程,每十分之一秒调用一次:
IEnumerator DoCheck()
{
for(;;)
{
ProximityCheck();
yield return new WaitForSeconds(.1f);
}
}
这将大大减少所进行的检查次数,而不会对游戏运行过程产生任何明显影响。
注意:可以使用 StopCoroutine 和 StopAllCoroutines 来停止协程。
当用 SetActive(false) 禁用某个协程所附加到的游戏对象时,该协程也将停止。调用 Destroy(example)
(其中 example
是一个 MonoBehaviour 实例)会立即触发 OnDisable
,并会处理协程,从而有效地停止协程。最后,在帧的末尾调用 OnDestroy
。
通过在 MonoBehaviour 实例上将 enabled 设置为 false 来禁用 MonoBehaviour 时,协程不会停止。