Files
zeling_v2/Docs/Architecture/10_UIModule.md
2026-05-08 11:04:00 +08:00

695 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 10 · UI 模块
> **命名空间** `BaseGames.UI`
> **程序集** `BaseGames.UI`
> **路径** `Assets/Scripts/UI/`
> **依赖** `BaseGames.Core.Events`、`TextMeshPro`
---
## 目录
1. [Canvas 架构Persistent 场景内)](#1-canvas-架构)
2. [UIManager](#2-uimanager)
3. [HUDController](#3-hudcontroller)
4. [BossHPBar](#4-bosshpbar)
5. [PauseMenuController](#5-pausemenucontroller)
6. [DeathScreenController](#6-deathscreencontroller)
7. [SettingsPanelController](#7-settingspanelcontroller)
8. [SaveSlotController](#75-saveslotcontroller)
9. [SaveIndicator](#76-saveindicator)
10. [LoadingScreenManager](#77-loadingscreenmanager)
11. [IBossHPProvider 接口](#78-ibosshpprovider-接口)
12. [LoadingOverlay](#8-loadingoverlay)
13. [DialogueBoxHUD Overlay](#9-dialoguebox)
10. [FloatingDamageText伤害数字](#10-floatingdamagetext)
11. [ToastNotification通知弹窗](#11-toastnotification)
12. [InputDeviceIconSwitcher](#12-inputdeviceiconswitcher)
13. [PanelStack控制器导航](#13-panelstack)
14. [UI 事件频道清单](#14-ui-事件频道清单)
---
## 1. Canvas 架构
所有 Canvas 挂在 **Persistent 场景**内,全程常驻:
```
[UIRoot]
├── Canvas_HUD Sorting Order: 10 (Screen Space - Overlay)
│ ├── HPContainer ← HP 格子列表(水平 HorizontalLayoutGroup
│ ├── SoulGauge ← 灵力弧形进度条Image.fillAmount
│ ├── SpiritGauge ← 魄元进度条
│ ├── GeoCounter ← TMP 数字 + 图标
│ ├── SpringCharges ← 灵泉次数图标列
│ ├── FormIndicator ← 当前形态图标3 种形态)
│ ├── ToolSlotHUD ← 工具槽图标 + 次数
│ ├── AbilityHints ← 已解锁技能图标
│ ├── BossHPBar ← 默认隐藏
│ └── InteractPrompt ← 交互提示文字(默认隐藏)
├── Canvas_Menu Sorting Order: 20 (Screen Space - Overlay)
│ ├── MainMenuPanel ← 主菜单(游戏启动时显示)
│ ├── SaveSlotPanel ← 存档槽选择(新游戏/继续,主菜单子面板)
│ ├── PauseMenuPanel ← 暂停菜单(默认隐藏)
│ ├── DeathScreenPanel ← 死亡画面(默认隐藏)
│ ├── SettingsPanel ← 设置菜单(默认隐藏)
│ ├── MapPanel ← 地图(默认隐藏)
│ └── ShopPanel ← 商店(默认隐藏)
└── Canvas_Overlay Sorting Order: 30 (Screen Space - Overlay)
├── LoadingOverlay ← 全屏黑幕(过场遮罩)
├── DialogueBox ← 对话框(底部)
└── ToastContainer ← 通知弹窗容器(右上)
```
**SaveSlotPanel** 展示 3 个存档槽卡片,每张卡片显示角色形态图标、区域名称、游玩时长、存档时间、完成度百分比;由 `SaveSlotController` 驱动(`SaveManager.GetSlotSummaryAsync(slotIndex)` 提供数据)。
**SaveIndicator**:右下角小图标(软盘 + 旋转动画),在自动存档流程中显示(订阅 `EVT_SaveIndicatorVisible` BoolEventChannelSO`true` 触发淡入,`false` 触发淡出),告知玩家正在保存中。
---
## 2. UIManager
```csharp
// 路径: Assets/Scripts/UI/UIManager.cs
[DefaultExecutionOrder(+50)]
public class UIManager : MonoBehaviour
{
[Header("Canvas Roots")]
[SerializeField] private GameObject _hudRoot;
[SerializeField] private GameObject _pauseMenuRoot;
[SerializeField] private GameObject _deathScreenRoot;
[SerializeField] private GameObject _settingsRoot;
[SerializeField] private GameObject _mapRoot;
[SerializeField] private GameObject _shopRoot;
[Header("Event Channels - Subscribe")]
[SerializeField] private GameStateEventChannelSO _onGameStateChanged;
[SerializeField] private VoidEventChannelSO _onPauseRequested;
[SerializeField] private VoidEventChannelSO _onFastTravelOpen;
[SerializeField] private StringEventChannelSO _onShopOpen;
[SerializeField] private VoidEventChannelSO _onMapOpen;
private Stack<GameObject> _panelStack = new();
private void OnEnable()
{
_onGameStateChanged.OnEventRaised += HandleGameStateChanged;
_onPauseRequested.OnEventRaised += TogglePause;
_onFastTravelOpen.OnEventRaised += OpenMap;
_onShopOpen.OnEventRaised += OpenShop;
_onMapOpen.OnEventRaised += OpenMap;
}
private void OnDisable()
{
_onGameStateChanged.OnEventRaised -= HandleGameStateChanged;
_onPauseRequested.OnEventRaised -= TogglePause;
_onFastTravelOpen.OnEventRaised -= OpenMap;
_onShopOpen.OnEventRaised -= OpenShop;
_onMapOpen.OnEventRaised -= OpenMap;
}
private void HandleGameStateChanged(GameStateId state)
{
// HUD 在 Gameplay 和 BossFight 状态下均显示
bool showHud = state == GameStates.Gameplay || state == GameStates.BossFight;
_hudRoot.SetActive(showHud);
// ⚠️ GameStateId 为 struct不可用 switch用 if/else 比较
if (state == GameStates.Dead)
_deathScreenRoot.SetActive(true);
else if (state == GameStates.Cutscene)
_hudRoot.SetActive(false); // 过场动画隐藏 HUD
}
public void OpenPanel(GameObject panel)
{
if (_panelStack.Count > 0) _panelStack.Peek().SetActive(false);
panel.SetActive(true);
_panelStack.Push(panel);
}
public void CloseTopPanel()
{
if (_panelStack.Count == 0) return;
_panelStack.Pop().SetActive(false);
if (_panelStack.Count > 0) _panelStack.Peek().SetActive(true);
}
private void TogglePause() => OpenPanel(_pauseMenuRoot);
private void OpenShop(string npcId) => OpenPanel(_shopRoot);
private void OpenMap() => OpenPanel(_mapRoot);
}
```
---
## 3. HUDController
```csharp
// 路径: Assets/Scripts/UI/HUD/HUDController.cs
public class HUDController : MonoBehaviour
{
[Header("HP")]
[SerializeField] private Transform _hpContainer;
[SerializeField] private GameObject _hpCellPrefab; // 单格 HP 图标
[Header("Gauges")]
[SerializeField] private Image _soulGaugeFill;
[SerializeField] private Image _spiritGaugeFill;
[SerializeField] private TMP_Text _geoText;
[Header("Spring Charges")]
[SerializeField] private Transform _springContainer;
[SerializeField] private GameObject _springIconPrefab;
[Header("Form")]
[SerializeField] private Image[] _formIcons; // 3 forms
[Header("Interact Prompt")]
[SerializeField] private TMP_Text _interactText;
[SerializeField] private GameObject _interactPromptRoot;
[Header("Event Channels - Subscribe")]
[SerializeField] private IntEventChannelSO _onHPChanged;
[SerializeField] private IntEventChannelSO _onMaxHPChanged;
[SerializeField] private IntEventChannelSO _onSoulPowerChanged;
[SerializeField] private IntEventChannelSO _onSpiritPowerChanged;
[SerializeField] private IntEventChannelSO _onGeoChanged;
[SerializeField] private IntEventChannelSO _onSpringChargesChanged;
[SerializeField] private IntEventChannelSO _onFormChanged;
[SerializeField] private StringEventChannelSO _onShowInteractPrompt;
[SerializeField] private VoidEventChannelSO _onHideInteractPrompt;
private void OnEnable()
{
_onHPChanged.OnEventRaised += UpdateHP;
_onMaxHPChanged.OnEventRaised += RebuildHPCells;
_onSoulPowerChanged.OnEventRaised += val => _soulGaugeFill.fillAmount = val / 100f;
_onSpiritPowerChanged.OnEventRaised += val => _spiritGaugeFill.fillAmount = val / 100f;
_onGeoChanged.OnEventRaised += val => _geoText.text = val.ToString();
_onSpringChargesChanged.OnEventRaised += RebuildSpringIcons;
_onFormChanged.OnEventRaised += UpdateFormIcon;
_onShowInteractPrompt.OnEventRaised += ShowInteractPrompt;
_onHideInteractPrompt.OnEventRaised += HideInteractPrompt;
}
private void OnDisable() { /* 对称 -= */ }
private void UpdateHP(int current); // 更新 HP 格子激活/灰化状态
private void RebuildHPCells(int max); // 重建 HP 格子列表MaxHP 改变时)
private void RebuildSpringIcons(int charges);
private void UpdateFormIcon(int formIndex);
private void ShowInteractPrompt(string text);
private void HideInteractPrompt();
}
```
---
## 4. BossHPBar
```csharp
// 路径: Assets/Scripts/UI/HUD/BossHPBar.cs
public class BossHPBar : MonoBehaviour
{
[SerializeField] private TMP_Text _bossNameText;
[SerializeField] private Image _hpFill;
[SerializeField] private Transform _phaseMarkersRoot;
[SerializeField] private GameObject _phaseMarkerPrefab;
[Header("Event Channels")]
[SerializeField] private BoolEventChannelSO _onBossFightToggled; // true=开始false=结束
[SerializeField] private IntEventChannelSO _onBossHPChanged;
[SerializeField] private StringEventChannelSO _onBossNameSet;
[SerializeField] private IntEventChannelSO _onBossHPMaxSet;
private int _maxHP;
private void OnEnable()
{
_onBossFightToggled.OnEventRaised += OnBossFightToggled;
_onBossHPChanged.OnEventRaised += hp => _hpFill.fillAmount = (float)hp / _maxHP;
_onBossNameSet.OnEventRaised += name => _bossNameText.text = name;
_onBossHPMaxSet.OnEventRaised += max => _maxHP = max;
}
private void OnDisable()
{
_onBossFightToggled.OnEventRaised -= OnBossFightToggled;
_onBossHPChanged.OnEventRaised -= hp => _hpFill.fillAmount = (float)hp / _maxHP;
_onBossNameSet.OnEventRaised -= name => _bossNameText.text = name;
_onBossHPMaxSet.OnEventRaised -= max => _maxHP = max;
}
private void OnBossFightToggled(bool started)
{
if (started) StartCoroutine(SlideIn());
else StartCoroutine(SlideOut());
}
private IEnumerator SlideIn(); // 动画Boss 血条从屏幕底部滑入
private IEnumerator SlideOut(); // 动画Boss 血条滑出并隐藏
}
```
---
## 5. PauseMenuController
```csharp
// 路径: Assets/Scripts/UI/Menus/PauseMenuController.cs
public class PauseMenuController : MonoBehaviour
{
[SerializeField] private UIManager _uiManager;
[SerializeField] private Button _btnResume;
[SerializeField] private Button _btnSettings;
[SerializeField] private Button _btnMainMenu;
[SerializeField] private Button _btnQuit;
[Header("Event Channels")]
[SerializeField] private GameStateEventChannelSO _onGameStateChanged;
[SerializeField] private VoidEventChannelSO _onResumeRequested;
private void Awake()
{
_btnResume.onClick.AddListener(Resume);
_btnSettings.onClick.AddListener(() => _uiManager.OpenPanel(_settingsRoot));
_btnMainMenu.onClick.AddListener(GoToMainMenu);
_btnQuit.onClick.AddListener(Application.Quit);
}
private void Resume()
{
_onResumeRequested.Raise();
_uiManager.CloseTopPanel();
}
private void GoToMainMenu();
// 广播 EVT_SceneLoadRequest目标 = MainMenuScene
}
```
---
## 6. DeathScreenController
```csharp
// 路径: Assets/Scripts/UI/Menus/DeathScreenController.cs
public class DeathScreenController : MonoBehaviour
{
[SerializeField] private TMP_Text _deathMessage;
[SerializeField] private Button _btnRespawn;
[Header("Event Channels")]
[SerializeField] private VoidEventChannelSO _onPlayerDied;
[SerializeField] private VoidEventChannelSO _onDeathScreenConfirmed; // Raise → GameManager.RespawnCoroutine
private void OnEnable() => _onPlayerDied.OnEventRaised += OnPlayerDied;
private void OnDisable() => _onPlayerDied.OnEventRaised -= OnPlayerDied;
// ⚠️ EVT_PlayerDied 发出后需等待 1.5s 死亡动画,否则死亡画面会提前弹出
private void OnPlayerDied() => StartCoroutine(ShowAfterDelay(1.5f));
private IEnumerator ShowAfterDelay(float delay)
{
yield return new WaitForSeconds(delay);
Show();
}
private void Show()
{
gameObject.SetActive(true);
_btnRespawn.onClick.RemoveAllListeners();
_btnRespawn.onClick.AddListener(Confirm);
}
private void Confirm()
{
gameObject.SetActive(false);
_onDeathScreenConfirmed.Raise(); // GameManager 监听后执行 RespawnCoroutine
}
}
```
---
## 7. SettingsPanelController
```csharp
// 路径: Assets/Scripts/UI/Menus/SettingsPanelController.cs
// 驱动 SettingsManager 的全部 Set* 方法
public class SettingsPanelController : MonoBehaviour
{
[SerializeField] private SettingsManager _settings;
[Header("Audio")]
[SerializeField] private Slider _masterVolume;
[SerializeField] private Slider _bgmVolume;
[SerializeField] private Slider _sfxVolume;
[SerializeField] private Slider _ambientVolume;
[Header("Video")]
[SerializeField] private Toggle _vSyncToggle;
[SerializeField] private TMP_Dropdown _fpsDropdown;
[SerializeField] private TMP_Dropdown _resolutionDropdown;
private void Start()
{
// 从 SettingsManager 读取当前值并填充控件
// 绑定 onChange 事件 → 调用对应 _settings.Set*()
}
}
```
---
## 7.5 SaveSlotController
```csharp
// 路径: Assets/Scripts/UI/Menus/SaveSlotController.cs
// 驱动主菜单存档槽选择(新游戏 / 继续)
public class SaveSlotController : MonoBehaviour
{
[SerializeField] private SaveSlotUI[] _slotUIs; // 3 个存档槽 UI
[SerializeField] private SaveManager _saveManager;
public async UniTask RefreshAsync()
{
for (int i = 0; i < 3; i++)
{
var summary = await _saveManager.GetSlotSummaryAsync(i);
_slotUIs[i].Refresh(summary); // null = 空槽(显示“新局”)
}
}
public void OnSlotSelected(int slotIndex);
// 新局_saveManager.CreateSlot(slotIndex) → 启动游戏
// 继续_saveManager.LoadAsync(slotIndex) → 载入存档
}
// 单个存档槽卡片组件
public class SaveSlotUI : MonoBehaviour
{
[SerializeField] private TMP_Text _playtimeText;
[SerializeField] private TMP_Text _regionText;
[SerializeField] private TMP_Text _percentText;
[SerializeField] private Image _formIcon;
[SerializeField] private TMP_Text _lastSavedText;
[SerializeField] private GameObject _emptyIndicator; // 空槽提示
public void Refresh(SlotSummary summary);
}
```
---
## 7.6 SaveIndicator
```csharp
// 路径: Assets/Scripts/UI/SaveIndicator.cs
// 右下角存档进行中提示字
[RequireComponent(typeof(CanvasGroup))]
public class SaveIndicator : MonoBehaviour
{
[SerializeField] private CanvasGroup _cg;
[SerializeField] private float _fadeDuration = 0.2f;
[Header("Event Channels")]
// ⚠️ 统一使用单一 BoolEventChannelSO对齐 02_EventSystem §4 EVT_SaveIndicatorVisible 和 12_SaveModule §4/§6
[SerializeField] private BoolEventChannelSO _onSaveIndicatorVisible; // → EVT_SaveIndicatorVisible
private void OnEnable()
{
_onSaveIndicatorVisible.OnEventRaised += visible => FadeTo(visible ? 1f : 0f);
}
private void OnDisable()
{
_onSaveIndicatorVisible.OnEventRaised -= visible => FadeTo(visible ? 1f : 0f);
}
private void FadeTo(float target) => StartCoroutine(FadeCoroutine(target));
private IEnumerator FadeCoroutine(float target)
{
float start = _cg.alpha;
float t = 0;
while (t < _fadeDuration)
{
_cg.alpha = Mathf.Lerp(start, target, t / _fadeDuration);
t += Time.unscaledDeltaTime;
yield return null;
}
_cg.alpha = target;
}
}
```
---
## 7.7 LoadingScreenManager
```csharp
// 路径: Assets/Scripts/UI/LoadingScreenManager.cs
// 全屏加载面:进度条 + 提示文字 + 随机背景图
public class LoadingScreenManager : MonoBehaviour
{
[SerializeField] private GameObject _loadingRoot;
[SerializeField] private Image _progressFill; // 进度条 fillAmount
[SerializeField] private TMP_Text _tipText; // 载入提示
[SerializeField] private Image[] _backgroundArts; // 随机切换的背景图
[SerializeField] private string[] _tipKeys; // 本地化 Key 数组
[SerializeField] private float _minDisplayTime = 0.5f; // 载入面最少展示时长
[Header("Event Channels")]
[SerializeField] private VoidEventChannelSO _onLoadingStarted;
[SerializeField] private VoidEventChannelSO _onLoadingComplete;
[SerializeField] private FloatEventChannelSO _onLoadingProgressUpdated; // 01
private void OnEnable()
{
_onLoadingStarted.OnEventRaised += Show;
_onLoadingComplete.OnEventRaised += Hide;
_onLoadingProgressUpdated.OnEventRaised += SetProgress;
}
private void OnDisable()
{
_onLoadingStarted.OnEventRaised -= Show;
_onLoadingComplete.OnEventRaised -= Hide;
_onLoadingProgressUpdated.OnEventRaised -= SetProgress;
}
private void Show()
{
_loadingRoot.SetActive(true);
_progressFill.fillAmount = 0f;
// 随机选取背景图和提示文字
foreach (var bg in _backgroundArts) bg.enabled = false;
_backgroundArts[Random.Range(0, _backgroundArts.Length)].enabled = true;
_tipText.text = LocalizationManager.Get(_tipKeys[Random.Range(0, _tipKeys.Length)]);
}
private void Hide() => StartCoroutine(HideAfterMinTime());
private IEnumerator HideAfterMinTime()
{
// 确保载入面至少展示 _minDisplayTime 秒
yield return new WaitForSecondsRealtime(_minDisplayTime);
_loadingRoot.SetActive(false);
}
private void SetProgress(float value) => _progressFill.fillAmount = value;
}
```
---
## 7.8 IBossHPProvider 接口
```csharp
// 路径: Assets/Scripts/UI/HUD/IBossHPProvider.cs
// 解耦接口:让 BossHPBar 不直接依赖 BossBaseUI → Combat 逆向耐合)
// BossBase 在运行时实现此接口BossOrchestrator 配置到 BossHPBar._provider 中
public interface IBossHPProvider
{
string BossId { get; } // Boss 前缀 ID
string BossNameKey { get; } // 本地化 Key
float HPRatio { get; } // 01 实时 HP 比例
int TotalPhases { get; } // Boss 总阶段数为阶段标记数
float[] PhaseThresholds { get; } // 各阶段切换 HP 阈值
}
```
---
## 8. LoadingOverlay
```csharp
// 路径: Assets/Scripts/UI/LoadingOverlay.cs
// 由 SceneLoader 直接调用(或通过事件),控制全屏黑幕渐入渐出
public class LoadingOverlay : MonoBehaviour
{
[SerializeField] private CanvasGroup _canvasGroup;
[SerializeField] private float _fadeDuration = 0.3f;
[Header("Event Channels")]
[SerializeField] private BoolEventChannelSO _onLoadingOverlayRequested;
private void OnEnable() => _onLoadingOverlayRequested.OnEventRaised += SetVisible;
private void OnDisable() => _onLoadingOverlayRequested.OnEventRaised -= SetVisible;
private void SetVisible(bool visible) => StartCoroutine(FadeCoroutine(visible ? 1f : 0f));
private IEnumerator FadeCoroutine(float target)
{
float start = _canvasGroup.alpha;
float t = 0;
while (t < _fadeDuration)
{
_canvasGroup.alpha = Mathf.Lerp(start, target, t / _fadeDuration);
t += Time.unscaledDeltaTime;
yield return null;
}
_canvasGroup.alpha = target;
_canvasGroup.blocksRaycasts = target > 0.5f;
}
}
```
---
## 9. DialogueBox
```csharp
// 路径: Assets/Scripts/UI/DialogueBox.cs
// 挂在 Canvas_Overlay 下;由 DialogueManager 控制(见 14_NarrativeModule.md
public class DialogueBox : MonoBehaviour
{
[SerializeField] private TMP_Text _speakerNameText;
[SerializeField] private TMP_Text _dialogueText;
[SerializeField] private GameObject _continuePrompt;
// DialogueManager 直接调用(不通过事件频道,避免帧延迟)
public void Show(string speakerName, string text, bool showContinue);
public void Hide();
// 文字逐字打印协程
public IEnumerator TypeText(string text, float charDelay = 0.03f);
}
```
---
## 10. FloatingDamageText
```csharp
// 路径: Assets/Scripts/UI/FloatingDamageText.cs
// 从对象池取出,显示伤害数字,向上飘动后归还
public class FloatingDamageText : MonoBehaviour
{
[SerializeField] private TMP_Text _text;
[SerializeField] private float _floatDistance = 1.5f;
[SerializeField] private float _duration = 0.8f;
private string _poolKey = AddressKeys.UI_FloatingDmgText;
public void Show(Vector2 worldPosition, int damage, DamageType type);
// 1. 设置世界坐标Camera.main.WorldToScreenPoint → RectTransform
// 2. 颜色Normal=白, Fire=橙, Poison=绿, True=黄
// 3. 协程向上漂移 + alpha 淡出
// 4. 归还对象池
// 由 EVT_DamageDealt 频道触发HUDController 订阅后调用)
}
```
---
## 11. ToastNotification
```csharp
// 路径: Assets/Scripts/UI/ToastNotification.cs
// 右上角通知弹窗(能力解锁、成就、提示)
public class ToastNotification : MonoBehaviour
{
[SerializeField] private TMP_Text _titleText;
[SerializeField] private TMP_Text _bodyText;
[SerializeField] private Image _icon;
[SerializeField] private float _displayDuration = 3f;
public void Show(string title, string body, Sprite icon = null);
private IEnumerator AutoHide();
}
// ToastManager管理通知队列一次只显示一条
public class ToastManager : MonoBehaviour
{
[SerializeField] private ToastNotification _toast;
private Queue<(string title, string body, Sprite icon)> _queue = new();
[Header("Event Channels")]
[SerializeField] private StringEventChannelSO _onAchievementUnlocked; // 广播成就名
// ... 其他通知来源
public void Enqueue(string title, string body, Sprite icon = null);
}
```
---
## 12. InputDeviceIconSwitcher
```csharp
// 路径: Assets/Scripts/UI/InputDeviceIconSwitcher.cs
// 检测输入设备切换KB/手柄),自动替换 UI 按键图标
public class InputDeviceIconSwitcher : MonoBehaviour
{
[SerializeField] private InputDeviceIconSetSO _kbIconSet;
[SerializeField] private InputDeviceIconSetSO _padIconSet;
[Header("Event Channel")]
[SerializeField] private BoolEventChannelSO _onDeviceChanged; // true=手柄
private void OnEnable() => _onDeviceChanged.OnEventRaised += SwitchIconSet;
private void OnDisable() => _onDeviceChanged.OnEventRaised -= SwitchIconSet;
private void SwitchIconSet(bool isGamepad);
// 广播给所有 InputIconImage 组件(自行从 IconSet 查找对应 Sprite
}
```
---
## 13. PanelStack
```csharp
// 已集成在 UIManager 内部OpenPanel / CloseTopPanel
// 控制器导航规则:
// - 每次 OpenPanel 时设置 EventSystem.SetSelectedGameObject(panel.defaultButton)
// - Escape / 手柄 B 键 → 触发 CloseTopPanel
// - Stack 为空时 → 若在 Gameplay 状态则无操作,若在 MainMenu 则弹出退出确认
```
---
## 14. UI 事件频道清单
| 资产名 | 类型 | Raise 方 | Subscribe 方 |
|--------|------|---------|-------------|
| `EVT_GameStateChanged` | `GameStateEventChannelSO` | `GameManager` | `UIManager` |
| `EVT_PlayerDied` | `VoidEventChannelSO` | `PlayerStats` | `DeathScreenController``GameManager` |
| `EVT_DeathScreenConfirmed` | `VoidEventChannelSO` | `DeathScreenController`Respawn 按钮) | `GameManager`(启动 RespawnCoroutine |
| `EVT_ShowInteractPrompt` | `StringEventChannelSO` | `InteractableDetector` | `HUDController` |
| `EVT_HideInteractPrompt` | `VoidEventChannelSO` | `InteractableDetector` | `HUDController` |
| `EVT_BossFightToggled` | `BoolEventChannelSO`true=开始false=结束) | `BossOrchestrator` | `BossHPBar``AudioManager`(切 Boss BGM |
| `EVT_BossHPChanged` | `IntEventChannelSO` | `BossBase` | `BossHPBar` |
| `EVT_BossNameSet` | `StringEventChannelSO` | `BossOrchestrator` | `BossHPBar` |
| `EVT_BossHPMaxSet` | `IntEventChannelSO` | `BossBase` | `BossHPBar` |
| `EVT_LoadingOverlay` | `BoolEventChannelSO` | `SceneLoader` | `LoadingOverlay` |
| `EVT_DamageDealt` | `DamageInfoEventChannelSO` | `HurtBox` | `HUDController`(生成伤害数字)、`AchievementManager` |
| `EVT_AchievementUnlocked` | `StringEventChannelSO` | `AchievementManager` | `ToastManager` |
| `EVT_AbilityUnlocked` | `StringEventChannelSO`abilityId | `PlayerStats.UnlockAbility` | `ToastManager``HUDController` |
| `EVT_PlayerFormChanged` | `IntEventChannelSO` | `FormController` | `HUDController``SkillHUD` |
| `EVT_InputDeviceChanged` | `BoolEventChannelSO` | 输入系统(设备切换回调) | `InputDeviceIconSwitcher` |
| `EVT_LoadingStarted` | `VoidEventChannelSO` | `SceneLoader` | `LoadingScreenManager` |
| `EVT_LoadingComplete` | `VoidEventChannelSO` | `SceneLoader` | `LoadingScreenManager` |
| `EVT_LoadingProgressUpdated` | `FloatEventChannelSO` | `SceneLoader` | `LoadingScreenManager` |
| `EVT_SaveIndicatorVisible` | `BoolEventChannelSO`true=显示false=隐藏) | `SaveManager.SaveAsync()` | `SaveIndicator` |