Version: 6000.3
语言: 中文
事件函数执行顺序
管理时间和帧速率

自定义播放器循环

Unity 运行时应用程序在称为 Player 循环的连续循环中运行。在 Player 循环的每次迭代中,Unity 都会调用各种系统来执行渲染、物理模拟和输入处理等任务。

在播放器循环期间更新的各种系统和子系统按定义的默认顺序调用。您可以使用PlayerLoop应用程序接口。您可以检索默认或当前的播放器循环,并插入您自己的自定义播放器循环系统,或者删除、重新排序或替换现有循环系统。

您可能想要自定义播放器循环的原因有以下几个:

  • 创建自己的托管更新作为自定义更新管理器的一部分,可以成为 Unity 本机事件函数的更高性能的替代方案。有关详细信息,请参阅 Unity 博客上的 10000 次更新调用
  • 你可能希望针对特定平台优化播放器循环,例如删除物理、AI和XR一个总称,包括虚拟现实 (VR)、增强现实 (AR) 和混合现实 (MR) 应用。支持这些形式的交互式应用程序的设备可以称为 XR 设备。更多信息
    请参阅术语表
    手机游戏的更新。
  • 你可能希望在“编辑”模式或“播放”模式下创建更新循环的可视化效果,以帮助调试。

访问播放器循环

Player 循环由一系列嵌套的 Player 循环系统组成,每个循环系统都是PlayerLoopSystem结构。根或最外层的 Player 循环系统表示完整的 Player 循环,其他 Player 循环系统嵌套在该循环下。

在这种情况下,Player 循环系统只是在 Player 循环期间需要更新的任何内容的抽象表示。默认系统是本机 Unity 系统(如音频和输入)的表示形式,但您也可以从 C# 代码创建自己的自定义系统。

要访问 Unity 的默认播放器循环,请使用PlayerLoop.GetDefaultPlayerLoop.要访问当前有效的播放器循环,请使用PlayerLoop.GetCurrentPlayerLoop.默认和当前的 Player 循环将是相同的,除非你应用一个新的自定义 Player 循环PlayerLoop.SetPlayerLoop.

播放器循环中的每个系统都有一个PlayerLoopSystem.type属性来标识它。您可以递归迭代 Player 循环系统结构并打印Name每个type属性,以在控制台中显示 Player 循环结构。您还可以使用type属性,如以下示例所示,以标识要添加、删除或替换的播放器循环子系统。默认系统的有效类型在UnityEngine.PlayerLoopNamespace。

API 参考PlayerLoop.GetDefaultPlayerLoop包括一个示例,说明如何检索默认的 Player 循环,迭代嵌套的 Player 循环系统,并打印Name他们的type性能。该代码生成以下控制台输出:

ROOT NODE
    TimeUpdate
        WaitForLastPresentationAndUpdateTime
    Initialization
        ProfilerStartFrame
        UpdateCameraMotionVectors
        DirectorSampleTime
        AsyncUploadTimeSlicedUpdate
        SynchronizeInputs
        SynchronizeState
        XREarlyUpdate
    EarlyUpdate
        PollPlayerConnection
        GpuTimestamp
        AnalyticsCoreStatsUpdate
        UnityWebRequestUpdate
        ExecuteMainThreadJobs
        ProcessMouseInWindow
        ClearIntermediateRenderers
        ClearLines
        PresentBeforeUpdate
        ResetFrameStatsAfterPresent
        UpdateAsyncInstantiate
        UpdateAsyncReadbackManager
        UpdateStreamingManager
        UpdateTextureStreamingManager
        UpdatePreloading
        UpdateContentLoading
        RendererNotifyInvisible
        PlayerCleanupCachedData
        UpdateMainGameViewRect
        UpdateCanvasRectTransform
        XRUpdate
        UpdateInputManager
        ProcessRemoteInput
        ScriptRunDelayedStartupFrame
        UpdateKinect
        DeliverIosPlatformEvents
        ARCoreUpdate
        DispatchEventQueueEvents
        Physics2DEarlyUpdate
        PhysicsResetInterpolatedTransformPosition
        SpriteAtlasManagerUpdate
        PerformanceAnalyticsUpdate
    FixedUpdate
        ClearLines
        NewInputFixedUpdate
        DirectorFixedSampleTime
        AudioFixedUpdate
        ScriptRunBehaviourFixedUpdate
        DirectorFixedUpdate
        LegacyFixedAnimationUpdate
        XRFixedUpdate
        PhysicsFixedUpdate
        Physics2DFixedUpdate
        PhysicsClothFixedUpdate
        DirectorFixedUpdatePostPhysics
        ScriptRunDelayedFixedFrameRate
    PreUpdate
        PhysicsUpdate
        Physics2DUpdate
        PhysicsClothUpdate
        CheckTexFieldInput
        IMGUISendQueuedEvents
        NewInputUpdate
        InputForUIUpdate
        SendMouseEvents
        AIUpdate
        WindUpdate
        UpdateVideo
    Update
        ScriptRunBehaviourUpdate
        ScriptRunDelayedDynamicFrameRate
        ScriptRunDelayedTasks
        DirectorUpdate
    PreLateUpdate
        AIUpdatePostScript
        DirectorUpdateAnimationBegin
        LegacyAnimationUpdate
        DirectorUpdateAnimationEnd
        DirectorDeferredEvaluate
        AccessibilityUpdate
        UIElementsUpdatePanels
        EndGraphicsJobsAfterScriptUpdate
        ConstraintManagerUpdate
        ParticleSystemBeginUpdateAll
        Physics2DLateUpdate
        PhysicsLateUpdate
        ScriptRunBehaviourLateUpdate
    PostLateUpdate
        PlayerSendFrameStarted
        DirectorLateUpdate
        ScriptRunDelayedDynamicFrameRate
        PhysicsSkinnedClothBeginUpdate
        UpdateRectTransform
        PlayerUpdateCanvases
        UIElementsRepaintPanels
        UpdateAudio
        VFXUpdate
        ParticleSystemEndUpdateAll
        EndGraphicsJobsAfterScriptLateUpdate
        UpdateCustomRenderTextures
        XRPostLateUpdate
        UpdateAllRenderers
        UpdateLightProbeProxyVolumes
        EnlightenRuntimeUpdate
        UpdateAllSkinnedMeshes
        ProcessWebSendMessages
        SortingGroupsUpdate
        UpdateVideoTextures
        UpdateVideo
        DirectorRenderImage
        PlayerEmitCanvasGeometry
        UIElementsRenderBatchModeOffscreen
        PhysicsSkinnedClothFinishUpdate
        FinishFrameRendering
        BatchModeUpdate
        PlayerSendFrameComplete
        UpdateCaptureScreenshot
        PresentAfterDraw
        ClearImmediateRenderers
        PlayerSendFramePostPresent
        UpdateResolution
        InputEndFrame
        TriggerEndOfFrameCallbacks
        GUIClearEvents
        ShaderHandleErrors
        ResetInputAxis
        ThreadedLoadingDebug
        ProfilerSynchronizeStats
        MemoryFrameMaintenance
        ExecuteGameCenterCallbacks
        XRPreEndFrame
        ProfilerEndFrame
        GraphicsWarmupPreloadedShaders
        ObjectDispatcherPostLateUpdate

