Unity源码PlayerLoop简要流程

以下是关于Unity源码PlayerLoop的简要流程和相关核心点的说明:


1. PlayerLoop简介

PlayerLoop是Unity引擎的主循环(Main Loop)系统,负责驱动游戏的各个阶段(如输入、物理、渲染、脚本更新等)的执行。
它本质上是一个分阶段、分模块的调度系统,保证每一帧都按照既定顺序执行各类引擎和用户逻辑。


2. ReentrancyChecker

ReentrancyChecker是Unity内部用于检测PlayerLoop重入(即递归调用)的工具。

  • 作用:防止PlayerLoop在未完成时被再次进入,避免死循环或状态错乱。
  • 实现:通常是一个静态计数器或标志位,在进入PlayerLoop时+1,退出时-1,如果发现已进入则报错或断言。

3. IsWorldPlaying

IsWorldPlaying用于判断当前世界(World)是否处于“运行”状态。

  • 作用:有些PlayerLoop阶段只在游戏运行时执行(如Update),编辑器暂停或非Play模式下不执行。
  • 实现:通常是一个全局或上下文状态标志,PlayerLoop各阶段会根据此状态决定是否执行。

4. s_defaultLoop

s_defaultLoop是Unity内置的默认PlayerLoop配置。

  • 作用:定义了每一帧各个阶段(如Initialization、Update、LateUpdate、PreRender等)的顺序和内容。
  • 结构:是一个嵌套的PlayerLoopSystem树状结构,描述了所有子系统的执行顺序。
  • 可通过PlayerLoop.GetDefaultPlayerLoop()获取。

5. PlayerLoopSystem组织方式

PlayerLoopSystem是PlayerLoop的核心数据结构,定义如下(伪代码):

struct PlayerLoopSystem
{
    Type type; // 阶段类型(如Update、FixedUpdate等)
    PlayerLoopSystem[] subSystemList; // 子阶段
    UpdateFunction updateDelegate; // 处理函数
    IntPtr updateFunction; // 原生函数指针
    IntPtr loopConditionFunction; // 条件函数
}
  • 树状结构:每个PlayerLoopSystem可以有多个子系统,形成多层嵌套。
  • 类型标识:每个阶段用Type区分(如UnityEngine.PlayerLoop.Update)。
  • 处理函数:每个阶段可绑定C#委托或原生函数。

6. PlayerLoopSystem的处理函数

  • updateDelegate:C#层的委托,指向该阶段要执行的函数。
  • updateFunction:原生层的函数指针,通常用于C++实现的引擎模块。
  • loopConditionFunction:判断该阶段是否需要执行的条件函数。

执行时,PlayerLoop会遍历PlayerLoopSystem树,依次调用每个阶段的处理函数。


7. ExecutePlayerLoop

ExecutePlayerLoop是PlayerLoop的核心执行入口。

  • 作用:递归遍历PlayerLoopSystem树,按顺序执行每个阶段的处理函数。
  • 伪代码流程如下:
void ExecutePlayerLoop(PlayerLoopSystem system)
{
    if (system.loopConditionFunction == null || system.loopConditionFunction())
    {
        if (system.updateDelegate != null)
            system.updateDelegate();
        else if (system.updateFunction != IntPtr.Zero)
            CallNativeFunction(system.updateFunction);

        if (system.subSystemList != null)
        {
            foreach (var sub in system.subSystemList)
                ExecutePlayerLoop(sub);
        }
    }
}
  • 主循环:每帧由Unity底层驱动,调用顶层PlayerLoopSystem(如s_defaultLoop)。
  • 递归执行:每个阶段先执行自身处理函数,再递归执行子阶段。

总结流程图

  1. 主循环启动(如每帧由引擎底层调用)
  2. ReentrancyChecker检测重入
  3. IsWorldPlaying判断是否执行
  4. 从s_defaultLoop开始递归
  5. PlayerLoopSystem树遍历
    • 检查loopConditionFunction
    • 执行updateDelegate或updateFunction
    • 递归子系统
  6. 一帧结束

