Files
zeling_v2/Assets/_Game/Scripts/UI/Settings/DataDrivenSettingsPanel.cs
2026-06-06 09:00:11 +08:00

215 lines
9.7 KiB
C#
Raw 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.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using BaseGames.Core;
using BaseGames.Localization;
namespace BaseGames.UI.Settings
{
/// <summary>
/// 数据驱动设置面板:据 <see cref="SettingsSchemaSO"/> 生成控件行并绑定 <see cref="ISettingsService"/>。
///
/// 取代硬编码的 <see cref="BaseGames.UI.SettingsPanelController"/>:策划改表即可增删 / 重排 / 改标签 / 分节,
/// 无需改代码。每行据 <see cref="SettingKey"/> 自动选用 Slider / Toggle / Dropdown 行预制件,
/// 并复用通用控件 <see cref="UISlider"/> / <see cref="UIDropdown"/>。
///
/// 控件来源:行预制件由 <c>SettingsPanelScaffold</c> 生成(标签为 <see cref="LocalizedText"/>)。
/// </summary>
public class DataDrivenSettingsPanel : UIPanelBase
{
public enum ControlKind { Slider, Toggle, Dropdown }
[Header("数据表")]
[SerializeField] private SettingsSchemaSO _schema;
[Header("行布局")]
[Tooltip("生成的行的父节点(通常挂 VerticalLayoutGroup。")]
[SerializeField] private Transform _container;
[SerializeField] private GameObject _headerPrefab;
[SerializeField] private GameObject _sliderRowPrefab;
[SerializeField] private GameObject _toggleRowPrefab;
[SerializeField] private GameObject _dropdownRowPrefab;
// FPS 下拉的取值集合(与 SettingsPanelController 一致)
private static readonly int[] FpsOptions = { 30, 60, 120, -1 };
private static readonly Language[] LanguageOptions =
{ Language.ChineseSimplified, Language.English, Language.Japanese, Language.Korean };
private static readonly string[] LanguageNativeNames =
{ "简体中文", "English", "日本語", "한국어" };
private ISettingsService _settings;
private ILocalizationService _loc;
private readonly List<GameObject> _spawned = new();
// ── 生命周期 ──────────────────────────────────────────────────────────
protected override void OnPanelOpen()
{
_settings = GetService<ISettingsService>();
_loc = GetService<ILocalizationService>();
Build();
}
protected override void OnPanelClose() => Clear();
// ── 构建 ──────────────────────────────────────────────────────────────
/// <summary>据 schema 重建所有行。可在无 ISettingsService 时调用(仅生成行 + 标签,不绑定)。</summary>
public void Build()
{
Clear();
if (_schema == null || _container == null) return;
foreach (var item in _schema.Items)
{
GameObject prefab = item.isHeader ? _headerPrefab : PrefabFor(KindOf(item.key));
if (prefab == null) continue;
var go = Instantiate(prefab, _container);
_spawned.Add(go);
SetLabel(go, item.labelKey);
if (!item.isHeader && _settings != null)
BindControl(go, item.key);
}
}
private void Clear()
{
foreach (var go in _spawned) if (go != null) Destroy(go);
_spawned.Clear();
}
private GameObject PrefabFor(ControlKind kind) => kind switch
{
ControlKind.Slider => _sliderRowPrefab,
ControlKind.Toggle => _toggleRowPrefab,
ControlKind.Dropdown => _dropdownRowPrefab,
_ => null,
};
private static void SetLabel(GameObject row, string labelKey)
{
var loc = row.GetComponentInChildren<LocalizedText>(true);
if (loc != null) loc.SetKey(labelKey);
}
// ── 控件类型映射 ──────────────────────────────────────────────────────
public static ControlKind KindOf(SettingKey key) => key switch
{
SettingKey.MasterVolume or SettingKey.BGMVolume or SettingKey.SFXVolume
or SettingKey.AmbientVolume or SettingKey.UIScale => ControlKind.Slider,
SettingKey.VSync or SettingKey.ScreenShake => ControlKind.Toggle,
SettingKey.TargetFPS or SettingKey.ColorblindMode or SettingKey.Language => ControlKind.Dropdown,
_ => ControlKind.Slider,
};
// ── 绑定派发 ──────────────────────────────────────────────────────────
private void BindControl(GameObject row, SettingKey key)
{
switch (KindOf(key))
{
case ControlKind.Slider: BindSlider(row, key); break;
case ControlKind.Toggle: BindToggle(row, key); break;
case ControlKind.Dropdown: BindDropdown(row, key); break;
}
}
private void BindSlider(GameObject row, SettingKey key)
{
var ui = row.GetComponentInChildren<UISlider>(true);
if (ui == null) return;
var data = _settings.Current;
switch (key)
{
case SettingKey.MasterVolume:
ui.Bind(0f, 1f, data.MasterVolume, v => _settings.SetMasterVolume(v), Percent); break;
case SettingKey.BGMVolume:
ui.Bind(0f, 1f, data.BGMVolume, v => _settings.SetBGMVolume(v), Percent); break;
case SettingKey.SFXVolume:
ui.Bind(0f, 1f, data.SFXVolume, v => _settings.SetSFXVolume(v), Percent); break;
case SettingKey.AmbientVolume:
ui.Bind(0f, 1f, data.AmbientVolume, v => _settings.SetAmbientVolume(v), Percent); break;
case SettingKey.UIScale:
ui.Bind(0.8f, 1.5f, data.UIScale, v => _settings.SetUIScale(v), Percent); break;
}
}
private void BindToggle(GameObject row, SettingKey key)
{
var toggle = row.GetComponentInChildren<Toggle>(true);
if (toggle == null) return;
var data = _settings.Current;
toggle.onValueChanged.RemoveAllListeners();
switch (key)
{
case SettingKey.VSync:
toggle.isOn = data.VSync;
toggle.onValueChanged.AddListener(v => _settings.SetVSync(v));
break;
case SettingKey.ScreenShake:
toggle.isOn = data.ScreenShakeEnabled;
toggle.onValueChanged.AddListener(v => _settings.SetScreenShakeEnabled(v));
break;
}
}
private void BindDropdown(GameObject row, SettingKey key)
{
var ui = row.GetComponentInChildren<UIDropdown>(true);
if (ui == null) return;
var data = _settings.Current;
switch (key)
{
case SettingKey.TargetFPS:
{
var opts = new List<string> { "30", "60", "120", Loc("SET_FPS_UNLIMITED", "无限") };
int idx = Array.IndexOf(FpsOptions, data.TargetFPS); if (idx < 0) idx = 1;
ui.Bind(opts, idx, i => _settings.SetTargetFrameRate(FpsOptions[Mathf.Clamp(i, 0, FpsOptions.Length - 1)]));
break;
}
case SettingKey.ColorblindMode:
{
int count = Enum.GetValues(typeof(ColorblindMode)).Length;
var opts = new List<string>(count);
for (int i = 0; i < count; i++)
opts.Add(Loc($"SET_COLORBLIND_{i}", ((ColorblindMode)i).ToString()));
ui.Bind(opts, (int)data.ColorblindMode,
i => _settings.SetColorblindMode((ColorblindMode)Mathf.Clamp(i, 0, count - 1)));
break;
}
case SettingKey.Language:
{
var opts = new List<string>(LanguageNativeNames);
int idx = _loc != null ? Array.IndexOf(LanguageOptions, _loc.CurrentLanguage) : 0;
if (idx < 0) idx = 0;
ui.Bind(opts, idx, i =>
GetService<ILocalizationService>()?.SetLanguage(LanguageOptions[Mathf.Clamp(i, 0, LanguageOptions.Length - 1)]));
break;
}
}
}
// ── 工具 ──────────────────────────────────────────────────────────────
private static string Percent(float v) => Mathf.RoundToInt(v * 100f) + "%";
private string Loc(string key, string fallback)
{
if (_loc != null && _loc.TryGet(key, out var v, LocalizationTable.UI)) return v;
return fallback;
}
protected override GameObject ResolveFirstSelected()
{
foreach (var go in _spawned)
{
if (go == null) continue;
var sel = go.GetComponentInChildren<Selectable>(true);
if (sel != null) return sel.gameObject;
}
return null;
}
}
}