启动引导

框架自动启动——在第一个场景加载前按依赖顺序注册全部 23 个服务。

框架自动启动 · 在第一个场景加载前运行一次 · 注册全部 23 个服务 · 不可被替换

功能概述

Bootstrap 是框架的自动启动例程。它按依赖顺序注册全部 23 个服务,清除上一次 Play 会话遗留的框架内部 GameObject(仅在 Editor 中禁用 Domain Reload 时才有影响),并预热 Logger。你永远不需要调用它——Unity 会在第一个场景加载前自动调用,并且每个 Play 会话恰好运行一次。

启动完成后,Unity Console 会显示这一行:

bootstrap complete (v2.1.0, 23 services)

看到这一行,说明框架已经启动,所有服务都可以使用了。

快速示例

最常见的实际用法是:你永远不会直接调用 Bootstrap。当你自己的 Awake 运行时,所有服务都已注册完毕——直接解析并使用即可。

using CommonGameSystem.Core;
using UnityEngine;

[DefaultExecutionOrder(100)]  // Run AFTER framework startup — the attribute goes on the class
public class GameController : MonoBehaviour
{
    void Awake()
    {
        // Bootstrap.Run has already fired. All services are registered.
        // Resolve once and cache the references.
        var time = ServiceLocator.Resolve<ITimeService>();
        var pool = ServiceLocator.Resolve<IObjectPoolService>();
        var bus  = ServiceLocator.Resolve<IEventBus>();

        // DeltaTime is a method: you pick the clock you want.
        Debug.Log($"Gameplay: {time.DeltaTime(Clock.Gameplay)}, " +
                  $"unscaled: {time.UnscaledDeltaTime(Clock.Gameplay)}");
    }
}

在 EditMode 测试中,Bootstrap 通常也已经运行过——它的启动钩子同样会在测试域中触发——因此可以直接解析服务:

using CommonGameSystem.Core;
using NUnit.Framework;

public class BootstrapSmokeTests
{
    [Test]
    public void Bootstrap_RegistersCoreServices()
    {
        // Bootstrap.Run already fired for the test domain. Resolve and assert.
        Assert.IsNotNull(ServiceLocator.Resolve<ILogger>());
        Assert.IsNotNull(ServiceLocator.Resolve<IObjectPoolService>());
        Assert.IsTrue(Bootstrap.HasRun);
    }
}

完整 API 一览

这里没有需要解析的东西。Bootstrap 通过 Unity 的 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] 钩子自动触发。它在任何场景的 Awake 之前运行,也先于你的任何代码接触服务定位器

namespace CommonGameSystem.Core
{
    public static class Bootstrap
    {
        // Entry point — Unity calls this automatically (do NOT invoke directly).
        public static void Run();

        // Registers the settings-persistence backend. Public so automated
        // tests can inject a stub; not part of normal use.
        public static void RegisterConfigurationPersistence();

        // Diagnostic-only (read-only — set internally). Editor-only:
        // these two properties do not exist in player builds.
        public static bool HasRun { get; }
        public static int  RunCount { get; }
    }
}
成员作用
Run()启动入口点。Unity 在每个 Play 会话中调用它一次,时机在第一个场景加载之前。你自己再调用一次会抛出 ServiceAlreadyRegisteredException
RegisterConfigurationPersistence()注册配置服务保存设置所用的存储后端:存档/读档服务处于激活状态时走该服务,否则走 Unity 的 PlayerPrefs。之所以是 public 只是为了让测试能够验证回退路径;正常使用中不会用到。
HasRun启动至少完成过一次后为 true。只读,仅限 Editor——引用处需要包在 #if UNITY_EDITOR 中,或放在 Editor/测试程序集里。
RunCount启动运行的累计次数(在 Editor 中禁用 Domain Reload 时会跨 Play 会话递增)。只读,仅限 Editor

23 个服务(按注册顺序)

