难以理解延迟和协程

Trouble understanding Delays and Coroutines

void start()
StartCoroutine(Text());

IEnumerator Text()
{
    Debug.Log("Hello")
    yield return new WaitForSeconds(3)
    Debug.Log("ByeBye")
}

我理解这样做的基本概念,但我不明白什么意思,例如 yield return new WaitforSeconds(3) 和 StartCoroutine 是什么以及 IEnumerator 是什么。 谁能给我解释一下他们的意思吗?

当您调用函数时,它会在 returning 之前运行完成。这实际上意味着函数中发生的任何动作都必须在单个帧更新内发生;函数调用不能用于包含程序动画或随时间变化的事件序列。例如,考虑逐渐降低对象的 alpha(不透明度)值直到它变得完全不可见的任务。

void Fade() 
{
    for (float ft = 1f; ft >= 0; ft -= 0.1f) 
    {
        Color c = renderer.material.color;
        c.a = ft;
        renderer.material.color = c;
    }
}

就目前而言,淡入淡出功能不会产生您预期的效果。为了使褪色可见,必须在一系列帧上减少 alpha 以显示正在渲染的中间值。但是,该功能将在单个帧更新内完整执行。永远不会看到中间值,对象会立即消失。 可以通过将代码添加到以 frame-by-frame 为基础执行淡入淡出的 Update 函数来处理此类情况。但是,对于此类任务,使用协程通常更为方便。 协程就像一个函数,它能够暂停执行并 return 控制 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;
    }
}

它本质上是一个使用 return 类型的 IEnumerator 声明的函数,并且在主体的某处包含 yield return 语句。 yield return 空行是执行将暂停并在下一帧恢复的点。设置协程运行,需要使用StartCoroutine函数:

void Update()
{
    if (Input.GetKeyDown("f")) 
    {
        StartCoroutine("Fade");
    }
}

您会注意到 Fade 函数中的循环计数器在协程的生命周期内保持其正确的值。事实上,任何变量或参数都会在 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 函数中。但是,此函数通常每秒会被调用多次。当一个任务不需要如此频繁地重复时,您可以将它放在协程中以定期获取更新,但不是每一帧。这方面的一个例子可能是当附近有敌人时警告玩家的警报。代码可能如下所示:

bool 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) 禁用它所附加的 GameObject 时,协程也会停止。调用 Destroy(example)(其中 example 是一个 MonoBehaviour 实例)会立即触发 OnDisable 并处理协程,从而有效地停止它。最后,在帧结束时调用 OnDestroy。 通过在 MonoBehaviour 实例上将 enabled 设置为 false 来禁用 MonoBehaviour 时,协程不会停止。

参考:https://docs.unity3d.com/Manual/Coroutines.html

Unity (ab) 使用枚举器构建 C# 协程,因为 async / await 不存在。当你写作时;

IEnumerator Text()
{
    Debug.Log("Hello")
    yield return new WaitForSeconds(3)
    Debug.Log("ByeBye")
}

编译器将其转换为类似的东西;

IEnumerator Text() => new StateMachine();

public class StateMachine : IEnumerable{
    private int state = 0;
    // plus any local variables moved to fields.
    StateMachine(){}
    public object Current { get; set; }
    public bool MoveNext(){
        switch(state){
            case 0:
            Debug.Log("Hello");
            Current = new WaitForSeconds(3);
            state = 1;
            return true;

            case 1:
            Debug.Log("ByeBye");
            return false;
        }
    }
}

由于您的函数状态现在存储在对象的字段中,您的方法可以在完成之前暂停。然后 Unity 将查看您产生的对象以决定何时调用 MoveNext().

现在 C# 具有异步方法,这也会导致您的方法被转换为状态机。新版本的 unity 可能会支持它们,例如;

async Task Text()
{
    Debug.Log("Hello")
    await Something.WaitForSeconds(3)
    Debug.Log("ByeBye")
}

但他们仍然必须支持构建 CoRoutines 的旧方法。