无法理解延误和协程

问题描述

void start()
StartCoroutine(Text());

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

我了解这样做的基本概念,但是我没有得到什么意思,例如yield return new WaitforSeconds(3)以及StartCoroutine和IEnumerator是什么。 谁能告诉我他们的意思?

解决方法

调用函数时,它会运行到完成再返回。这实际上意味着函数中发生的任何动作都必须在单个帧更新中发生;函数调用不能用来包含程序动画或一段时间内发生的一系列事件。例如,考虑一下逐渐减小对象的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值以显示正在渲染的中间值。但是,该功能将在单帧更新中完整执行。中间值将永远不会被看到,对象将立即消失。 通过将代码添加到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语句包含在主体中的某个位置。收益率返回空行是执行将暂停并在下一帧重新开始的点。要设置协程运行,您需要使用StartCoroutine函数:

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

您会注意到,淡入淡出功能中的循环计数器在协程的整个生命周期内都保持其正确的值。实际上,任何变量或参数将在产量之间正确保留。 默认情况下,协程在产生后会在帧上恢复,但是也可以使用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);
    }
}

这可以用作在一段时间内传播效果的一种方法,但这也是有用的优化。游戏中的许多任务需要定期执行,而最明显的方法是将它们包含在“更新”功能中。但是,通常每秒会多次调用此函数。当不需要重复执行某项任务时,您可以将其放入协程中以定期进行更新,而不是每一帧。例如,警报可以警告玩家附近是否有敌人。代码可能看起来像这样:

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

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

,

Unity(ab)使用枚举器来构建C#CoRoutines,因为异步/等待不存在。当你写的时候;

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的旧方法。