25 KiB
游戏启动流程开发手册
文件位置:
Docs/Guides/01_BootFlow_Setup_Guide.md
版本:1.0 · 适用项目:zeling_v2
目录
1. 架构概览
启动流程由以下四个程序集中的组件协同完成,通过事件频道(ScriptableObject 事件总线)彻底解耦:
BaseGames.Core BaseGames.UI
───────────────────────────── ─────────────────────────────
GameManager ◄──────────── SplashScreenController
└─ BootSequencer ────────────► (via EVT_SplashStartRequest)
└─ GameStateMachine MainMenuController
LoadingScreenManager
BaseGames.Core.Events (共享)
─────────────────────────────
VoidEventChannelSO FloatEventChannelSO
IntEventChannelSO StringEventChannelSO
SceneLoadRequestEventChannelSO
核心设计原则:
BaseGames.Core不依赖BaseGames.UI,所有跨程序集通信通过 SO 事件频道进行。- 所有场景加载请求都汇聚到
EVT_SceneLoadRequest,GameManager 和 SceneService 共享同一 SO 实例。 - GameStateMachine 是状态权威,所有 UI 面板的显隐跟随
EVT_GameStateChanged事件。
2. 完整启动时序图
应用启动(首场景加载前)
│
├── [RuntimeInitializeOnLoadMethod] GameBootstrap
│ └── 以 Additive 模式加载 Scene_Persistent
│
▼
Scene_Persistent 加载完成
│
├── GameServiceRegistrar.Awake(-2000)
│ └── 向 ServiceLocator 注册:SceneService、DeathRespawnService、
│ EventChannelRegistry、GameSaveManager、…
│
├── GameManager.Awake(-1000)
│ ├── DontDestroyOnLoad(root)
│ ├── FSM.TransitionTo(Initializing)
│ └── 注册所有 FSM 状态
│
└── GameManager.Start()
├── Raise(EVT_GameStateChanged, Initializing)
└── StartCoroutine(BootCoroutine)
│
├── BootSequencer.RunBootSequenceCoroutine()
│ ├── Raise(EVT_SplashStartRequest) ──► SplashScreenController 开始
│ │ 播放演出(工作室 Logo →
│ │ 游戏标题)可任意键跳过
│ │
│ ├── [并行] PreloadCoroutine()
│ │ └── Addressables.DownloadDependenciesAsync("Preload")
│ │ 每帧 Raise(EVT_LoadingProgressUpdated, progress*0.9)
│ │
│ └── WaitUntil(SplashDone && PreloadDone)
│ SplashScreenController 结束 → Raise(EVT_SplashComplete)
│
└── SceneService.LoadMainMenuCoroutine()
└── SceneLoader.LoadSceneCoroutine(Scene_MainMenu)
└── 加载成功 → Raise(EVT_SceneLoaded, "Scene_MainMenu")
└── GameManager.HandleSceneLoaded
└── FSM: Initializing → MainMenu
Raise(EVT_GameStateChanged, MainMenu)
─────────────────────────────────────────────────────────────────────────────
主菜单激活
│
├── MainMenuController.OnEnable()
│ └── 订阅 EVT_GameStateChanged / EVT_SlotConfirmed
│
└── MainMenuController 响应 EVT_GameStateChanged(MainMenu)
└── 播放入场动画(菜单面板下滑)
└── RefreshContinueButton()(检查存档是否存在)
─────────────────────────────────────────────────────────────────────────────
新游戏 / 继续
│
├── 玩家点击「新游戏」或「继续」
│ └── 显示 SaveSlotPanel(选择存档槽 0/1/2)
│
├── SaveSlotController → Raise(EVT_SlotConfirmed, slotIndex)
│
├── MainMenuController.HandleSlotConfirmed()
│ └── 关闭 SaveSlotPanel
│ Raise(EVT_SceneLoadRequest, {firstGameSceneKey, Scene, ShowLoadingScreen=true})
│
├── GameManager.HandleSceneLoadRequest()
│ └── FSM: MainMenu → LoadingScene
│ Raise(EVT_GameStateChanged, LoadingScene)
│
├── SceneLoader.LoadSceneCoroutine()
│ ├── Raise(EVT_LoadingStarted) → LoadingScreenManager 显示进度画面
│ ├── 每帧 Raise(EVT_LoadingProgressUpdated, p*0.9)
│ ├── Raise(EVT_LoadingProgressUpdated, 1.0)
│ └── Raise(EVT_LoadingComplete) → LoadingScreenManager 隐藏
│ Raise(EVT_SceneLoaded, gameSceneName)
│
└── GameManager.HandleSceneLoaded()
└── FSM: LoadingScene → Gameplay
Raise(EVT_GameStateChanged, Gameplay)
─────────────────────────────────────────────────────────────────────────────
暂停 → 返回主菜单
│
├── PauseMenuController.GoToMainMenu()
│ └── Raise(EVT_SceneLoadRequest, {Scene_MainMenu, Scene, ShowLoadingScreen=false})
│
├── GameManager.HandleSceneLoadRequest()
│ └── 目标为 Scene_MainMenu → 跳过 LoadingScene 中间状态
│
└── SceneLoader 加载完成 → Raise(EVT_SceneLoaded, "Scene_MainMenu")
└── FSM: Paused → MainMenu
3. 关键脚本说明
GameManager(Core/GameManager.cs)
全局游戏管理器,持有 FSM 并协调所有顶层服务。
| 新增字段 | 用途 |
|---|---|
_bootSequencer |
引用 Persistent 场景中的 BootSequencer |
_onSceneLoadRequest |
监听场景加载请求(与 SceneService 共享同一 SO) |
_onSceneLoaded |
监听 SceneLoader 加载完成(携带场景名) |
Start() 启动 BootCoroutine(),HandleSceneLoadRequest() / HandleSceneLoaded() 自动驱动 FSM 转换。
BootSequencer(Core/BootSequencer.cs)
挂载在 Persistent 场景,驱动 Splash 演出与 Addressable 预热并行执行。
| Inspector 字段 | 说明 |
|---|---|
_preloadLabel |
Addressable 预热标签(留空则跳过预热) |
_onSplashStartRequest |
赋 EVT_SplashStartRequest(Raise) |
_onSplashComplete |
赋 EVT_SplashComplete(Listen) |
_onPreloadProgress |
赋 EVT_LoadingProgressUpdated(可选,供进度条显示) |
重要: _onSplashComplete 留空时,BootSequencer 不等待 Splash,直接进入主菜单。适合调试阶段快速跳过 Splash。
SplashScreenController(UI/Splash/SplashScreenController.cs)
挂载在 Canvas_Splash(排序层 100),播放两段淡入/淡出动画,任意键可跳过。
| Inspector 字段 | 说明 |
|---|---|
_studioLogoGroup |
工作室 Logo 的 CanvasGroup |
_gameTitleGroup |
游戏标题的 CanvasGroup |
_onSplashStartRequest |
赋 EVT_SplashStartRequest(Listen) |
_onSplashComplete |
赋 EVT_SplashComplete(Raise) |
_fadeDuration |
淡入/淡出时长(秒,默认 1.0) |
_holdDuration |
每帧持续时长(秒,默认 1.5) |
MainMenuController(UI/MainMenu/MainMenuController.cs)
挂载在主菜单场景的 Canvas 上,管理按钮、子面板、入场动画。
| Inspector 字段 | 说明 |
|---|---|
_onGameStateChanged |
赋 EVT_GameStateChanged(Listen) |
_onSceneLoadRequest |
赋 EVT_SceneLoadRequest(Raise) |
_onSlotConfirmed |
赋 EVT_SlotConfirmed(Listen) |
_firstGameSceneKey |
第一个游戏场景的 Addressable Key(必填) |
_btnNewGame/Continue/Settings/Credits/Quit |
各按钮引用 |
_menuPanel |
主按钮区 GameObject(用于入场动画) |
_saveSlotPanel / _settingsPanel / _creditsPanel |
子面板 |
_saveSlotController |
SaveSlotController 引用 |
SceneLoader(Core/SceneLoader.cs)
加载时序改为逐帧轮询,当 ShowLoadingScreen = true 时自动发布进度事件。
| 新增字段 | 用途 |
|---|---|
_onLoadingStarted |
赋 EVT_LoadingStarted(Raise 给 LoadingScreenManager) |
_onLoadingComplete |
赋 EVT_LoadingComplete(Raise 给 LoadingScreenManager) |
_onLoadingProgressUpdated |
赋 EVT_LoadingProgressUpdated(Raise 进度值 0~1) |
4. 事件频道速查表
| SO 资产名 | 类型 | 发布者 | 监听者 | 说明 |
|---|---|---|---|---|
EVT_SplashStartRequest |
VoidEventChannelSO | BootSequencer | SplashScreenController | 触发 Splash 演出开始 |
EVT_SplashComplete |
VoidEventChannelSO | SplashScreenController | BootSequencer | Splash 结束通知 |
EVT_LoadingStarted |
VoidEventChannelSO | SceneLoader | LoadingScreenManager | 加载开始,显示进度画面 |
EVT_LoadingComplete |
VoidEventChannelSO | SceneLoader | LoadingScreenManager | 加载结束,隐藏进度画面 |
EVT_LoadingProgressUpdated |
FloatEventChannelSO | SceneLoader / BootSequencer | LoadingScreenManager | 进度值 0~1 |
EVT_SlotConfirmed |
IntEventChannelSO | SaveSlotController | MainMenuController | 存档槽选择完成(携带槽索引) |
EVT_SceneLoadRequest |
SceneLoadRequestEventChannelSO | MainMenuController / PauseMenuController / … | GameManager + SceneService | 场景加载请求(共享同一 SO) |
EVT_SceneLoaded |
StringEventChannelSO | SceneLoader | GameManager | 加载完成(携带场景名) |
EVT_GameStateChanged |
GameStateEventChannelSO | GameManager | MainMenuController / UIManager / … | FSM 状态改变通知 |
注意:
EVT_SceneLoadRequest和EVT_SceneLoaded在 Inspector 中必须使用同一个 SO 实例赋给所有相关组件。不同组件使用不同 SO 实例是最常见的配置错误。
5. 分步配置教程
5.1 创建事件频道资产(Step 1)
方法 A — 一键创建(推荐):
- 打开菜单 BaseGames → Tools → Boot Flow Wizard。
- 在 Step 1 区域点击 「一键创建所有缺失资产」。
- 资产将创建在
Assets/_Game/Data/Events/UI/Splash/、UI/Loading/、UI/MainMenu/、Core/目录下。 - 检查清单全部变绿后进入下一步。
方法 B — 手动创建:
在 Project 窗口右键 → Create,按以下清单逐一创建:
Assets/_Game/Data/Events/UI/Splash/
EVT_SplashStartRequest.asset (VoidEventChannelSO)
EVT_SplashComplete.asset (VoidEventChannelSO)
Assets/_Game/Data/Events/UI/Loading/
EVT_LoadingStarted.asset (VoidEventChannelSO)
EVT_LoadingComplete.asset (VoidEventChannelSO)
EVT_LoadingProgressUpdated.asset (FloatEventChannelSO)
Assets/_Game/Data/Events/UI/MainMenu/
EVT_SlotConfirmed.asset (IntEventChannelSO)
5.2 配置 Persistent 场景(Step 2)
前置条件: 已完成 Step 1,Scene_Persistent 已在 Hierarchy 中打开。
方法 A — 自动脚手架(推荐):
- 在 Hierarchy 中打开
Scene_Persistent(双击或在 Build Settings 中打开)。 - 打开 BaseGames → Tools → Boot Flow Wizard,点击 Step 2 中的 「脚手架 Persistent 场景」。
- 工具将自动:
- 在
[Services]下创建BootSequencerGameObject 并挂载组件。 - 在
[UI]下创建Canvas_Splash(排序层 100)并挂载SplashScreenController。 - 在
[UI]下创建Canvas_Loading(排序层 99)并挂载LoadingScreenManager。 - 自动绑定所有存在的事件频道 SO。
- 为
GameManager绑定_bootSequencer、_onSceneLoadRequest、_onSceneLoaded。 - 为
SceneLoader绑定三个加载事件频道。
- 在
- 保存场景(Ctrl+S)。
方法 B — 手动配置(如需精细控制):
- 在
[Services]下新建空 GameObject 命名为BootSequencer,挂载BootSequencer组件。 - 在 Inspector 中赋值:
_onSplashStartRequest→EVT_SplashStartRequest_onSplashComplete→EVT_SplashComplete_onPreloadProgress→EVT_LoadingProgressUpdated(可选)_preloadLabel→ Addressable 标签名(如"Preload",留空则跳过)
- 在
GameManager组件 Inspector 中赋值:_bootSequencer→ 上述 BootSequencer 组件_onSceneLoadRequest→EVT_SceneLoadRequest_onSceneLoaded→EVT_SceneLoaded
- 在
SceneLoader组件 Inspector 中赋值:_onLoadingStarted→EVT_LoadingStarted_onLoadingComplete→EVT_LoadingComplete_onLoadingProgressUpdated→EVT_LoadingProgressUpdated
5.3 配置 Splash 演出(Step 2 续)
- 找到
Canvas_SplashGameObject。 - 在子节点中创建两个 Image + CanvasGroup 结构:
Canvas_Splash (SplashScreenController) └── StudioLogo <- 工作室 Logo 图片 └── CanvasGroup └── GameTitle <- 游戏标题图片/文字 └── CanvasGroup - 将
StudioLogo的 CanvasGroup 赋给SplashScreenController._studioLogoGroup。 - 将
GameTitle的 CanvasGroup 赋给SplashScreenController._gameTitleGroup。 - 调整
_fadeDuration(淡入/淡出时长)和_holdDuration(停留时长)。
跳过 Splash(调试模式):
- 将
BootSequencer._onSplashComplete留空,BootSequencer 不会等待 Splash 完成,直接进入主菜单。
5.4 配置 Loading 画面(Step 2 续)
- 找到
Canvas_LoadingGameObject(默认已由脚手架创建,初始SetActive(false))。 - 为
LoadingScreenManager创建所需 UI 子节点:Canvas_Loading (LoadingScreenManager) └── LoadingPanel (背景遮罩 + 内容) └── ProgressBar (Slider) └── TipText (可选:随机提示文字) - 在
LoadingScreenManagerInspector 中赋值:_loadingPanel→LoadingPanelGameObject_progressBar→ProgressBarSlider 组件_onLoadingStarted→EVT_LoadingStarted(已由脚手架绑定)_onLoadingComplete→EVT_LoadingComplete(已由脚手架绑定)_onLoadingProgressUpdated→EVT_LoadingProgressUpdated(已由脚手架绑定)
5.5 创建并配置主菜单场景(Step 3)
创建场景:
- 在 Project 窗口右键 → Create → Scene,命名为
Scene_MainMenu。 - 将其放置在
Assets/_Game/Scenes/目录下。 - 双击打开场景。
自动脚手架:
- 打开 Boot Flow Wizard → Step 3,点击 「脚手架 MainMenu 场景」。
- 工具将在场景中生成:
[MainMenu] └── Canvas_MainMenu (Canvas, CanvasScaler, GraphicRaycaster, MainMenuController) └── MenuPanel (VerticalLayoutGroup) ├── Btn_NewGame (Image, Button) ├── Btn_Continue (Image, Button) ├── Btn_Settings (Image, Button) ├── Btn_Credits (Image, Button) └── Btn_Quit (Image, Button) ├── SaveSlotPanel (SetActive=false, SaveSlotController) ├── SettingsPanel (SetActive=false) └── CreditsPanel (SetActive=false) - 必须手动填写
MainMenuController._firstGameSceneKey:- 在 Inspector 中输入第一个游戏场景的 Addressable Key(字符串)。
- 例如:
"Scene_Prologue"或"Scene_Town_01"。
SaveSlotPanel 配置:
- 打开
SaveSlotPanel。 - 为
SaveSlotController补充三个存档槽按钮引用(_slot0Btn、_slot1Btn、_slot2Btn)。 - 为每个按钮添加适当的 UI 样式(背景图、存档信息文本等)。
SettingsPanel / CreditsPanel:
这两个面板为空节点,由各自项目美术/策划填充内容。MainMenuController 通过 _settingsPanel.SetActive(true/false) 控制其显隐。
5.6 Build Settings 配置(Step 4)
- 打开菜单 File → Build Settings(或 Boot Flow Wizard 中点击 「打开 Build Settings」)。
- 将以下场景加入 Scenes in Build(顺序重要):
索引 场景 说明 0 Assets/_Game/Scenes/Scene_Boot.unity启动入口(仅包含 GameBootstrap)— Assets/_Game/Scenes/Scene_Persistent.unityDontDestroyOnLoad 场景(不需要显式索引) — Assets/_Game/Scenes/Scene_MainMenu.unity主菜单(通过 Addressables 加载) - 确保
Scene_Boot为索引 0(Player 设置中的第一场景)。
注意: 其他所有游戏场景(关卡等)应通过 Addressables 打包,不应加入 Build Settings 的 Scene 列表,以避免包体膨胀。
6. 使用编辑器向导工具
Boot Flow Wizard
菜单:BaseGames → Tools → Boot Flow Wizard
窗口提供四个步骤的实时状态检查:
- ✅(绿色) = 该项已正确配置
- ⬜(灰色) = 该项尚未完成
| 步骤 | 功能 |
|---|---|
| Step 1 | 检测所有 8 个启动流程事件频道资产,一键创建缺失项 |
| Step 2 | 检测 Persistent 场景中 9 个组件/字段绑定状态,一键脚手架 |
| Step 3 | 检测主菜单场景组件状态,一键脚手架,快速打开 Build Settings |
| Step 4 | 运行全量验证并在 Console 输出带位置信息的报告 |
底部状态栏实时显示 通过 N / 总计 M 项检查。
Create Event Channel Assets
菜单:BaseGames → Tools → Create Event Channel Assets
在 Assets/_Game/Data/Events/ 目录下批量创建所有系统所需事件频道(含启动流程部分)。已存在资产自动跳过(幂等操作)。
Scaffold Persistent Scene / Scaffold Main Menu Scene
菜单:BaseGames → Tools → Scaffold Persistent Scene
菜单:BaseGames → Tools → Scaffold Main Menu Scene(优先级 202)
独立的脚手架工具,适合不打开向导窗口时直接执行。
7. FSM 状态转换关系
启动流程涉及的 GameStateMachine 转换:
Initializing
│
└──(EVT_SceneLoaded: Scene_MainMenu)──► MainMenu
│
┌─────────────────────┤
│ │
(新游戏/继续) (其他)
│
▼
LoadingScene
│
└──(EVT_SceneLoaded: GameScene)──► Gameplay
│
┌──────────┤
│ │
(Pause) (PlayerDied)
│ │
Paused Dead
│
┌──────────────┤
│ │
(Resume) (GoToMainMenu)
│ │
Gameplay MainMenu
GameOver ──(EVT_SceneLoaded: Scene_MainMenu)──► MainMenu
HandleSceneLoadRequest 逻辑(GameManager):
TransitionType.Scene+ 目标 ≠Scene_MainMenu→ FSM 当前在 MainMenu/Gameplay/BossFight → 转换到LoadingSceneTransitionType.Scene+ 目标 =Scene_MainMenu→ 不经过LoadingScene,由HandleSceneLoaded直接转换TransitionType.Room→ 完全忽略,状态保持 Gameplay
8. 自定义扩展指南
添加第三段 Splash(如游戏内 IP 授权方 Logo)
在 SplashScreenController 中 PlayAsync() 方法末尾追加:
// 第三段:IP Logo
if (_ipLogoGroup != null)
{
yield return StartCoroutine(Fade(_ipLogoGroup, 0f, 1f, _fadeDuration));
yield return new WaitForSeconds(_holdDuration);
yield return StartCoroutine(Fade(_ipLogoGroup, 1f, 0f, _fadeDuration));
}
同时在 Inspector 中添加 [SerializeField] private CanvasGroup _ipLogoGroup; 字段。
修改 Loading 画面为视频背景
- 给
Canvas_Loading添加RawImage组件用于显示视频。 - 在
LoadingScreenManager的Show()中启动VideoPlayer.Play(),Hide()中停止。 - 使用
EVT_LoadingStarted/EVT_LoadingComplete频道触发视频播放/停止。
新游戏时跳过存档选择(单存档模式)
在 MainMenuController 中,将 OnNewGameClicked 修改为直接触发槽 0:
private void OnNewGameClicked()
{
// 单存档:直接使用槽 0
HandleSlotConfirmed(0);
}
添加主菜单背景音乐
在 MainMenuController.Start() 或响应 EVT_GameStateChanged(MainMenu) 时:
_onBGMRequest?.Raise("BGM_MainMenu"); // 赋值 EVT_BGMRequest 频道
AudioManager 的 EVT_BGMRequest 监听器会自动淡入播放。
9. 常见问题排查
❌ 游戏启动后停在黑屏,不显示 Splash 也不进入主菜单
原因: GameManager._bootSequencer 未绑定,BootCoroutine 无法执行。
解决: 打开 Boot Flow Wizard → Step 2,检查 GameManager._bootSequencer 已绑定 是否为绿色;否则重新执行脚手架。
❌ Splash 演出播放完毕后一直黑屏(不进入主菜单)
原因 1: GameManager._onSceneLoaded 未绑定或绑定了错误的 SO 实例,HandleSceneLoaded 从未被调用。
解决: 确认 GameManager._onSceneLoaded 与 SceneLoader._onSceneLoaded 绑定同一个 EVT_SceneLoaded.asset。
原因 2: ISceneService 未注册(GameServiceRegistrar 未正确引用 SceneService)。
解决: Console 中搜索 [GameManager] ISceneService 未注册,检查 Persistent 场景中的 GameServiceRegistrar._sceneService 引用。
❌ 点击「新游戏」后进入黑屏,加载进度条不出现
原因 1: LoadingScreenManager._onLoadingStarted 未绑定。
原因 2: LoadingScreenManager._loadingPanel 为空(未赋值 UI 根节点)。
解决: 检查 Boot Flow Wizard Step 2 中 SceneLoader._onLoadingStarted 已绑定 状态。
❌ MainMenuController._firstGameSceneKey 填写后仍报 Addressable 加载失败
原因: Key 填写有误,或对应场景未在 Addressables 中标记。
解决:
- 打开 Window → Asset Management → Addressables → Groups。
- 找到目标场景资产,确认其 Address 与
_firstGameSceneKey完全一致(大小写敏感)。 - 确保该场景已勾选 Include in Build。
❌ 返回主菜单时 FSM 报 Invalid transition 警告
原因: 通常是从 Dead 或 GameOver 状态直接触发 HandleSceneLoaded(MainMenu),而这两个状态的 ValidNextStates 不包含 MainMenu。
解决: 确保 DeathRespawnService.StartGameOverCoroutine() 在加载主菜单场景之前已调用 GameManager.RequestTransition(GameOver)。参见 BuiltinGameStates.cs 中 GameOverState.ValidNextStates。
❌ 编辑器中运行正常,打包后 Splash 不显示
原因: Canvas_Splash 上的 Image Sprite 未加入 Addressable Build 或 Sprite Atlas。
解决: 将 Splash 使用的所有 Texture/Sprite 加入 Addressable Group(标签 "Preload" 以便启动时预热),或直接内嵌进默认 Resources。
文档最后更新:2026-05-19