Files
zeling_v2/Docs/Guides/01_BootFlow_Setup_Guide.md
2026-05-19 23:20:44 +08:00

25 KiB
Raw Permalink Blame History

游戏启动流程开发手册

文件位置:Docs/Guides/01_BootFlow_Setup_Guide.md
版本1.0 · 适用项目zeling_v2


目录

  1. 架构概览
  2. 完整启动时序图
  3. 关键脚本说明
  4. 事件频道速查表
  5. 分步配置教程
  6. 使用编辑器向导工具
  7. FSM 状态转换关系
  8. 自定义扩展指南
  9. 常见问题排查

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_SceneLoadRequestGameManager 和 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. 关键脚本说明

GameManagerCore/GameManager.cs

全局游戏管理器,持有 FSM 并协调所有顶层服务。

新增字段 用途
_bootSequencer 引用 Persistent 场景中的 BootSequencer
_onSceneLoadRequest 监听场景加载请求(与 SceneService 共享同一 SO
_onSceneLoaded 监听 SceneLoader 加载完成(携带场景名)

Start() 启动 BootCoroutine()HandleSceneLoadRequest() / HandleSceneLoaded() 自动驱动 FSM 转换。


BootSequencerCore/BootSequencer.cs

挂载在 Persistent 场景,驱动 Splash 演出与 Addressable 预热并行执行。

Inspector 字段 说明
_preloadLabel Addressable 预热标签(留空则跳过预热)
_onSplashStartRequest EVT_SplashStartRequestRaise
_onSplashComplete EVT_SplashCompleteListen
_onPreloadProgress EVT_LoadingProgressUpdated(可选,供进度条显示)

重要: _onSplashComplete 留空时BootSequencer 不等待 Splash直接进入主菜单。适合调试阶段快速跳过 Splash。


SplashScreenControllerUI/Splash/SplashScreenController.cs

挂载在 Canvas_Splash(排序层 100播放两段淡入/淡出动画,任意键可跳过。

Inspector 字段 说明
_studioLogoGroup 工作室 Logo 的 CanvasGroup
_gameTitleGroup 游戏标题的 CanvasGroup
_onSplashStartRequest EVT_SplashStartRequestListen
_onSplashComplete EVT_SplashCompleteRaise
_fadeDuration 淡入/淡出时长(秒,默认 1.0
_holdDuration 每帧持续时长(秒,默认 1.5

MainMenuControllerUI/MainMenu/MainMenuController.cs

挂载在主菜单场景的 Canvas 上,管理按钮、子面板、入场动画。

Inspector 字段 说明
_onGameStateChanged EVT_GameStateChangedListen
_onSceneLoadRequest EVT_SceneLoadRequestRaise
_onSlotConfirmed EVT_SlotConfirmedListen
_firstGameSceneKey 第一个游戏场景的 Addressable Key必填
_btnNewGame/Continue/Settings/Credits/Quit 各按钮引用
_menuPanel 主按钮区 GameObject用于入场动画
_saveSlotPanel / _settingsPanel / _creditsPanel 子面板
_saveSlotController SaveSlotController 引用

SceneLoaderCore/SceneLoader.cs

加载时序改为逐帧轮询,当 ShowLoadingScreen = true 时自动发布进度事件。

新增字段 用途
_onLoadingStarted EVT_LoadingStartedRaise 给 LoadingScreenManager
_onLoadingComplete EVT_LoadingCompleteRaise 给 LoadingScreenManager
_onLoadingProgressUpdated EVT_LoadingProgressUpdatedRaise 进度值 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_SceneLoadRequestEVT_SceneLoaded 在 Inspector 中必须使用同一个 SO 实例赋给所有相关组件。不同组件使用不同 SO 实例是最常见的配置错误。


5. 分步配置教程

5.1 创建事件频道资产Step 1

方法 A — 一键创建(推荐):

  1. 打开菜单 BaseGames → Tools → Boot Flow Wizard
  2. Step 1 区域点击 「一键创建所有缺失资产」
  3. 资产将创建在 Assets/_Game/Data/Events/UI/Splash/UI/Loading/UI/MainMenu/Core/ 目录下。
  4. 检查清单全部变绿后进入下一步。

方法 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 1Scene_Persistent 已在 Hierarchy 中打开。

方法 A — 自动脚手架(推荐):

  1. 在 Hierarchy 中打开 Scene_Persistent(双击或在 Build Settings 中打开)。
  2. 打开 BaseGames → Tools → Boot Flow Wizard,点击 Step 2 中的 「脚手架 Persistent 场景」
  3. 工具将自动:
    • [Services] 下创建 BootSequencer GameObject 并挂载组件。
    • [UI] 下创建 Canvas_Splash(排序层 100并挂载 SplashScreenController
    • [UI] 下创建 Canvas_Loading(排序层 99并挂载 LoadingScreenManager
    • 自动绑定所有存在的事件频道 SO。
    • GameManager 绑定 _bootSequencer_onSceneLoadRequest_onSceneLoaded
    • SceneLoader 绑定三个加载事件频道。
  4. 保存场景Ctrl+S

方法 B — 手动配置(如需精细控制):

  1. [Services] 下新建空 GameObject 命名为 BootSequencer,挂载 BootSequencer 组件。
  2. 在 Inspector 中赋值:
    • _onSplashStartRequestEVT_SplashStartRequest
    • _onSplashCompleteEVT_SplashComplete
    • _onPreloadProgressEVT_LoadingProgressUpdated(可选)
    • _preloadLabel → Addressable 标签名(如 "Preload",留空则跳过)
  3. GameManager 组件 Inspector 中赋值:
    • _bootSequencer → 上述 BootSequencer 组件
    • _onSceneLoadRequestEVT_SceneLoadRequest
    • _onSceneLoadedEVT_SceneLoaded
  4. SceneLoader 组件 Inspector 中赋值:
    • _onLoadingStartedEVT_LoadingStarted
    • _onLoadingCompleteEVT_LoadingComplete
    • _onLoadingProgressUpdatedEVT_LoadingProgressUpdated

5.3 配置 Splash 演出Step 2 续)

  1. 找到 Canvas_Splash GameObject。
  2. 在子节点中创建两个 Image + CanvasGroup 结构:
    Canvas_Splash (SplashScreenController)
    └── StudioLogo          <- 工作室 Logo 图片
        └── CanvasGroup
    └── GameTitle           <- 游戏标题图片/文字
        └── CanvasGroup
    
  3. StudioLogo 的 CanvasGroup 赋给 SplashScreenController._studioLogoGroup
  4. GameTitle 的 CanvasGroup 赋给 SplashScreenController._gameTitleGroup
  5. 调整 _fadeDuration(淡入/淡出时长)和 _holdDuration(停留时长)。

跳过 Splash调试模式

  • BootSequencer._onSplashComplete 留空BootSequencer 不会等待 Splash 完成,直接进入主菜单。

5.4 配置 Loading 画面Step 2 续)

  1. 找到 Canvas_Loading GameObject默认已由脚手架创建初始 SetActive(false))。
  2. LoadingScreenManager 创建所需 UI 子节点:
    Canvas_Loading (LoadingScreenManager)
    └── LoadingPanel (背景遮罩 + 内容)
        └── ProgressBar (Slider)
        └── TipText (可选:随机提示文字)
    
  3. LoadingScreenManager Inspector 中赋值:
    • _loadingPanelLoadingPanel GameObject
    • _progressBarProgressBar Slider 组件
    • _onLoadingStartedEVT_LoadingStarted(已由脚手架绑定)
    • _onLoadingCompleteEVT_LoadingComplete(已由脚手架绑定)
    • _onLoadingProgressUpdatedEVT_LoadingProgressUpdated(已由脚手架绑定)

5.5 创建并配置主菜单场景Step 3

创建场景:

  1. 在 Project 窗口右键 → Create → Scene命名为 Scene_MainMenu
  2. 将其放置在 Assets/_Game/Scenes/ 目录下。
  3. 双击打开场景。

自动脚手架:

  1. 打开 Boot Flow Wizard → Step 3,点击 「脚手架 MainMenu 场景」
  2. 工具将在场景中生成:
    [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)
    
  3. 必须手动填写 MainMenuController._firstGameSceneKey
    • 在 Inspector 中输入第一个游戏场景的 Addressable Key字符串
    • 例如:"Scene_Prologue""Scene_Town_01"

SaveSlotPanel 配置:

  1. 打开 SaveSlotPanel
  2. SaveSlotController 补充三个存档槽按钮引用(_slot0Btn_slot1Btn_slot2Btn)。
  3. 为每个按钮添加适当的 UI 样式(背景图、存档信息文本等)。

SettingsPanel / CreditsPanel
这两个面板为空节点,由各自项目美术/策划填充内容。MainMenuController 通过 _settingsPanel.SetActive(true/false) 控制其显隐。


5.6 Build Settings 配置Step 4

  1. 打开菜单 File → Build Settings(或 Boot Flow Wizard 中点击 「打开 Build Settings」)。
  2. 将以下场景加入 Scenes in Build顺序重要
    索引 场景 说明
    0 Assets/_Game/Scenes/Scene_Boot.unity 启动入口(仅包含 GameBootstrap
    Assets/_Game/Scenes/Scene_Persistent.unity DontDestroyOnLoad 场景(不需要显式索引)
    Assets/_Game/Scenes/Scene_MainMenu.unity 主菜单(通过 Addressables 加载)
  3. 确保 Scene_Boot 为索引 0Player 设置中的第一场景)。

注意: 其他所有游戏场景(关卡等)应通过 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 → 转换到 LoadingScene
  • TransitionType.Scene + 目标 = Scene_MainMenu → 不经过 LoadingScene,由 HandleSceneLoaded 直接转换
  • TransitionType.Room → 完全忽略,状态保持 Gameplay

8. 自定义扩展指南

SplashScreenControllerPlayAsync() 方法末尾追加:

// 第三段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 画面为视频背景

  1. Canvas_Loading 添加 RawImage 组件用于显示视频。
  2. LoadingScreenManagerShow() 中启动 VideoPlayer.Play()Hide() 中停止。
  3. 使用 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._onSceneLoadedSceneLoader._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 中标记。
解决:

  1. 打开 Window → Asset Management → Addressables → Groups
  2. 找到目标场景资产,确认其 Address 与 _firstGameSceneKey 完全一致(大小写敏感)。
  3. 确保该场景已勾选 Include in Build。

返回主菜单时 FSM 报 Invalid transition 警告

原因: 通常是从 DeadGameOver 状态直接触发 HandleSceneLoaded(MainMenu),而这两个状态的 ValidNextStates 不包含 MainMenu
解决: 确保 DeathRespawnService.StartGameOverCoroutine() 在加载主菜单场景之前已调用 GameManager.RequestTransition(GameOver)。参见 BuiltinGameStates.csGameOverState.ValidNextStates


编辑器中运行正常,打包后 Splash 不显示

原因: Canvas_Splash 上的 Image Sprite 未加入 Addressable Build 或 Sprite Atlas。
解决: 将 Splash 使用的所有 Texture/Sprite 加入 Addressable Group标签 "Preload" 以便启动时预热),或直接内嵌进默认 Resources。


文档最后更新2026-05-19