完整启动流程
This commit is contained in:
580
Docs/Guides/01_BootFlow_Setup_Guide.md
Normal file
580
Docs/Guides/01_BootFlow_Setup_Guide.md
Normal file
@@ -0,0 +1,580 @@
|
||||
# 游戏启动流程开发手册
|
||||
|
||||
> 文件位置:`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*
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,956 +0,0 @@
|
||||
# PathBerserker2d 技术评估与使用手册
|
||||
|
||||
> **版本**: PathBerserker2d (Unity Asset Store)
|
||||
> **Unity 要求**: 2020.3+
|
||||
> **依赖**: `com.unity.modules.physics2d`
|
||||
> **许可**: Unity Asset Store EULA
|
||||
> **包路径**: `Assets/PathBerserker2d/`
|
||||
> **官方文档**: https://oribow.github.io/PathBerserker2dDemo/Documentation/
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [概述与设计哲学](#1-概述与设计哲学)
|
||||
2. [架构总览](#2-架构总览)
|
||||
3. [核心组件](#3-核心组件)
|
||||
4. [NavAgent 详解](#4-navagent-详解)
|
||||
5. [NavSurface 详解](#5-navsurface-详解)
|
||||
6. [NavLink 与 NavLinkCluster](#6-navlink-与-navlinkcluster)
|
||||
7. [辅助组件](#7-辅助组件)
|
||||
8. [全局系统 — PBWorld 与 Settings](#8-全局系统--pbworld-与-settings)
|
||||
9. [路径与寻路请求](#9-路径与寻路请求)
|
||||
10. [预置行为脚本](#10-预置行为脚本)
|
||||
11. [TransformBasedMovement 移动系统](#11-transformbasedmovement-移动系统)
|
||||
12. [Demo 场景与使用模式](#12-demo-场景与使用模式)
|
||||
13. [Corgi 引擎集成](#13-corgi-引擎集成)
|
||||
14. [全局设置参考](#14-全局设置参考)
|
||||
15. [与 BaseGames 架构集成方案](#15-与-basegames-架构集成方案)
|
||||
16. [性能分析与优化建议](#16-性能分析与优化建议)
|
||||
17. [优缺点总结](#17-优缺点总结)
|
||||
18. [总结与建议](#18-总结与建议)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述与设计哲学
|
||||
|
||||
### 1.1 什么是 PathBerserker2d
|
||||
|
||||
PathBerserker2d 是一款专为 **2D 游戏** 设计的导航/寻路插件。与 Unity 内置的 NavMesh(面向 3D)不同,它基于 **线段(Segment)** 而非面片构建导航图,天然适合 2D 横版游戏的地形表示。
|
||||
|
||||
核心理念:
|
||||
|
||||
> **从 2D Collider 自动烘焙导航线段,通过多线程 A\* 异步寻路,以事件驱动的方式委托移动实现。**
|
||||
|
||||
### 1.2 核心设计原则
|
||||
|
||||
| 原则 | 说明 |
|
||||
|------|------|
|
||||
| **线段导航** | 导航数据为沿 Collider2D 表面的线段,适合平台跳跃和横版场景 |
|
||||
| **自动烘焙** | 从子对象的 Collider2D 自动提取导航线段,无需手动标记 |
|
||||
| **异步多线程** | 寻路计算在后台线程完成(默认4线程),不阻塞主线程 |
|
||||
| **事件驱动** | NavAgent 通过事件通知移动组件,移动实现完全解耦 |
|
||||
| **动态世界** | 支持运行时烘焙、移动平台、动态链接启停 |
|
||||
| **链接系统** | 跳跃、下落、攀爬、电梯、传送等复杂行为通过 NavLink 建模 |
|
||||
|
||||
### 1.3 适用场景
|
||||
|
||||
- 2D 横版平台跳跃(Metroidvania / Platformer)
|
||||
- 2D 俯视角 RPG(360° 模式)
|
||||
- 需要 AI 自动寻路的 2D 游戏
|
||||
- 含电梯、梯子、传送门、移动平台等复杂地形的关卡
|
||||
|
||||
---
|
||||
|
||||
## 2. 架构总览
|
||||
|
||||
### 2.1 系统架构图
|
||||
|
||||
```
|
||||
NavSurface (烘焙 Collider2D → 线段数据)
|
||||
↓ OnEnable 时添加
|
||||
PBWorld.NavGraph (全局导航图,B2DynamicTree 空间索引)
|
||||
↑ NavLink / NavLinkCluster 添加连接
|
||||
↑ NavAreaMarker 标记区域 NavTag
|
||||
↑ NavSegmentSubstractor 裁剪线段
|
||||
|
||||
NavAgent.PathTo() → PBWorld.PathTo(PathRequest)
|
||||
↓ 入队
|
||||
PathfinderThread (多线程 A*) → PathRequest.Fulfill(Path)
|
||||
↓ 主线程轮询
|
||||
NavAgent.HandlePathRequest() → 开始 FollowPath
|
||||
↓ 事件驱动
|
||||
TransformBasedMovement / 自定义移动 (实际移动)
|
||||
↓ 完成后回调
|
||||
NavAgent.CompleteSegmentTraversal() / CompleteLinkTraversal()
|
||||
```
|
||||
|
||||
### 2.2 关键设计模式
|
||||
|
||||
| 模式 | 说明 |
|
||||
|------|------|
|
||||
| **异步多线程寻路** | PathRequest 入队 → PathfinderThread 后台 A\* → 主线程轮询结果 |
|
||||
| **事件驱动移动** | NavAgent 触发事件,外部组件响应事件实现移动逻辑 |
|
||||
| **动态世界** | NavSurface 可运行时烘焙/加载/卸载,链接可动态添加/移除/切换可穿越性 |
|
||||
| **移动平台支持** | 所有坐标相对 NavSurface 本地坐标存储,Transform 变化时自动跟随 |
|
||||
| **自动重新寻路** | `autoRepathIntervall` 控制周期性重算路径,应对世界变化 |
|
||||
| **WebGL 兼容** | 检测平台后使用协程替代线程,确保单线程环境可用 |
|
||||
|
||||
### 2.3 目录结构
|
||||
|
||||
```
|
||||
Assets/PathBerserker2d/
|
||||
├── Scripts/PathBerserker2d/ # 核心源码
|
||||
│ ├── NavAgent/ # NavAgent.cs, TransformBasedMovement.cs
|
||||
│ │ └── NavAgentUsers/ # 预置行为脚本 (8个)
|
||||
│ ├── NavSurface/ # NavSurface.cs, 碰撞体过滤, 线段创建
|
||||
│ │ ├── NavSegments/ # 线段数据结构
|
||||
│ │ └── Creation/ # 烘焙管线
|
||||
│ ├── NavObjects/ # NavLink, NavLinkCluster, NavAreaMarker 等
|
||||
│ ├── NavGraph/ # 导航图, 图节点, 空间索引
|
||||
│ ├── Pathfinder/ # A* 寻路, 路径请求, 多线程
|
||||
│ ├── PBWorld.cs # 全局单例管理器
|
||||
│ ├── PathBerserker2dSettings.cs # 全局设置
|
||||
│ └── IVelocityProvider.cs # 速度接口
|
||||
├── Demo/ # 演示场景与脚本
|
||||
│ ├── Scenes/ # 15+ 场景
|
||||
│ └── Scripts/ # 演示脚本
|
||||
├── Corgi/ # Corgi Engine 集成层
|
||||
├── Resources/ # 全局设置资源文件
|
||||
└── Documentation/ # 官方文档 (zip)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心组件
|
||||
|
||||
### 3.1 组件总览
|
||||
|
||||
| 组件 | 菜单路径 | 职责 |
|
||||
|------|---------|------|
|
||||
| **NavSurface** | `PathBerserker2d/Nav Surface` | 从 Collider2D 烘焙导航线段 |
|
||||
| **NavAgent** | `PathBerserker2d/Nav Agent` | 寻路请求与路径跟随 |
|
||||
| **NavLink** | `PathBerserker2d/Nav Link` | 两点间导航链接(跳跃/下落/攀爬等) |
|
||||
| **NavLinkCluster** | `PathBerserker2d/Nav Link Cluster` | 多点互联链接集群(电梯/梯子) |
|
||||
| **NavAreaMarker** | `PathBerserker2d/Nav Area Marker` | 区域 NavTag 标记 |
|
||||
| **NavSegmentSubstractor** | — | 在烘焙时裁剪指定区域的线段 |
|
||||
| **DynamicObstacle** | — | 标记 GameObject 在烘焙时被忽略 |
|
||||
| **TransformBasedMovement** | — | 基于 Transform 的默认移动实现 |
|
||||
|
||||
### 3.2 组件依赖关系
|
||||
|
||||
```
|
||||
NavAgent ←需要→ TransformBasedMovement (或自定义移动组件)
|
||||
NavAgent ←依赖→ PBWorld (全局单例,自动创建)
|
||||
NavSurface ←依赖→ 子对象的 Collider2D
|
||||
NavLink / NavLinkCluster ←映射→ NavSurface 上的线段
|
||||
NavAreaMarker ←需要→ RectTransform
|
||||
NavSegmentSubstractor ←需要→ RectTransform
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. NavAgent 详解
|
||||
|
||||
### 4.1 状态机
|
||||
|
||||
```
|
||||
NavAgent 状态:
|
||||
┌─────────┐
|
||||
│ Idle │ ←── 初始 / Stop() / ForceStop() / 到达目标
|
||||
└────┬─────┘
|
||||
│ PathTo()
|
||||
▼
|
||||
┌─────────────────────────────┐
|
||||
│ FollowPath │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ OnSegment │ ←─ 在线段上移动
|
||||
│ ├─────────────────────┤ │
|
||||
│ │ WaitForLinkOnSegment│ ←─ 等待链接可用
|
||||
│ ├─────────────────────┤ │
|
||||
│ │ OnLink │ ←─ 正在穿越链接
|
||||
│ └─────────────────────┘ │
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
### 4.2 公共方法
|
||||
|
||||
| 方法 | 返回值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `PathTo(Vector2 goal)` | `bool` | 寻路到目标位置 |
|
||||
| `PathTo(params Vector2[] goals)` | `bool` | 寻路到多个目标中最近的 |
|
||||
| `UpdatePath()` | `void` | 触发重新寻路(不改变目标) |
|
||||
| `SetRandomDestination()` | `bool` | 设置随机目标 |
|
||||
| `Stop()` | `void` | 优雅停止(完成当前链接后停止) |
|
||||
| `ForceStop()` | `void` | 立即强制停止 |
|
||||
| `CompleteSegmentTraversal()` | `void` | 通知已完成线段移动(由移动组件调用) |
|
||||
| `CompleteLinkTraversal()` | `void` | 通知已完成链接穿越(由移动组件调用) |
|
||||
| `WarpToNearestSegment()` | `void` | 传送到最近线段 |
|
||||
| `CanTraverseLink(int linkType)` | `bool` | 检查是否可穿越指定类型链接 |
|
||||
| `CanReach(Vector2 goal)` | `bool` | 同步检查目标是否可达 |
|
||||
| `CreatePathRequest()` | `PathRequest` | 创建自定义寻路请求 |
|
||||
|
||||
### 4.3 公共属性
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `CurrentStatus` | `NavAgentStatus` | 当前状态枚举 |
|
||||
| `IsIdle` | `bool` | 是否空闲 |
|
||||
| `IsFollowingAPath` | `bool` | 是否正在跟随路径 |
|
||||
| `IsOnLink` | `bool` | 是否正在链接上 |
|
||||
| `IsMovingOnSegment` | `bool` | 是否在线段上移动 |
|
||||
| `HasValidPosition` | `bool` | 当前位置是否有效(在线段上) |
|
||||
| `Height` | `float` | 代理高度 |
|
||||
| `MaxSlopeAngle` | `float` | 最大坡度角 |
|
||||
| `CurrentSegmentNormal` | `Vector2` | 当前线段法线 |
|
||||
| `PathGoal` | `Vector2` | 当前路径目标 |
|
||||
| `Position` | `Vector2` | 代理位置 |
|
||||
| `CurrentNavTagVector` | `int` | 当前位置的 NavTag |
|
||||
| `TimeOnLink` | `float` | 在当前链接上经过的时间 |
|
||||
|
||||
### 4.4 事件
|
||||
|
||||
| 事件 | 参数 | 触发时机 |
|
||||
|------|------|---------|
|
||||
| `OnStartLinkTraversal` | `NavAgent, PathSegment` | 开始穿越链接 |
|
||||
| `OnLinkTraversal` | `NavAgent, PathSegment, float t` | 链接穿越每帧回调(t: 0→1) |
|
||||
| `OnStartSegmentTraversal` | `NavAgent, PathSegment` | 开始线段移动 |
|
||||
| `OnSegmentTraversal` | `NavAgent, PathSegment, float t` | 线段移动每帧回调 |
|
||||
| `OnFailedToFindPath` | — | 寻路失败 |
|
||||
| `OnStop` | — | 停止移动 |
|
||||
| `OnReachedGoal` | — | 到达目标 |
|
||||
| `OnStartFollowingNewPath` | — | 开始跟随新路径 |
|
||||
|
||||
### 4.5 Inspector 配置
|
||||
|
||||
| 字段 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `height` | 1 | 代理高度(影响净空检查) |
|
||||
| `maxSlopeAngle` | 60 | 最大可行走坡度角 |
|
||||
| `autoRepathIntervall` | 1 | 自动重新寻路间隔(秒) |
|
||||
| `maximumDistanceToPathStart` | ∞ | 到路径起点的最大距离 |
|
||||
| `linkTraversalCostMultipliers[]` | — | 各链接类型代价乘数 |
|
||||
| `navTagTraversalCostMultipliers[]` | — | 各 NavTag 代价乘数 |
|
||||
| `allowCloseEnoughPath` | `false` | 允许"近似到达"路径 |
|
||||
| `enableDebugMessages` | `false` | 启用调试日志 |
|
||||
|
||||
---
|
||||
|
||||
## 5. NavSurface 详解
|
||||
|
||||
### 5.1 核心概念
|
||||
|
||||
NavSurface 是导航数据的来源。它扫描子对象上的 **Collider2D**,沿碰撞体表面生成导航线段,并在烘焙完成后将数据添加到全局 NavGraph。
|
||||
|
||||
关键特性:
|
||||
- **自动烘焙**:编辑器中或运行时均可烘焙
|
||||
- **移动平台**:烘焙数据以本地坐标存储,NavSurface Transform 移动时路径跟随
|
||||
- **动态加载**:`OnEnable` 添加到 NavGraph,`OnDisable` 移除
|
||||
|
||||
### 5.2 公共属性
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `WorldBounds` | `Rect` | 世界空间包围盒 |
|
||||
| `MaxClearance` | `float` | 最大净空检查高度 |
|
||||
| `MinClearance` | `float` | 最小净空(低于此值的线段被移除) |
|
||||
| `CellSize` | `float` | 净空检查精度 |
|
||||
| `ColliderMask` | `LayerMask` | 碰撞体过滤层 |
|
||||
| `TotalLineLength` | `float` | 所有线段总长度 |
|
||||
| `MaxSlopeAngle` | `float` | 最大坡度角 |
|
||||
| `LocalToWorldMatrix` | `Matrix4x4` | 本地到世界矩阵 |
|
||||
|
||||
### 5.3 公共方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `IEnumerator Bake()` | 运行时烘焙(协程) |
|
||||
| `Vector2 LocalToWorld(Vector2 pos)` | 本地坐标转世界坐标 |
|
||||
| `Vector2 WorldToLocal(Vector2 pos)` | 世界坐标转本地坐标 |
|
||||
|
||||
### 5.4 事件
|
||||
|
||||
| 事件 | 说明 |
|
||||
|------|------|
|
||||
| `OnBakingCompleted` | 烘焙完成 |
|
||||
| `OnReadyToPathfind` | 已添加到 NavGraph,可以开始寻路 |
|
||||
| `OnRemovedFromPathfinding` | 已从 NavGraph 移除 |
|
||||
|
||||
### 5.5 Inspector 烘焙配置
|
||||
|
||||
| 字段 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `maxClearance` | 1.8 | 障碍检查最大高度 |
|
||||
| `minClearance` | 0.1 | 净空低于此值的线段被移除 |
|
||||
| `cellSize` | 0.1 | 障碍检查精度(越小越精确但越慢) |
|
||||
| `includedColliders` | ~0 | 参与烘焙的层 |
|
||||
| `onlyStaticColliders` | false | 仅包含静态碰撞体 |
|
||||
| `maxSlopeAngle` | 180 | 移除超过此角度的线段 |
|
||||
| `smallestDistanceYouCareAbout` | 0.1 | RDP 线简化容差 |
|
||||
| `minSegmentLength` | 0.1 | 最小线段长度 |
|
||||
|
||||
### 5.6 使用示例
|
||||
|
||||
```
|
||||
场景层级结构:
|
||||
NavSurface (GameObject)
|
||||
├── Ground (BoxCollider2D)
|
||||
├── Platform_1 (BoxCollider2D)
|
||||
├── Platform_2 (PolygonCollider2D)
|
||||
└── Wall (BoxCollider2D)
|
||||
```
|
||||
|
||||
> **注意**: NavSurface 只扫描 **子对象** 上的 Collider2D,自身上的碰撞体不会被烘焙。
|
||||
|
||||
---
|
||||
|
||||
## 6. NavLink 与 NavLinkCluster
|
||||
|
||||
### 6.1 NavLink — 单个导航链接
|
||||
|
||||
NavLink 连接两个点,代表跳跃、下落、攀爬等需要特殊处理的路径段。
|
||||
|
||||
#### 可视化类型
|
||||
|
||||
| 类型 | 说明 |
|
||||
|------|------|
|
||||
| `Linear` | 直线 |
|
||||
| `QuadradticBezier` | 二次贝塞尔曲线 |
|
||||
| `Projectile` | 抛物线 |
|
||||
| `Teleport` | 传送(瞬移) |
|
||||
| `None` | 无可视化 |
|
||||
| `TransformBasedMovement` | 使用 TransformBasedMovement 的默认处理 |
|
||||
|
||||
#### 公共属性
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `GoalWorldPosition` | `Vector2` | 目标世界位置(可读写) |
|
||||
| `StartWorldPosition` | `Vector2` | 起点世界位置(可读写) |
|
||||
| `IsBidirectional` | `bool` | 是否双向 |
|
||||
| `IsAddedToWorld` | `bool` | 是否已添加到 NavGraph |
|
||||
|
||||
#### 公共方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `UpdateMapping()` | 位置改变后更新链接映射 |
|
||||
| `SetStartToGoalLinkTraversable(bool)` | 设置正向可穿越性 |
|
||||
| `SetGoalToStartLinkTraversable(bool)` | 设置反向可穿越性 |
|
||||
|
||||
#### 共有基类属性 (BaseNavLink)
|
||||
|
||||
| 属性 | 说明 |
|
||||
|------|------|
|
||||
| `LinkType` | 链接类型索引(corner=0, jump=1, fall=2, teleport=3, climb=4, elevator=5) |
|
||||
| `Clearance` | 允许通过的最大代理高度 |
|
||||
| `AvgWaitTime` | 平均等待时间(影响寻路代价) |
|
||||
| `CostOverride` | 代价覆盖(≤0 使用距离计算) |
|
||||
| `NavTag` | NavTag 标记 |
|
||||
| `MaxTraversableDistance` | 最大可穿越距离 |
|
||||
| `autoMap` | 自动映射到 NavGraph |
|
||||
|
||||
### 6.2 NavLinkCluster — 链接集群
|
||||
|
||||
NavLinkCluster 适用于多点互联场景,如 **电梯**(多层站点)、**梯子**(多个停靠点)。
|
||||
|
||||
内部为每对点自动生成一个链接。
|
||||
|
||||
#### LinkPoint 结构
|
||||
|
||||
```csharp
|
||||
struct LinkPoint {
|
||||
Vector2 point;
|
||||
PointTraversalType traversalType; // Exit | Entry | Both
|
||||
}
|
||||
```
|
||||
|
||||
#### 公共方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `UpdateMapping()` | 更新所有链接映射 |
|
||||
| `SetLinksTraversable(Func<Vector2, Vector2, bool>)` | 根据起终点函数设置各链接可穿越性 |
|
||||
|
||||
#### 使用场景
|
||||
|
||||
```csharp
|
||||
// 电梯到达某层时,只允许穿越与该层相关的链接
|
||||
navLinkCluster.SetLinksTraversable((start, goal) =>
|
||||
Mathf.Abs(start.y - currentFloor.position.y) < 0.1f ||
|
||||
Mathf.Abs(goal.y - currentFloor.position.y) < 0.1f);
|
||||
|
||||
// 离开时禁用所有链接
|
||||
navLinkCluster.SetLinksTraversable((start, goal) => false);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 辅助组件
|
||||
|
||||
### 7.1 NavAreaMarker — 区域标记
|
||||
|
||||
**要求**: `RectTransform`
|
||||
|
||||
将矩形区域内的所有导航线段标记为指定 NavTag。常用于标记水域、危险区域、门区域等。
|
||||
|
||||
| 属性/方法 | 说明 |
|
||||
|-----------|------|
|
||||
| `NavTag` | NavTag 索引 |
|
||||
| `MarkerColor` | 标记颜色 |
|
||||
| `updateAfterTimeOfNoMovement` | 停止移动后更新延迟 |
|
||||
| `updateAfterTime` | 最大更新间隔 |
|
||||
| `UpdateMappings()` | 手动更新映射 |
|
||||
|
||||
**门控模式**: 通过启用/禁用 NavAreaMarker 来控制门的通行:
|
||||
```csharp
|
||||
// 开门 → 禁用标记 → 代理可通过
|
||||
navAreaMarker.enabled = false;
|
||||
// 关门 → 启用标记 → 区域被标记为不可通行 NavTag
|
||||
navAreaMarker.enabled = true;
|
||||
```
|
||||
|
||||
### 7.2 NavSegmentSubstractor — 线段裁剪器
|
||||
|
||||
**要求**: `RectTransform`
|
||||
|
||||
在烘焙时从指定矩形区域移除线段。支持角度过滤:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `fromAngle` | 最小角度过滤 |
|
||||
| `toAngle` | 最大角度过滤 |
|
||||
|
||||
### 7.3 DynamicObstacle — 动态障碍物
|
||||
|
||||
空标记组件。附加到 GameObject 上,使其在 NavSurface 烘焙时被忽略。
|
||||
|
||||
### 7.4 IVelocityProvider — 速度接口
|
||||
|
||||
```csharp
|
||||
interface IVelocityProvider {
|
||||
Vector2 WorldVelocity { get; }
|
||||
}
|
||||
```
|
||||
|
||||
用于 `Follower` 组件的目标预测功能。在跟随目标上实现此接口即可。
|
||||
|
||||
---
|
||||
|
||||
## 8. 全局系统 — PBWorld 与 Settings
|
||||
|
||||
### 8.1 PBWorld — 全局单例
|
||||
|
||||
自动实例化,`DontDestroyOnLoad`。管理全局 NavGraph 和寻路调度。
|
||||
|
||||
#### 静态 API
|
||||
|
||||
```csharp
|
||||
// === 点映射 ===
|
||||
// 映射世界点到最近导航位置
|
||||
static bool TryMapPoint(Vector2 position, out NavSegmentPositionPointer pointer)
|
||||
// 自定义搜索半径
|
||||
static bool TryMapPoint(Vector2 position, float searchRadius, out NavSegmentPositionPointer pointer)
|
||||
// 确保映射位置对代理可通行
|
||||
static bool TryMapPoint(Vector2 position, float searchRadius, NavAgent agent, out NavSegmentPositionPointer pointer)
|
||||
|
||||
// === 随机 ===
|
||||
static Vector2 GetRandomPointOnGraph() // 返回图上随机点
|
||||
|
||||
// === 寻路 ===
|
||||
static void PathTo(PathRequest pathRequest) // 入队异步寻路请求
|
||||
|
||||
// === 空间查询 ===
|
||||
static List<NavSubsegmentPointer> BoxCast(Rect rect, float rotation, float fromAngle, float toAngle)
|
||||
|
||||
// === 图变化监听 ===
|
||||
static INavGraphChangeSource NavGraphChangeSource // 订阅 OnGraphChange 事件
|
||||
```
|
||||
|
||||
#### NavGraphChange 事件枚举
|
||||
|
||||
| 值 | 说明 |
|
||||
|----|------|
|
||||
| `NavSurfaceAdded` | NavSurface 添加 |
|
||||
| `NavSurfaceRemoved` | NavSurface 移除 |
|
||||
| `NavLinkAdded` | NavLink 添加 |
|
||||
| `NavLinkRemoved` | NavLink 移除 |
|
||||
| `SegmentModifierAdded` | 线段修改器添加 |
|
||||
| `SegmentModifierRemoved` | 线段修改器移除 |
|
||||
| `NavLinkMoved` | NavLink 位置变更 |
|
||||
|
||||
### 8.2 NavSegmentPositionPointer
|
||||
|
||||
导航位置指针结构体,用于表示一个在导航图上的有效位置:
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `Position` | `Vector2` | 世界位置 |
|
||||
| `Normal` | `Vector2` | 法线 |
|
||||
| `IsValid()` | `bool` | 是否有效 |
|
||||
| `IsInvalid()` | `bool` | 是否无效 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 路径与寻路请求
|
||||
|
||||
### 9.1 PathRequest — 寻路请求
|
||||
|
||||
| 枚举 | 值 |
|
||||
|------|-----|
|
||||
| **RequestState** | `Draft`, `Pending`, `Finished`, `Failed` |
|
||||
| **RequestFailReason** | `CouldntMapStart`, `CouldntMapGoal`, `MappedStartChanged`, `AllMappedGoalsChanged`, `NoPathFromStartToGoal`, `WorldWasDestroyed`, `ToFarFromStart` |
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `Status` | `RequestState` | 请求状态 |
|
||||
| `FailReason` | `RequestFailReason` | 失败原因 |
|
||||
| `Path` | `Path` | 计算出的路径 |
|
||||
| `start` | `NavSegmentPositionPointer` | 起点 |
|
||||
| `goals` | `IList<NavSegmentPositionPointer>` | 目标列表 |
|
||||
| `client` | `NavAgent` | 请求代理 |
|
||||
|
||||
### 9.2 Path — 路径
|
||||
|
||||
| 属性/方法 | 说明 |
|
||||
|-----------|------|
|
||||
| `Current` | 当前路径片段 (`PathSegment`) |
|
||||
| `NextSegment` | 下一个片段 |
|
||||
| `HasNext` | 是否有下一个 |
|
||||
| `Goal` | 目标位置 |
|
||||
| `Start` | 起点位置 |
|
||||
| `totalCosts` | 总代价 |
|
||||
| `MoveNext()` | 前进到下一片段 |
|
||||
| `AllPathPoints()` | 获取所有路径点列表 |
|
||||
| `RemainingPathPoints()` | 获取剩余路径点列表 |
|
||||
|
||||
### 9.3 PathSegment — 路径片段
|
||||
|
||||
| 属性/方法 | 说明 |
|
||||
|-----------|------|
|
||||
| `Next` | 下一个片段 |
|
||||
| `LinkStart` | 链接起点(世界坐标,随 NavSurface 移动) |
|
||||
| `LinkEnd` | 链接终点 |
|
||||
| `Normal` | 线段法线 |
|
||||
| `Tangent` | 线段切线 |
|
||||
| `Point` | 线段上的参考点 |
|
||||
| `link` | 关联的链接实例 |
|
||||
| `owner` | 所属 NavSurface |
|
||||
| `GetTagVector(float t)` | 获取位置的 NavTag |
|
||||
| `DistanceAlongSegment(Vector2 pos)` | 投影距离 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 预置行为脚本
|
||||
|
||||
PathBerserker2d 提供 8 个即用的 NavAgent 行为脚本:
|
||||
|
||||
| 组件 | 说明 | 典型用途 |
|
||||
|------|------|---------|
|
||||
| **MouseWalker** | 鼠标点击处寻路 | 调试、点击移动原型 |
|
||||
| **PatrolWalker** | 按顺序巡逻路线,循环 | 巡逻敌人 |
|
||||
| **RandomWalker** | 持续随机行走 | NPC 闲逛 |
|
||||
| **MultiGoalWalker** | 寻路到多个目标中最近的一个 | 收集物搜索 |
|
||||
| **Follower** | 跟随目标 Transform,支持目标预测 | 追踪型敌人 |
|
||||
| **KeepGrounded** | 通过 Raycast 检测移动平台并 parent | 移动平台上的代理 |
|
||||
| **AdjustRotation** | 旋转代理匹配线段法线方向 | 沿曲面行走的方向适配 |
|
||||
| **FootStepSounds** | 根据 NavTag 播放不同脚步声 | 环境音效 |
|
||||
|
||||
### 使用方式
|
||||
|
||||
直接将这些脚本添加到带有 NavAgent 的 GameObject 上即可。它们自动获取同 GameObject 上的 NavAgent 引用。
|
||||
|
||||
---
|
||||
|
||||
## 11. TransformBasedMovement 移动系统
|
||||
|
||||
### 11.1 概述
|
||||
|
||||
`TransformBasedMovement` 是 PathBerserker2d 提供的默认移动实现。它订阅 NavAgent 的事件,在回调中直接修改 Transform 位置,并在完成后调用 `CompleteSegmentTraversal()` / `CompleteLinkTraversal()` 通知 NavAgent。
|
||||
|
||||
### 11.2 FeatureFlags
|
||||
|
||||
```csharp
|
||||
[Flags] enum FeatureFlags {
|
||||
SegmentMovement = 1, // 线段移动
|
||||
JumpLinks = 2, // 跳跃链接
|
||||
CornerLinks = 4, // 拐角链接
|
||||
FallLinks = 8, // 下落链接
|
||||
TeleportLinks = 16, // 传送链接
|
||||
ClimbLinks = 32, // 攀爬链接
|
||||
ElevatorLinks = 64, // 电梯链接
|
||||
OtherLinks = 128, // 其他链接
|
||||
}
|
||||
```
|
||||
|
||||
### 11.3 配置属性
|
||||
|
||||
| 字段 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `movementSpeed` | 5 | 线段移动速度 (unit/s) |
|
||||
| `cornerSpeed` | 100 | 拐角链接速度 (degrees/s) |
|
||||
| `jumpSpeed` | 5 | 跳跃速度 (unit/s) |
|
||||
| `fallSpeed` | 5 | 下落速度 (unit/s) |
|
||||
| `climbSpeed` | 5 | 攀爬速度 (unit/s) |
|
||||
| `enableAgentRotation` | true | 是否旋转代理朝向移动方向 |
|
||||
| `enabledFeatures` | 全部启用 | 控制由本组件处理的功能 |
|
||||
|
||||
### 11.4 自定义移动组件
|
||||
|
||||
如果 `TransformBasedMovement` 不满足需求(例如需要物理驱动移动),可以创建自定义移动组件:
|
||||
|
||||
```csharp
|
||||
public class CustomMovement : MonoBehaviour
|
||||
{
|
||||
NavAgent navAgent;
|
||||
|
||||
void Awake() => navAgent = GetComponent<NavAgent>();
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
navAgent.OnStartSegmentTraversal += HandleSegmentStart;
|
||||
navAgent.OnSegmentTraversal += HandleSegmentMove;
|
||||
navAgent.OnStartLinkTraversal += HandleLinkStart;
|
||||
navAgent.OnLinkTraversal += HandleLinkMove;
|
||||
}
|
||||
|
||||
void HandleSegmentMove(NavAgent agent, PathSegment seg, float t)
|
||||
{
|
||||
// 自定义线段移动逻辑
|
||||
// ...
|
||||
// 完成后:
|
||||
agent.CompleteSegmentTraversal();
|
||||
}
|
||||
|
||||
void HandleLinkMove(NavAgent agent, PathSegment seg, float t)
|
||||
{
|
||||
// 自定义链接穿越逻辑
|
||||
// ...
|
||||
// 完成后:
|
||||
agent.CompleteLinkTraversal();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Demo 场景与使用模式
|
||||
|
||||
### 12.1 Demo 场景一览
|
||||
|
||||
| 场景 | 展示内容 |
|
||||
|------|---------|
|
||||
| `breakable_wall` | 可破坏墙壁 + 寻路 |
|
||||
| `door` | NavAreaMarker 门控 |
|
||||
| `elevator` | NavLinkCluster 电梯系统 |
|
||||
| `follow_target` | Follower 跟随行为 |
|
||||
| `ladder` | 攀爬链接 |
|
||||
| `moving_platform` | 移动平台导航 |
|
||||
| `multi_surface` | 多 NavSurface 场景 |
|
||||
| `procedural_endless_jumper` | 运行时烘焙 |
|
||||
| `runtime_bake` | 运行时动态烘焙 |
|
||||
| `simple_patrol` | 基础巡逻 |
|
||||
| `teleport` | 传送链接 |
|
||||
| `threesixtydegree` | 360° 全向导航 |
|
||||
| `trafficlight` | 交通灯 + 等待逻辑 |
|
||||
| `tricky_jump` | 复杂跳跃场景 |
|
||||
|
||||
### 12.2 常用使用模式
|
||||
|
||||
#### 模式 1:最简单的寻路
|
||||
|
||||
```csharp
|
||||
// GoalWalker 模式:检查距离 + IsIdle → PathTo
|
||||
void Update()
|
||||
{
|
||||
if (Vector2.Distance(goal.position, navAgent.transform.position) > 0.5f
|
||||
&& (navAgent.IsIdle || goal.hasChanged))
|
||||
{
|
||||
goal.hasChanged = false;
|
||||
navAgent.PathTo(goal.position);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 模式 2:电梯 — 动态链接切换
|
||||
|
||||
```csharp
|
||||
// 电梯到达某层 → 启用相关链接
|
||||
navLinkCluster.SetLinksTraversable((start, goal) =>
|
||||
Mathf.Abs(start.y - levels[nextLevel].position.y) < 0.1f ||
|
||||
Mathf.Abs(goal.y - levels[nextLevel].position.y) < 0.1f);
|
||||
|
||||
// 电梯离开 → 禁用所有链接
|
||||
navLinkCluster.SetLinksTraversable((start, goal) => false);
|
||||
```
|
||||
|
||||
#### 模式 3:门 — NavAreaMarker 控制
|
||||
|
||||
```csharp
|
||||
// 开门:禁用 NavAreaMarker → 区域不再有阻挡标记
|
||||
navAreaMarker.enabled = false;
|
||||
|
||||
// 关门:启用 NavAreaMarker → 门区域被标记为不可通行 NavTag
|
||||
navAreaMarker.enabled = true;
|
||||
```
|
||||
|
||||
#### 模式 4:移动平台
|
||||
|
||||
```csharp
|
||||
// MovingPlatform 是 NavSurface 的子对象
|
||||
// 只需移动 NavSurface 的 Transform,导航数据自动跟随
|
||||
transform.Translate(velocity * Time.deltaTime);
|
||||
```
|
||||
|
||||
#### 模式 5:运行时烘焙
|
||||
|
||||
```csharp
|
||||
// 在运行时触发烘焙
|
||||
StartCoroutine(navSurface.Bake());
|
||||
|
||||
// 监听烘焙完成
|
||||
navSurface.OnBakingCompleted += () => Debug.Log("Bake done!");
|
||||
navSurface.OnReadyToPathfind += () => Debug.Log("Ready for pathfinding!");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 13. Corgi 引擎集成
|
||||
|
||||
PathBerserker2d 附带 **CorgiBasedMovement** 组件,替代 `TransformBasedMovement` 以对接 Corgi Engine 的角色控制系统。
|
||||
|
||||
### 13.1 核心映射
|
||||
|
||||
| 导航行为 | Corgi 映射 |
|
||||
|---------|-----------|
|
||||
| 线段移动 | `CharacterHorizontalMovement.SetHorizontalMove()` |
|
||||
| 跳跃链接 | `CharacterJump.JumpStart()` |
|
||||
| 攀爬链接 | `CharacterLadder`(反射调用) |
|
||||
| 传送链接 | 直接设置 Position |
|
||||
| 拐角链接 | 直接 `CompleteLinkTraversal()` |
|
||||
| 卡住检测 | `fallbackTeleportDelay` 秒后传送 |
|
||||
|
||||
### 13.2 Corgi AI Actions
|
||||
|
||||
| 脚本 | 说明 |
|
||||
|------|------|
|
||||
| `AIActionPBMoveTowardsTarget` | 移向目标 |
|
||||
| `AIActionPBMoveTowardsClosestTarget` | 移向最近目标 |
|
||||
| `AIActionPBMoveTowardsRandomPathableTarget` | 移向可达随机目标 |
|
||||
| `AIActionPBPatrol` | 巡逻 |
|
||||
| `AIDecisionPBHasReachedGoal` | 判断是否到达 |
|
||||
| `AIDecisionPBPathfindingFailed` | 判断寻路失败 |
|
||||
|
||||
> **注意**: 本项目使用自研架构而非 Corgi Engine,此集成仅作参考。
|
||||
|
||||
---
|
||||
|
||||
## 14. 全局设置参考
|
||||
|
||||
**位置**: `Edit > Project Settings > PathBerserker2d`
|
||||
**资源文件**: `Assets/PathBerserker2d/Resources/PathBerserker2dSettings`
|
||||
|
||||
### 14.1 设置项
|
||||
|
||||
| 属性 | 类型 | 当前值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `PointMappingDistance` | float | 0.2 | 点映射最大距离(性能关键,应尽量小) |
|
||||
| `PathfinderThreadCount` | int | 4 | 寻路线程数(WebGL 强制 = 1) |
|
||||
| `InitiateUpdateInterval` | float | 0.1s | NavGraph 更新间隔 |
|
||||
| `DrawGraphWhilePlaying` | bool | — | 运行时绘制导航图 |
|
||||
| `NavSurfaceLineWidth` | float | — | 线宽 |
|
||||
| `UsePolygonCollider2dPathsForBaking` | bool | — | 多边形碰撞体烘焙方式 |
|
||||
|
||||
### 14.2 内置链接类型
|
||||
|
||||
| 索引 | 名称 | 说明 |
|
||||
|------|------|------|
|
||||
| 0 | `corner` | 拐角(自动生成) |
|
||||
| 1 | `jump` | 跳跃 |
|
||||
| 2 | `fall` | 下落 |
|
||||
| 3 | `teleport` | 传送 |
|
||||
| 4 | `climb` | 攀爬 |
|
||||
| 5 | `elevator` | 电梯 |
|
||||
|
||||
### 14.3 内置 NavTag
|
||||
|
||||
| 名称 | 用途 |
|
||||
|------|------|
|
||||
| `default` | 默认 |
|
||||
| `water` | 水域 |
|
||||
| `lava` | 岩浆 |
|
||||
| `grass` | 草地 |
|
||||
| `concrete` | 混凝土 |
|
||||
| `dirt` | 泥土 |
|
||||
|
||||
---
|
||||
|
||||
## 15. 与 BaseGames 架构集成方案
|
||||
|
||||
### 15.1 防腐层原则
|
||||
|
||||
根据 BaseGames 架构总纲(`00_Architecture_Overview.md`)第 3 条设计理念:
|
||||
|
||||
> **防腐层隔离**: 业务代码永远不直接调用 PathBerserker2d 等第三方 API。所有第三方交互通过 `Adapters/` 层代理。
|
||||
|
||||
因此 PathBerserker2d 的所有功能必须通过 **适配器层** 暴露给业务系统。
|
||||
|
||||
### 15.2 适配器设计
|
||||
|
||||
```
|
||||
Layer 3 — Adapters/
|
||||
└── NavAgentAdapter.cs # PathBerserker2d NavAgent 的防腐包装
|
||||
|
||||
业务层调用链:
|
||||
EnemyAI (Layer 5) → NavAgentAdapter (Layer 3) → NavAgent (第三方)
|
||||
```
|
||||
|
||||
#### NavAgentAdapter 接口建议
|
||||
|
||||
```csharp
|
||||
namespace BaseGames.Adapters
|
||||
{
|
||||
/// <summary>
|
||||
/// PathBerserker2d NavAgent 的防腐层适配器。
|
||||
/// 业务代码通过此接口进行寻路,不直接引用 PathBerserker2d API。
|
||||
/// </summary>
|
||||
public interface INavigation
|
||||
{
|
||||
bool PathTo(Vector2 goal);
|
||||
bool PathTo(params Vector2[] goals);
|
||||
void Stop();
|
||||
void ForceStop();
|
||||
bool IsIdle { get; }
|
||||
bool IsFollowingPath { get; }
|
||||
bool CanReach(Vector2 goal);
|
||||
Vector2 CurrentPosition { get; }
|
||||
|
||||
event Action OnReachedGoal;
|
||||
event Action OnPathFailed;
|
||||
event Action OnStopped;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 15.3 与其他系统的集成点
|
||||
|
||||
| BaseGames 系统 | 集成方式 |
|
||||
|----------------|---------|
|
||||
| **敌人 AI (20_Enemy_AI)** | 行为树通过 `INavigation` 接口调用寻路 |
|
||||
| **环境系统 (11_Environment)** | MovingPlatform 使用 NavSurface 子对象模式 |
|
||||
| **环境系统 (11_Environment)** | BreakableWall 销毁后触发 NavSurface 重新烘焙 |
|
||||
| **环境系统 (11_Environment)** | TeleportGate 对应 NavLink(teleport 类型) |
|
||||
| **物理动画 (08_Physics)** | 自定义移动组件桥接 `IPhysicsBody` 与 NavAgent 事件 |
|
||||
| **标签系统 (02_GameTag)** | NavTag 可映射为 GameTagSO 用于环境效果触发 |
|
||||
|
||||
### 15.4 自定义移动组件集成
|
||||
|
||||
由于 BaseGames 使用自研物理系统 (`IPhysicsBody` / `RaycastBody2D`),不应使用默认的 `TransformBasedMovement`。需要创建桥接移动组件:
|
||||
|
||||
```csharp
|
||||
namespace BaseGames.Adapters
|
||||
{
|
||||
public class PhysicsBasedNavMovement : MonoBehaviour
|
||||
{
|
||||
// 订阅 NavAgent 事件
|
||||
// 将移动指令转发给 IPhysicsBody
|
||||
// 通过 IPhysicsBody 驱动实际移动
|
||||
// 移动完成后调用 CompleteSegmentTraversal() / CompleteLinkTraversal()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 16. 性能分析与优化建议
|
||||
|
||||
### 16.1 性能特性
|
||||
|
||||
| 方面 | 评估 |
|
||||
|------|------|
|
||||
| **寻路** | 多线程 A\*,不阻塞主线程。线程数可配置(默认 4) |
|
||||
| **烘焙** | 协程方式,可分帧执行。复杂场景烘焙较重 |
|
||||
| **内存** | 线段数据以本地坐标存储,紧凑 |
|
||||
| **空间索引** | B2DynamicTree(Box2D 动态树),查询高效 |
|
||||
| **WebGL** | 自动降级为单线程协程模式 |
|
||||
|
||||
### 16.2 优化建议
|
||||
|
||||
| 建议 | 说明 |
|
||||
|------|------|
|
||||
| **减小 `PointMappingDistance`** | 当前 0.2,越小映射查询越快 |
|
||||
| **调整 `autoRepathIntervall`** | 不要过于频繁,1-2 秒通常足够 |
|
||||
| **合理设置 `cellSize`** | 烘焙精度越高越慢,0.1 适合大多数情况 |
|
||||
| **分区管理 NavSurface** | 按房间/区域拆分 NavSurface,按需启用/禁用 |
|
||||
| **限制同时寻路数** | 大量代理时排队处理,避免请求洪峰 |
|
||||
| **善用 `onlyStaticColliders`** | 排除动态碰撞体可加速烘焙 |
|
||||
|
||||
---
|
||||
|
||||
## 17. 优缺点总结
|
||||
|
||||
### 17.1 优势
|
||||
|
||||
| 优势 | 说明 |
|
||||
|------|------|
|
||||
| **原生 2D 支持** | 基于线段而非面片,天然适合 2D 横版游戏 |
|
||||
| **完整源码** | 所有代码可见,便于调试和定制 |
|
||||
| **链接系统丰富** | 内置 6 种链接类型,覆盖跳跃/下落/攀爬/电梯/传送/拐角 |
|
||||
| **动态世界** | 运行时烘焙、移动平台、动态链接切换 |
|
||||
| **多线程寻路** | 不阻塞主线程,支持多代理并发 |
|
||||
| **事件驱动架构** | 移动实现完全解耦,易于替换为自定义移动系统 |
|
||||
| **预置行为丰富** | 8 个即用行为脚本覆盖常见场景 |
|
||||
| **移动平台支持** | 本地坐标体系自动跟随 NavSurface Transform 变化 |
|
||||
|
||||
### 17.2 不足
|
||||
|
||||
| 不足 | 说明 |
|
||||
|------|------|
|
||||
| **文档较简陋** | 官方文档以 Demo 为主,缺少系统性 API 文档 |
|
||||
| **无官方维护迹象** | Asset Store 版本更新不频繁 |
|
||||
| **烘焙仅支持 Collider2D** | 不支持 Tilemap 直接烘焙(需转换为 Collider) |
|
||||
| **链接需手动放置** | 跳跃/攀爬等链接需要手动在编辑器中放置 |
|
||||
| **360° 模式限制** | 虽支持 360° 但主要为横版优化 |
|
||||
| **缺少动态避障** | 无 RVO / ORCA 等动态避障实现 |
|
||||
| **NavTag 数量有限** | 内置 6 个 NavTag,扩展需修改设置 |
|
||||
|
||||
### 17.3 技术评分
|
||||
|
||||
| 维度 | 评分 (1-10) | 说明 |
|
||||
|------|-------------|------|
|
||||
| 2D 适配度 | **9** | 专为 2D 设计,线段导航非常贴合横版场景 |
|
||||
| API 设计 | **8** | 事件驱动架构清晰,易于扩展 |
|
||||
| 文档质量 | **5** | Demo 丰富但系统文档不足 |
|
||||
| 性能 | **8** | 多线程寻路 + B2DynamicTree 空间索引 |
|
||||
| 易用性 | **7** | 基础使用简单,高级功能学习曲线稍陡 |
|
||||
| 可维护性 | **7** | 完整源码可见,但注释较少 |
|
||||
| 扩展性 | **8** | 事件驱动 + 接口隔离,易接入自定义系统 |
|
||||
| **综合** | **7.4** | 适合 2D Metroidvania 项目的可靠寻路方案 |
|
||||
|
||||
---
|
||||
|
||||
## 18. 总结与建议
|
||||
|
||||
### 18.1 总体评价
|
||||
|
||||
PathBerserker2d 是目前 Unity 生态中少有的 **专为 2D 横版设计** 的寻路方案。其基于线段的导航模型天然适配平台跳跃类游戏,多线程异步寻路保证了性能,事件驱动架构提供了良好的扩展性。
|
||||
|
||||
对于 BaseGames 这样的 2D Metroidvania 项目,它提供了:
|
||||
- **完整的 2D 导航能力**:地面行走、跳跃、下落、攀爬、电梯、传送
|
||||
- **移动平台支持**:天然适配关卡中的动态地形
|
||||
- **与自定义物理系统的解耦**:通过事件驱动架构可桥接 `IPhysicsBody`
|
||||
|
||||
### 18.2 集成建议
|
||||
|
||||
1. **严格通过适配器层访问** — 遵循 BaseGames 防腐层原则,创建 `NavAgentAdapter` / `INavigation` 接口
|
||||
2. **替换 TransformBasedMovement** — 创建 `PhysicsBasedNavMovement` 桥接 `IPhysicsBody`
|
||||
3. **按房间分区 NavSurface** — 配合关卡加载系统,按需启用/禁用导航区域
|
||||
4. **NavTag 映射 GameTagSO** — 统一标签体系,复用效果系统触发逻辑
|
||||
5. **行为树集成** — 通过 `INavigation` 接口在行为树节点中调用寻路
|
||||
|
||||
### 18.3 注意事项
|
||||
|
||||
- 可破坏墙壁销毁后需触发 NavSurface 重新烘焙
|
||||
- 电梯/升降台需使用 NavLinkCluster 配合动态可穿越性控制
|
||||
- 大量 AI 代理场景需排队管理寻路请求避免性能瓶颈
|
||||
- WebGL 构建时自动降级为单线程,需额外测试性能
|
||||
Reference in New Issue
Block a user