参考


下面继续详细补充PlayerLoop相关的进阶内容,包括自定义、调试、常见用法和注意事项:


8. 自定义PlayerLoop

Unity允许开发者通过C# API自定义PlayerLoop流程,插入、替换或移除某些阶段,以实现特殊的执行顺序或功能。

8.1 获取和设置PlayerLoop

// 获取当前PlayerLoop
var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop();

// 修改playerLoop(如插入自定义阶段)
// ...

// 设置新的PlayerLoop
UnityEngine.LowLevel.PlayerLoop.SetPlayerLoop(playerLoop);

8.2 插入自定义阶段

假设你要在Update阶段插入自己的处理:

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

public class MyCustomSystem
{
    public static void MyUpdate()
    {
        Debug.Log("My Custom Update");
    }
}

// 获取默认PlayerLoop
var loop = PlayerLoop.GetDefaultPlayerLoop();

// 找到Update阶段
for (int i = 0; i < loop.subSystemList.Length; i++)
{
    if (loop.subSystemList[i].type == typeof(Update))
    {
        // 构造自定义阶段
        var mySystem = new PlayerLoopSystem
        {
            type = typeof(MyCustomSystem),
            updateDelegate = MyCustomSystem.MyUpdate
        };

        // 插入到Update子系统的开头
        var subs = loop.subSystemList[i].subSystemList.ToList();
        subs.Insert(0, mySystem);
        loop.subSystemList[i].subSystemList = subs.ToArray();
        break;
    }
}

// 应用修改
PlayerLoop.SetPlayerLoop(loop);

8.3 注意事项

  • 修改PlayerLoop后,所有MonoBehaviour的生命周期(如Update)依然会被正常调度。
  • 自定义阶段的执行顺序完全由你插入的位置决定。
  • 不建议频繁动态修改PlayerLoop,通常在游戏初始化时设置一次即可。

9. PlayerLoop调试与分析

9.1 查看PlayerLoop结构

可以通过递归遍历PlayerLoopSystem树,打印出所有阶段和子阶段,帮助理解执行顺序。

void PrintPlayerLoop(PlayerLoopSystem system, int depth = 0)
{
    Debug.Log(new string(' ', depth * 2) + system.type);
    if (system.subSystemList != null)
    {
        foreach (var sub in system.subSystemList)
            PrintPlayerLoop(sub, depth + 1);
    }
}

9.2 性能分析

  • 自定义阶段中应避免耗时操作,防止卡顿。
  • 可用Profiler标记自定义阶段,便于性能分析。

10. PlayerLoop常见用法

  • 自定义调度系统:如ECS、Job System等可通过PlayerLoop插入自定义调度点。
  • 热更框架:如ILRuntime、HybridCLR等热更方案,常通过PlayerLoop插入热更入口。
  • 特殊渲染/物理/网络同步:可在特定阶段插入自定义逻辑,保证时序正确。

11. PlayerLoop注意事项

  • 兼容性:自定义PlayerLoop在不同Unity版本可能有差异,注意API变动。
  • 生命周期:PlayerLoop只在Play模式下完整运行,编辑器下部分阶段不会执行。
  • 线程安全:PlayerLoop所有阶段默认在主线程执行,需自行管理多线程逻辑。

12. 典型PlayerLoop阶段(Unity 2021+)

  • Initialization
  • EarlyUpdate
  • FixedUpdate
  • PreUpdate
  • Update
  • PreLateUpdate
  • PostLateUpdate
  • TimeUpdate
  • PhysicsUpdate
  • ScriptRunBehaviourUpdate(MonoBehaviour.Update)
  • ScriptRunDelayedDynamicFrameRate
  • ScriptRunDelayedTasks

