UI系统组件

This commit is contained in:
2026-06-06 09:00:11 +08:00
parent fe4fd60083
commit d794b83ebe
107 changed files with 25690 additions and 476 deletions

View File

@@ -1,7 +1,5 @@
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using BaseGames.Core;
using BaseGames.Core.Assets;
using BaseGames.Core.Events;
@@ -10,9 +8,9 @@ namespace BaseGames.UI
/// <summary>
/// 暂停菜单控制器(架构 10_UIModule §5
/// 挂载在 Canvas_Menu → PauseMenuPanel GameObject 上。
/// 按钮绑定在 Awake 中完成;UIManager 负责面板开关
/// 按钮绑定在 Awake 中完成;生命周期 / 焦点由 <see cref="UIPanelBase"/> 统一处理
/// </summary>
public class PauseMenuController : MonoBehaviour, IFocusable
public class PauseMenuController : UIPanelBase
{
// UIManager 通过 ServiceLocator 解析,开启时自动获取,无需 Inspector 直接绑定具体类型
private IUIManager _uiManager;
@@ -35,18 +33,13 @@ namespace BaseGames.UI
_btnQuit?.onClick.AddListener(Application.Quit);
}
private void OnEnable()
{
// 暂停面板由 UIManager 开启,此时 ServiceLocator 已就绪
_uiManager = ServiceLocator.GetOrDefault<IUIManager>();
// 手柄导航:打开时将焦点置于第一个按钮
EventSystem.current?.SetSelectedGameObject(_btnResume?.gameObject);
}
// 暂停面板由 UIManager 开启,此时 ServiceLocator 已就绪
protected override void OnPanelOpen() => _uiManager = GetService<IUIManager>();
protected override void OnPanelClose() => _uiManager = null;
private void OnDisable()
{
_uiManager = null;
}
/// <summary>默认焦点 / 焦点恢复回到"继续"按钮(基类 FocusFirst 调用)。</summary>
protected override GameObject ResolveFirstSelected()
=> _btnResume != null ? _btnResume.gameObject : null;
// ── 按钮回调 ──────────────────────────────────────────────────────────
@@ -71,11 +64,5 @@ namespace BaseGames.UI
ShowLoadingScreen = false,
});
}
// ── IFocusable ────────────────────────────────────────────────────────
/// <summary>面板恢复为栈顶时(关闭子面板后)自动移回第一个按鈕。</summary>
public void OnFocusRestored()
=> EventSystem.current?.SetSelectedGameObject(_btnResume?.gameObject);
}
}

View File

@@ -83,6 +83,9 @@ namespace BaseGames.UI.Menus
if (t.IsFaulted && !(t.Exception?.InnerException is OperationCanceledException))
Debug.LogException(t.Exception?.InnerException ?? t.Exception, this);
}, TaskScheduler.FromCurrentSynchronizationContext());
// 面板打开时设置初始焦点(键盘 / 手柄导航入口)
StartCoroutine(RestoreFocusNextFrame());
}
private void OnDisable()

View File

@@ -1,16 +1,17 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using BaseGames.Core;
using BaseGames.Localization;
namespace BaseGames.UI
{
/// <summary>
/// 设置面板控制器(架构 10_UIModule §7
/// 驱动 SettingsManager 的音量与画面设置,并从当前配置初始化控件值。
/// 生命周期 / 焦点由 <see cref="UIPanelBase"/> 统一处理。
/// </summary>
public class SettingsPanelController : MonoBehaviour, IFocusable
public class SettingsPanelController : UIPanelBase
{
// ISettingsService 通过 ServiceLocator 获取,无需 Inspector 直接注入具体类,
// 支持测试场景替换 Mock 实现。
@@ -32,12 +33,19 @@ namespace BaseGames.UI
[SerializeField] private TMP_Dropdown _colorblindDropdown; // None / Prot / Deut / Trit
[SerializeField] private Toggle _screenShakeToggle;
[Header("语言")]
[SerializeField] private TMP_Dropdown _languageDropdown; // 中文 / English / 日本語 / 한국어
[Header("按键重绑定")]
[SerializeField] private GameObject _rebindPanelRoot; // RebindPanel GameObject
private static readonly int[] FpsOptions = { 30, 60, 120, -1 };
private void OnEnable()
// 语言下拉项顺序(与脚手架填充的显示项一一对应)
private static readonly Language[] LanguageOptions =
{ Language.ChineseSimplified, Language.English, Language.Japanese, Language.Korean };
protected override void OnPanelOpen()
{
_settings = ServiceLocator.GetOrDefault<ISettingsService>();
if (_settings == null) return;
@@ -95,8 +103,18 @@ namespace BaseGames.UI
_screenShakeToggle.onValueChanged.AddListener(v => _settings.SetScreenShakeEnabled(v));
}
// 手柄导航:打开设置面板时将焦点置于主音量滑条
EventSystem.current?.SetSelectedGameObject(_masterVolume?.gameObject);
// ── 语言 ──────────────────────────────────────────────────────────
if (_languageDropdown != null)
{
_languageDropdown.onValueChanged.RemoveAllListeners();
var loc = ServiceLocator.GetOrDefault<ILocalizationService>();
int idx = loc != null ? System.Array.IndexOf(LanguageOptions, loc.CurrentLanguage) : 0;
_languageDropdown.value = idx >= 0 ? idx : 0;
_languageDropdown.RefreshShownValue();
_languageDropdown.onValueChanged.AddListener(i =>
ServiceLocator.GetOrDefault<ILocalizationService>()?
.SetLanguage(LanguageOptions[Mathf.Clamp(i, 0, LanguageOptions.Length - 1)]));
}
}
private void UpdateUIScaleLabel(float v)
@@ -115,10 +133,10 @@ namespace BaseGames.UI
slider.onValueChanged.AddListener(onChange);
}
// ── IFocusable ────────────────────────────────────────────────────────
// ── 焦点 ──────────────────────────────────────────────────────────────
/// <summary>面板恢复为栈顶时将焦点移回主音量滑条。</summary>
public void OnFocusRestored()
=> EventSystem.current?.SetSelectedGameObject(_masterVolume?.gameObject);
/// <summary>默认焦点 / 焦点恢复回到主音量滑条(基类 FocusFirst 调用)。</summary>
protected override GameObject ResolveFirstSelected()
=> _masterVolume != null ? _masterVolume.gameObject : null;
}
}