143 lines
6.9 KiB
C#
143 lines
6.9 KiB
C#
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using BaseGames.Core;
|
||
using BaseGames.Localization;
|
||
|
||
namespace BaseGames.UI
|
||
{
|
||
/// <summary>
|
||
/// 设置面板控制器(架构 10_UIModule §7)。
|
||
/// 驱动 SettingsManager 的音量与画面设置,并从当前配置初始化控件值。
|
||
/// 生命周期 / 焦点由 <see cref="UIPanelBase"/> 统一处理。
|
||
/// </summary>
|
||
public class SettingsPanelController : UIPanelBase
|
||
{
|
||
// ISettingsService 通过 ServiceLocator 获取,无需 Inspector 直接注入具体类,
|
||
// 支持测试场景替换 Mock 实现。
|
||
private ISettingsService _settings;
|
||
|
||
[Header("音量滑条")]
|
||
[SerializeField] private Slider _masterVolume;
|
||
[SerializeField] private Slider _bgmVolume;
|
||
[SerializeField] private Slider _sfxVolume;
|
||
[SerializeField] private Slider _ambientVolume;
|
||
|
||
[Header("画面")]
|
||
[SerializeField] private Toggle _vSyncToggle;
|
||
[SerializeField] private TMP_Dropdown _fpsDropdown; // 30 / 60 / 120 / 无限
|
||
|
||
[Header("可访问性")]
|
||
[SerializeField] private Slider _uiScaleSlider; // 0.8 ~ 1.5
|
||
[SerializeField] private TMP_Text _uiScaleValueText; // 实时显示 "100%"
|
||
[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 static readonly Language[] LanguageOptions =
|
||
{ Language.ChineseSimplified, Language.English, Language.Japanese, Language.Korean };
|
||
|
||
protected override void OnPanelOpen()
|
||
{
|
||
_settings = ServiceLocator.GetOrDefault<ISettingsService>();
|
||
if (_settings == null) return;
|
||
var data = _settings.Current;
|
||
|
||
// 初始化控件值(先移除监听再设置值再添加,防止面板重开时重复注册)
|
||
InitSlider(_masterVolume, data.MasterVolume, v => _settings.SetMasterVolume(v));
|
||
InitSlider(_bgmVolume, data.BGMVolume, v => _settings.SetBGMVolume(v));
|
||
InitSlider(_sfxVolume, data.SFXVolume, v => _settings.SetSFXVolume(v));
|
||
InitSlider(_ambientVolume,data.AmbientVolume, v => _settings.SetAmbientVolume(v));
|
||
|
||
if (_vSyncToggle != null)
|
||
{
|
||
_vSyncToggle.onValueChanged.RemoveAllListeners();
|
||
_vSyncToggle.isOn = data.VSync;
|
||
_vSyncToggle.onValueChanged.AddListener(v => _settings.SetVSync(v));
|
||
}
|
||
|
||
if (_fpsDropdown != null)
|
||
{
|
||
_fpsDropdown.onValueChanged.RemoveAllListeners();
|
||
int idx = System.Array.IndexOf(FpsOptions, data.TargetFPS);
|
||
_fpsDropdown.value = idx >= 0 ? idx : 1; // default 60
|
||
_fpsDropdown.onValueChanged.AddListener(i =>
|
||
_settings.SetTargetFrameRate(FpsOptions[Mathf.Clamp(i, 0, FpsOptions.Length - 1)]));
|
||
}
|
||
|
||
// ── 可访问性 ──────────────────────────────────────────────────────
|
||
if (_uiScaleSlider != null)
|
||
{
|
||
_uiScaleSlider.onValueChanged.RemoveAllListeners();
|
||
_uiScaleSlider.minValue = 0.8f;
|
||
_uiScaleSlider.maxValue = 1.5f;
|
||
_uiScaleSlider.value = Mathf.Clamp(data.UIScale, _uiScaleSlider.minValue, _uiScaleSlider.maxValue);
|
||
UpdateUIScaleLabel(_uiScaleSlider.value);
|
||
_uiScaleSlider.onValueChanged.AddListener(v =>
|
||
{
|
||
_settings.SetUIScale(v);
|
||
UpdateUIScaleLabel(v);
|
||
});
|
||
}
|
||
|
||
if (_colorblindDropdown != null)
|
||
{
|
||
_colorblindDropdown.onValueChanged.RemoveAllListeners();
|
||
_colorblindDropdown.value = (int)data.ColorblindMode;
|
||
_colorblindDropdown.onValueChanged.AddListener(i =>
|
||
_settings.SetColorblindMode((ColorblindMode)Mathf.Clamp(i, 0, 3)));
|
||
}
|
||
|
||
if (_screenShakeToggle != null)
|
||
{
|
||
_screenShakeToggle.onValueChanged.RemoveAllListeners();
|
||
_screenShakeToggle.isOn = data.ScreenShakeEnabled;
|
||
_screenShakeToggle.onValueChanged.AddListener(v => _settings.SetScreenShakeEnabled(v));
|
||
}
|
||
|
||
// ── 语言 ──────────────────────────────────────────────────────────
|
||
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)
|
||
{
|
||
if (_uiScaleValueText != null)
|
||
_uiScaleValueText.text = Mathf.RoundToInt(v * 100f) + "%";
|
||
}
|
||
|
||
// ── 辅助 ──────────────────────────────────────────────────────────────
|
||
|
||
private static void InitSlider(Slider slider, float value, UnityEngine.Events.UnityAction<float> onChange)
|
||
{
|
||
if (slider == null) return;
|
||
slider.onValueChanged.RemoveAllListeners();
|
||
slider.value = value;
|
||
slider.onValueChanged.AddListener(onChange);
|
||
}
|
||
|
||
// ── 焦点 ──────────────────────────────────────────────────────────────
|
||
|
||
/// <summary>默认焦点 / 焦点恢复回到主音量滑条(基类 FocusFirst 调用)。</summary>
|
||
protected override GameObject ResolveFirstSelected()
|
||
=> _masterVolume != null ? _masterVolume.gameObject : null;
|
||
}
|
||
}
|