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