将自定义更新插入默认播放器循环

自定义播放器循环的推荐且风险最低的方法是将自定义播放器循环系统插入到默认播放器循环中。这样,您就可以添加自己的自定义更新逻辑,而不会中断任何 Unity 的内置系统。

中的代码示例PlayerLoop.SetPlayerLoopAPI 参考显示了如何在指定点将自定义播放器循环系统插入到默认播放器循环中。在这种情况下,的行为CustomUpdate方法在InsertSystem类。但是以下小的修改可以使CustomUpdate您的 MonoBehaviour脚本一段代码,允许您创建自己的组件、触发游戏事件、随时间修改组件属性以及以您喜欢的任何方式响应用户输入。更多信息
请参阅术语表
可以订阅并提供自己的更新逻辑:

  • 将事件声明为InsertSystem类:
public static event Action AddCustomUpdate;
  • 修改CustomUpdate调用此事件的方法:
private static void CustomUpdate() => AddCustomUpdate?.Invoke();

然后,您可以从任何 MonoBehaviour 脚本订阅自定义更新,如下所示:

public class MyMonoBehaviour : MonoBehaviour
{
    private void OnEnable()
    {
        SystemInsertion.AddCustomUpdate += MyCustomUpdate;
    }

    private void Update()
    {
        Debug.Log("Update");
    }

    private void LateUpdate()
    {
        Debug.Log("Late Update");
    }

    private void OnDisable()
    {
        SystemInsertion.AddCustomUpdate -= MyCustomUpdate;
    }

    private void MyCustomUpdate()
    {
        Debug.Log("Custom update running!");
    }
}

注意:作为参数传递给的播放器循环系统PlayerLoop.SetPlayerLoop覆盖当前播放器循环。因此,请确保传递给此方法的任何循环都包含要保留的所有系统,包括不替换的系统。如果从头开始创建新的播放器循环系统,则必须将要保留的所有系统显式添加到新循环中。

替换播放器循环中的默认系统

以下示例将播放器循环中的默认系统替换为自定义更新。该示例将PreUpdate.AIUpdate系统,但你可以通过指定其 PlayerLoopSystem.type 标识符来替换 Player 循环中的任何系统。默认系统的有效类型可以通过迭代默认 Player 循环(如上一节所示)或从UnityEngine.PlayerLoopNamespace。

重要提示: 替换播放器循环中的系统可能会产生意想不到的后果,因为它会移除该系统的默认功能。请谨慎使用此方法,并且仅当确定要用自己的自定义逻辑替换默认功能时。

using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;
using UnityEngine;

// Replace an existing system in the Unity Player Loop with a custom update.

public class MyCustomUpdate { } // Empty class to use as a type identifier for the custom update

public class SystemReplacement
{
    //Run this method on runtime initialization
    [RuntimeInitializeOnLoadMethod]
    private static void AppStart()
    {
        // Retrieve the default Player loop system. Get the current loop instead if the default was already modified previously.
        var defaultSystems = PlayerLoop.GetDefaultPlayerLoop();
        // Create a custom update system to replace an existing one
        var customUpdate = new PlayerLoopSystem()
        {
            updateDelegate = CustomUpdate,
            type = typeof(MyCustomUpdate)
        };
        // Specify the system to replace as the type parameter, in this case PreUpdate.AIUpdate
        ReplaceSystem<PreUpdate.AIUpdate>(ref defaultSystems, customUpdate);
        PlayerLoop.SetPlayerLoop(defaultSystems);
    }

    // Custom update method that will be called in the Player Loop
    private static void CustomUpdate()
    {
        Debug.Log("Custom update running!");
    }

    //Recursively replace a system of type T with a replacement system in the Player Loop
    private static bool ReplaceSystem<T>(ref PlayerLoopSystem system, PlayerLoopSystem replacement)
    {
        if (system.type == typeof(T))
        {
            system = replacement;
            return true;
        }
        if (system.subSystemList != null)
        {
            for (var i = 0; i < system.subSystemList.Length; i++)
            {
                if (ReplaceSystem<T>(ref system.subSystemList[i], replacement))
                {
                    return true;
                }
            }
        }
        return false;
    }
}

其他资源和示例

事件函数执行顺序
管理时间和帧速率