每个阶段下又有多个子阶段,具体可用PrintPlayerLoop方法查看。

下面继续详细补充PlayerLoop相关的进阶内容,包括自定义、调试、常见用法和注意事项:


8. 自定义PlayerLoop

Unity允许开发者通过C# API自定义PlayerLoop流程,插入、替换或移除某些阶段,以实现特殊的执行顺序或功能。

8.1 获取和设置PlayerLoop

// 获取当前PlayerLoop
var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop();

// 修改playerLoop(如插入自定义阶段)
// ...

// 设置新的PlayerLoop
UnityEngine.LowLevel.PlayerLoop.SetPlayerLoop(playerLoop);

8.2 插入自定义阶段

假设你要在Update阶段插入自己的处理:

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

public class MyCustomSystem
{
    public static void MyUpdate()
    {
        Debug.Log("My Custom Update");
    }
}

// 获取默认PlayerLoop
var loop = PlayerLoop.GetDefaultPlayerLoop();

// 找到Update阶段
for (int i = 0; i < loop.subSystemList.Length; i++)
{
    if (loop.subSystemList[i].type == typeof(Update))
    {
        // 构造自定义阶段
        var mySystem = new PlayerLoopSystem
        {
            type = typeof(MyCustomSystem),
            updateDelegate = MyCustomSystem.MyUpdate
        };

        // 插入到Update子系统的开头
        var subs = loop.subSystemList[i].subSystemList.ToList();
        subs.Insert(0, mySystem);
        loop.subSystemList[i].subSystemList = subs.ToArray();
        break;
    }
}

// 应用修改
PlayerLoop.SetPlayerLoop(loop);

8.3 注意事项

  • 修改PlayerLoop后,所有MonoBehaviour的生命周期(如Update)依然会被正常调度。
  • 自定义阶段的执行顺序完全由你插入的位置决定。
  • 不建议频繁动态修改PlayerLoop,通常在游戏初始化时设置一次即可。

9. PlayerLoop调试与分析

9.1 查看PlayerLoop结构

可以通过递归遍历PlayerLoopSystem树,打印出所有阶段和子阶段,帮助理解执行顺序。

void PrintPlayerLoop(PlayerLoopSystem system, int depth = 0)
{
    Debug.Log(new string(' ', depth * 2) + system.type);
    if (system.subSystemList != null)
    {
        foreach (var sub in system.subSystemList)
            PrintPlayerLoop(sub, depth + 1);
    }
}

9.2 性能分析

  • 自定义阶段中应避免耗时操作,防止卡顿。
  • 可用Profiler标记自定义阶段,便于性能分析。

10. PlayerLoop常见用法

  • 自定义调度系统:如ECS、Job System等可通过PlayerLoop插入自定义调度点。
  • 热更框架:如ILRuntime、HybridCLR等热更方案,常通过PlayerLoop插入热更入口。
  • 特殊渲染/物理/网络同步:可在特定阶段插入自定义逻辑,保证时序正确。

11. PlayerLoop注意事项

  • 兼容性:自定义PlayerLoop在不同Unity版本可能有差异,注意API变动。
  • 生命周期:PlayerLoop只在Play模式下完整运行,编辑器下部分阶段不会执行。
  • 线程安全:PlayerLoop所有阶段默认在主线程执行,需自行管理多线程逻辑。

12. 典型PlayerLoop阶段(Unity 2021+)

  • Initialization
  • EarlyUpdate
  • FixedUpdate
  • PreUpdate
  • Update
  • PreLateUpdate
  • PostLateUpdate
  • TimeUpdate
  • PhysicsUpdate
  • ScriptRunBehaviourUpdate(MonoBehaviour.Update)
  • ScriptRunDelayedDynamicFrameRate
  • ScriptRunDelayedTasks

每个阶段下又有多个子阶段,具体可用PrintPlayerLoop方法查看。


13. 参考资料


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你一身傲骨怎能输

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值