# 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. [DialogueBox(HUD 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 _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; // 0–1 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 不直接依赖 BossBase(UI → Combat 逆向耐合) // BossBase 在运行时实现此接口,BossOrchestrator 配置到 BossHPBar._provider 中 public interface IBossHPProvider { string BossId { get; } // Boss 前缀 ID string BossNameKey { get; } // 本地化 Key float HPRatio { get; } // 0–1 实时 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` |