Bootstrap 按以下顺序启动这些服务。每一个都是可以用 ServiceLocator.Resolve<T>() 解析的接口,并各自拥有独立的参考页面。

  1. ServiceLocator —— 其他所有服务所在的注册表;也是你获取任意服务时唯一需要调用的类。参见服务定位器
  2. ILogger —— 带按分类过滤的分类日志。参见日志
  3. IObjectPoolService —— Prefab 池化。参见对象池
  4. ITimeService —— 按时钟划分的时间、暂停与慢动作(Gameplay/UI/Background 时钟)。参见时间
  5. IEventBus —— 系统间类型安全的发布/订阅。参见事件总线
  6. ISaveService —— 带安全原子写入与槽位的 JSON 存档文件。参见存档/读档
  7. IConfiguration —— 带变更事件的类型化设置组。参见配置
  8. IInputService —— 输入动作表上下文与按键重绑定。参见输入
  9. IAudioService —— 经由 AudioMixer 的音乐、音效与语音。参见音频
  10. IPanelStack —— UI 面板压栈/出栈,带手柄与键盘焦点。参见UI 框架
  11. ISceneService —— 带加载画面、取消支持与叠加加载/卸载的异步场景加载。参见场景流程
  12. ILocalizationService —— 键到字符串的查找与运行时语言切换。参见本地化
  13. IAchievementService —— 本地统计与成就,支持自动解锁。参见成就
  14. IScheduler —— After/Every/NextFrame 定时器与在主线程执行的调度。参见调度器
  15. ITweenService —— 感知暂停的数值补间,内置 31 种缓动。参见补间
  16. IAssetProvider —— 带引用计数的 Addressables 加载。参见资源提供器
  17. IRandomService —— 可设种子、可复现的随机数,支持命名流。参见随机
  18. IStateMachineService —— 扁平有限状态机工厂。参见FSM
  19. IPushdownStackService —— 堆叠式游戏状态作用域(例如玩法之上的暂停菜单)。参见下推栈
  20. ITweenSequenceService —— 顺序与并行的补间时间线。参见补间序列
  21. IAddressableSceneService —— 从 Addressables 加载的叠加场景。参见Addressable 场景
  22. IDeferredBus —— 现在入队事件,稍后统一派发。参见延迟事件队列
  23. ICommandRegistry —— 运行时命令注册表(控制台 UI 由你自行构建)。参见命令注册表

内部辅助注册

Bootstrap 还会注册少量内部辅助对象,以便自动化测试可以替换它们:设置持久化后端、三个内置设置验证器,以及输入键位映射源。游戏代码永远不会直接解析它们,它们也不计入 23 个服务。

行为与边界情况

  • 只运行一次。 在同一个 Play 会话中调用 Bootstrap.Run() 两次会抛出 ServiceAlreadyRegisteredException。Unity 的钩子保证它在每次进入 Play 时恰好触发一次——你永远不需要调用它。

  • 仅限主线程。 启动钩子在 Unity 主线程上运行。Debug 构建会对此做断言;违规会被尽早捕获并中止 Play。

  • 注册前先清理。 在注册服务之前,Bootstrap 会清除上一次 Play 会话遗留的框架内部 GameObject。这只有在 Editor 中禁用 Domain Reload 时才有意义(Project Settings → Editor → "Enter Play Mode Settings");开启 Domain Reload 时,每次 Play 会话本来就会以干净的层级开始。框架通过内部标记组件找到自己遗留的对象,并用 Object.Destroy 销毁它们——在帧末而非立即——因此 PlayMode 测试应在启动后 yield return null,让销毁落定。该标记组件是框架内部实现;不要自行挂载。

  • Logger 预热。 启动期间,Bootstrap 会在主线程上进行一次 Logger 调用。这次调用为静态 Logger 辅助方法完成预热,使后续来自工作线程的调用(例如存档文件 I/O)是安全的。

  • 启动失败会终止 Play。 序列中较晚创建的服务通过构造函数接收依赖。如果某个构造函数抛出异常,异常会向上传播并立即中止 Play——问题在启动时就暴露,而不是几分钟之后。

  • Editor 关闭钩子。 仅在 Editor 中,Bootstrap 会监视 Play 模式退出,并按注册顺序的逆序释放所有可释放的服务。玩家构建不包含这个钩子——它们依赖正常的进程销毁流程。

  • Bootstrap 本身不可被替换——它是启动其他一切的代码。不过它注册的每个服务都可以被替换,而且每个服务都附带一个 Null(无操作)实现,可以作为一行代码的关闭开关换入:

    // Example: silence all diagnostics
    ServiceLocator.Replace<ILogger>(new NullLogger());
    
    // Example: disable audio entirely
    ServiceLocator.Replace<IAudioService>(new NullAudio());
    
    // Example: stub the scene service for testing
    ServiceLocator.Replace<ISceneService>(new NullSceneService());
    

    Logger 有一个注意点:静态 Logger 辅助方法在启动期间缓存了后端,因此会话中途的替换不会重定向它们。要在运行时静音日志,请改为设置 Logger.MinimumLevel = LogLevel.Off——详见日志

相关页面