25 KiB
25 KiB
10 · UI 模块
命名空间
BaseGames.UI
程序集BaseGames.UI
路径Assets/Scripts/UI/
依赖BaseGames.Core.Events、TextMeshPro
目录
- Canvas 架构(Persistent 场景内)
- UIManager
- HUDController
- BossHPBar
- PauseMenuController
- DeathScreenController
- SettingsPanelController
- SaveSlotController
- SaveIndicator
- LoadingScreenManager
- IBossHPProvider 接口
- LoadingOverlay
- DialogueBox(HUD Overlay)
- FloatingDamageText(伤害数字)
- ToastNotification(通知弹窗)
- InputDeviceIconSwitcher
- PanelStack(控制器导航)
- 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
// 路径: 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
// 路径: 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
// 路径: 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
// 路径: 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
// 路径: 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
// 路径: 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
// 路径: 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
// 路径: 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
// 路径: 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 接口
// 路径: 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
// 路径: 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
// 路径: 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
// 路径: 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
// 路径: 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
// 路径: 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
// 已集成在 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 |