581 lines
25 KiB
Markdown
581 lines
25 KiB
Markdown
# 游戏启动流程开发手册
|
||
|
||
> 文件位置:`Docs/Guides/01_BootFlow_Setup_Guide.md`
|
||
> 版本:1.0 · 适用项目:zeling_v2
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [架构概览](#1-架构概览)
|
||
2. [完整启动时序图](#2-完整启动时序图)
|
||
3. [关键脚本说明](#3-关键脚本说明)
|
||
4. [事件频道速查表](#4-事件频道速查表)
|
||
5. [分步配置教程](#5-分步配置教程)
|
||
- 5.1 [创建事件频道资产(Step 1)](#51-创建事件频道资产step-1)
|
||
- 5.2 [配置 Persistent 场景(Step 2)](#52-配置-persistent-场景step-2)
|
||
- 5.3 [配置 Splash 演出(Step 2 续)](#53-配置-splash-演出step-2-续)
|
||
- 5.4 [配置 Loading 画面(Step 2 续)](#54-配置-loading-画面step-2-续)
|
||
- 5.5 [创建并配置主菜单场景(Step 3)](#55-创建并配置主菜单场景step-3)
|
||
- 5.6 [Build Settings 配置(Step 4)](#56-build-settings-配置step-4)
|
||
6. [使用编辑器向导工具](#6-使用编辑器向导工具)
|
||
7. [FSM 状态转换关系](#7-fsm-状态转换关系)
|
||
8. [自定义扩展指南](#8-自定义扩展指南)
|
||
9. [常见问题排查](#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_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 — 一键创建(推荐):**
|
||
|
||
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 1,`Scene_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 中赋值:
|
||
- `_onSplashStartRequest` → `EVT_SplashStartRequest`
|
||
- `_onSplashComplete` → `EVT_SplashComplete`
|
||
- `_onPreloadProgress` → `EVT_LoadingProgressUpdated`(可选)
|
||
- `_preloadLabel` → Addressable 标签名(如 `"Preload"`,留空则跳过)
|
||
3. 在 `GameManager` 组件 Inspector 中赋值:
|
||
- `_bootSequencer` → 上述 BootSequencer 组件
|
||
- `_onSceneLoadRequest` → `EVT_SceneLoadRequest`
|
||
- `_onSceneLoaded` → `EVT_SceneLoaded`
|
||
4. 在 `SceneLoader` 组件 Inspector 中赋值:
|
||
- `_onLoadingStarted` → `EVT_LoadingStarted`
|
||
- `_onLoadingComplete` → `EVT_LoadingComplete`
|
||
- `_onLoadingProgressUpdated` → `EVT_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 中赋值:
|
||
- `_loadingPanel` → `LoadingPanel` GameObject
|
||
- `_progressBar` → `ProgressBar` Slider 组件
|
||
- `_onLoadingStarted` → `EVT_LoadingStarted`(已由脚手架绑定)
|
||
- `_onLoadingComplete` → `EVT_LoadingComplete`(已由脚手架绑定)
|
||
- `_onLoadingProgressUpdated` → `EVT_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` 为索引 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 → 转换到 `LoadingScene`
|
||
- `TransitionType.Scene` + 目标 = `Scene_MainMenu` → 不经过 `LoadingScene`,由 `HandleSceneLoaded` 直接转换
|
||
- `TransitionType.Room` → 完全忽略,状态保持 Gameplay
|
||
|
||
---
|
||
|
||
## 8. 自定义扩展指南
|
||
|
||
### 添加第三段 Splash(如游戏内 IP 授权方 Logo)
|
||
|
||
在 `SplashScreenController` 中 `PlayAsync()` 方法末尾追加:
|
||
|
||
```csharp
|
||
// 第三段: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. 在 `LoadingScreenManager` 的 `Show()` 中启动 `VideoPlayer.Play()`,`Hide()` 中停止。
|
||
3. 使用 `EVT_LoadingStarted` / `EVT_LoadingComplete` 频道触发视频播放/停止。
|
||
|
||
---
|
||
|
||
### 新游戏时跳过存档选择(单存档模式)
|
||
|
||
在 `MainMenuController` 中,将 `OnNewGameClicked` 修改为直接触发槽 0:
|
||
|
||
```csharp
|
||
private void OnNewGameClicked()
|
||
{
|
||
// 单存档:直接使用槽 0
|
||
HandleSlotConfirmed(0);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 添加主菜单背景音乐
|
||
|
||
在 `MainMenuController.Start()` 或响应 `EVT_GameStateChanged(MainMenu)` 时:
|
||
|
||
```csharp
|
||
_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 中标记。
|
||
**解决:**
|
||
1. 打开 **Window → Asset Management → Addressables → Groups**。
|
||
2. 找到目标场景资产,确认其 Address 与 `_firstGameSceneKey` 完全一致(大小写敏感)。
|
||
3. 确保该场景已勾选 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*
|