Unity3D :协程

Unity3D :协程
推荐:将NSDT场景编辑器加入你的3D工具链
3D工具集:NSDT简石数字孪生

协程

协程允许您将任务分散到多个帧中。在 Unity 中,协程是一种可以暂停执行并将控制权返回给 Unity 的方法,但随后在下一帧中断的地方继续。

在大多数情况下,调用方法时,该方法将运行到完成,然后将控制权以及任何可选的返回值返回到调用方法。这意味着方法中发生的任何操作都必须在单个帧更新中发生。

如果要使用方法调用来包含随时间推移的过程动画或事件序列,则可以使用协程。

但是,请务必记住,协程不是线程。在协程中运行的同步操作仍在主线程上执行。如果要减少在主线程上花费的 CPU 时间,避免协程中的阻塞操作与任何其他脚本代码中的阻塞操作同样重要。如果要在 Unity 中使用多线程代码,请考虑使用 C# 作业系统。

如果需要处理长时间异步操作,例如等待 HTTP 传输、资产加载或文件 I/O 完成,最好使用协程。

协程示例

例如,考虑逐渐减小对象的 alpha(不透明度)值直到它变得不可见的任务:

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

在此示例中,Fade 方法没有您可能期望的效果。要使淡入淡出可见,您必须减少一系列帧上的淡入淡出的 alpha 以显示 Unity 渲染的中间值。但是,此示例方法在单个帧更新中完整执行。中间值永远不会显示,对象会立即消失。

若要变通解决此问题,可以向逐帧执行淡入淡出的函数添加代码。但是,对此类任务使用协程可能更方便。Update

在 C# 中,声明如下的协程:

IEnumerator Fade()
{
    Color c = renderer.material.color;
    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        c.a = alpha;
        renderer.material.color = c;
        yield return null;
    }
}

协程是使用 IEnumerator 返回类型和正文中某处包含的 yield return 语句声明的方法。该行是下一帧中执行暂停和恢复的点。若要设置协程运行,需要使用 StartCoroutine 函数:yield return null

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

Fade 函数中的循环计数器在协程的生存期内保持其正确的值,并且在语句之间保留任何变量或参数。yield

协程时间延迟

默认情况下,Unity 在语句后恢复帧上的协程。如果要引入时间延迟,请使用 WaitForSeconds:yield

IEnumerator Fade()
{
    Color c = renderer.material.color;
    for (float alpha = 1f; alpha >= 0; alpha -= 0.1f)
    {
        c.a = alpha;
        renderer.material.color = c;
        yield return new WaitForSeconds(.1f);
    }
}

可以使用 在一段时间内传播效果,也可以将其用作在方法中包含任务的替代方法。Unity 每秒调用该方法数次,因此,如果您不需要经常重复任务,则可以将其放在协程中以获取定期更新,但不是每一帧。WaitForSecondsUpdateUpdate

例如,您可以在应用程序中设置一个警报,如果附近有敌人,则警告玩家,使用以下代码:

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(;;)
    {
        if (ProximityCheck())
        {
            // Perform some action here
        }
        yield return new WaitForSeconds(.1f);
    }
}

这减少了 Unity 执行的检查次数,而不会对游戏玩法产生任何明显影响。

停止协程

若要停止协程,请使用“停止协程”和“停止所有协程”。如果已将 SetActive 设置为禁用协程附加到的游戏对象,则协程也会停止。调用(实例在哪里)会立即触发 OnDisabled,Unity 会处理协程,从而有效地停止它。最后,在帧末尾调用。falseDestroy(example)exampleMonoBehaviourOnDestroy

注意:如果禁用了启用的 by 设置,Unity 不会停止协程。MonoBehaviourfalse

分析协程

协程的执行方式与其他脚本代码不同。Unity 中的大多数脚本代码都显示在性能跟踪中单个位置的特定回调调用下方。但是,协程的 CPU 代码始终出现在跟踪中的两个位置。

每当 Unity 启动协程时,协程中的所有初始代码(从协程方法开始到第一个语句)都会显示在跟踪中。每当调用 StartCoroutine 方法时,初始代码最常出现。Unity 回调生成的协程(例如返回 的回调)首先出现在其各自的 Unity 回调中。yieldStartIEnumerator

协程代码的其余部分(从第一次恢复到完成执行)显示在 Unity 主循环内的行中。DelayedCallManager

发生这种情况是因为 Unity 执行协程的方式。C# 编译器自动生成支持协程的类的实例。然后,Unity 使用此对象跟踪单个方法的多次调用之间的协程状态。由于协程中的局部范围变量必须在调用后保留,因此 Unity 会将本地范围变量提升到生成的类中,这些变量在协程期间仍保留在堆上分配。此对象还跟踪协程的内部状态:它记住协程在生成后必须在代码中的哪个点恢复。yield

因此,协程启动时发生的内存压力等于固定开销分配加上其局部范围变量的大小。

启动协程的代码构造并调用对象,然后在满足协程的条件时 Unity 再次调用该对象。由于协程通常在其他协程之外启动,因此这会在调用和 之间分配其执行开销。DelayedCallManageryieldyieldDelayedCallManager

您可以使用 Unity 性能分析器来检查和了解 Unity 在应用程序中执行协程的位置。为此,请在启用深度性能分析的情况下分析应用程序,这会分析脚本代码的每个部分并记录所有函数调用。然后,可以使用 CPU 使用率探查器模块调查应用程序中的协程。

延迟调用中具有协程的探查器会话
延迟调用中具有协程的探查器会话

最佳做法是将一系列操作压缩到尽可能少的单个协程数。嵌套协程对于代码清晰度和维护非常有用,但它们会增加内存开销,因为协程跟踪对象。

如果协程运行每个帧,并且不运行长时间运行的操作,则将其替换为 or 回调的性能更高。如果您有长时间运行或无限循环的协程,这将非常有用。yieldUpdateLateUpdate

3D建模学习工作室整理翻译,转载请注明出处!

上一篇:Unity3D :事件函数 (mvrlink.com)

下一篇:Unity3D :命名空间 (mvrlink.com)

NSDT场景编辑器 | NSDT 数字孪生 | GLTF在线编辑器 | 3D模型在线转换 | UnrealSynth虚幻合成数据生成器 | 3D模型自动纹理化工具
2023 power by nsdt©鄂ICP备2023000829号