UI系统
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
@@ -9,99 +11,100 @@ using BaseGames.Localization;
|
||||
namespace BaseGames.UI.MainMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// 新游戏模式选择面板(普通 / 钢铁之魂):开新档前选择难度模式。
|
||||
///
|
||||
/// 设计:
|
||||
/// · 自包含、场景无关——本地 SetActive 显隐 + 回调,不走 UIManager 面板栈,
|
||||
/// 与 MainMenuController 现有的子面板管理方式一致。
|
||||
/// · 选定后通过 onModeChosen 回调把 DifficultyLevel 交还给调用方(SaveSlotController),
|
||||
/// 由调用方负责 CreateSlot(slot, steelSoul) + IDifficultyService.BeginNewGame(level)。
|
||||
/// · 钢铁之魂为破坏性/高难选项,默认焦点置于普通,并显示一段警示文案。
|
||||
/// 新游戏模式(难度)选择面板,<b>数据驱动</b>:据 <see cref="NewGameModeConfigSO"/> 生成难度按钮,
|
||||
/// 经 <see cref="IUINavigator"/> 模态压栈,返回 <see cref="DifficultyLevel"/>;
|
||||
/// 点返回 / 按 ESC 取消则返回 null(由 <see cref="UIResultPanel{T}"/> 兜底)。
|
||||
/// 选中某项时在共享说明区显示其 descKey(手柄/鼠标导航通用)。
|
||||
/// 策划改 UI_NewGameModeConfig 即可增删/重排难度、改标签/说明;样式改 UI_NewGameModePanel / UI_MainMenu_Button 预制件。
|
||||
/// </summary>
|
||||
public class NewGameModeController : MonoBehaviour
|
||||
public class NewGameModeController : UIResultPanel<DifficultyLevel?>
|
||||
{
|
||||
[Header("根节点(显隐用,留空则用本 GameObject)")]
|
||||
[SerializeField] private GameObject _root;
|
||||
[Header("数据表 / 选项列表")]
|
||||
[SerializeField] private NewGameModeConfigSO _config;
|
||||
[Tooltip("难度按钮的父节点(通常挂 VerticalLayoutGroup)。")]
|
||||
[SerializeField] private Transform _container;
|
||||
[SerializeField] private MainMenuButtonView _buttonPrefab;
|
||||
|
||||
[Header("按钮")]
|
||||
[SerializeField] private Button _btnNormal;
|
||||
[SerializeField] private Button _btnSteelSoul;
|
||||
[SerializeField] private Button _btnBack;
|
||||
[Header("引用")]
|
||||
[SerializeField] private LocalizedText _titleText;
|
||||
[Tooltip("当前选中难度的说明文本(随选中项切换)。")]
|
||||
[SerializeField] private TMP_Text _descText;
|
||||
[SerializeField] private Button _btnBack;
|
||||
|
||||
[Header("钢铁之魂说明")]
|
||||
[Tooltip("选中钢铁之魂时显示的警示文案(一命模式,死亡即清档)。走本地化键 MODE_STEELSOUL_DESC。")]
|
||||
[SerializeField] private TMP_Text _steelSoulDescText;
|
||||
[SerializeField] private string _steelSoulDescKey = "MODE_STEELSOUL_DESC";
|
||||
// 取消 / ESC / 返回默认结果:未选择。
|
||||
protected override DifficultyLevel? CancelResult => null;
|
||||
|
||||
private Action<DifficultyLevel> _onModeChosen;
|
||||
private Action _onBack;
|
||||
private readonly List<(MainMenuButtonView view, string descKey)> _options = new();
|
||||
private MainMenuButtonView _firstButton;
|
||||
private GameObject _lastSelected;
|
||||
|
||||
private void Awake()
|
||||
private void Awake() => _btnBack?.onClick.AddListener(() => Complete(null));
|
||||
|
||||
protected override void OnPanelOpen() => BuildMenu();
|
||||
|
||||
/// <summary>默认焦点:第一项(通常普通难度),避免误选高难项。</summary>
|
||||
protected override GameObject ResolveFirstSelected()
|
||||
=> _firstButton != null ? _firstButton.Button.gameObject
|
||||
: _btnBack != null ? _btnBack.gameObject : null;
|
||||
|
||||
/// <summary>据配置重建难度按钮(public 以便编辑器预览/测试)。</summary>
|
||||
public void BuildMenu()
|
||||
{
|
||||
_btnNormal? .onClick.AddListener(() => Choose(DifficultyLevel.Normal));
|
||||
_btnSteelSoul?.onClick.AddListener(() => Choose(DifficultyLevel.SteelSoul));
|
||||
_btnBack? .onClick.AddListener(HandleBack);
|
||||
SetVisible(false);
|
||||
}
|
||||
ClearMenu();
|
||||
if (_titleText != null && _config != null) _titleText.SetKey(_config.TitleKey);
|
||||
if (_config == null || _container == null || _buttonPrefab == null) return;
|
||||
|
||||
/// <summary>
|
||||
/// 弹出模式选择。
|
||||
/// </summary>
|
||||
/// <param name="onModeChosen">玩家选定模式后回调(面板已自动关闭),携带难度档位。</param>
|
||||
/// <param name="onBack">点击返回 / 取消后回调(可选)。</param>
|
||||
public void Show(Action<DifficultyLevel> onModeChosen, Action onBack = null)
|
||||
{
|
||||
_onModeChosen = onModeChosen;
|
||||
_onBack = onBack;
|
||||
|
||||
if (_steelSoulDescText != null && !string.IsNullOrEmpty(_steelSoulDescKey))
|
||||
foreach (var item in _config.Items)
|
||||
{
|
||||
string s = LocalizationManager.Get(_steelSoulDescKey, LocalizationTable.UI);
|
||||
_steelSoulDescText.text = string.IsNullOrEmpty(s) ? _steelSoulDescKey : s;
|
||||
var view = Instantiate(_buttonPrefab, _container);
|
||||
view.gameObject.SetActive(true);
|
||||
var level = item.level;
|
||||
view.Bind(item.labelKey, item.icon, () => Complete(level));
|
||||
_options.Add((view, item.descKey));
|
||||
if (_firstButton == null) _firstButton = view;
|
||||
}
|
||||
|
||||
SetVisible(true);
|
||||
|
||||
// 默认焦点置于普通模式(避免误选一命模式)
|
||||
EventSystem.current?.SetSelectedGameObject(_btnNormal != null
|
||||
? _btnNormal.gameObject
|
||||
: _btnSteelSoul?.gameObject);
|
||||
if (_descText != null) _descText.text = string.Empty;
|
||||
_lastSelected = null;
|
||||
}
|
||||
|
||||
/// <summary>外部强制关闭,不触发回调。</summary>
|
||||
public void Close()
|
||||
private void ClearMenu()
|
||||
{
|
||||
_onModeChosen = null;
|
||||
_onBack = null;
|
||||
SetVisible(false);
|
||||
_options.Clear();
|
||||
_firstButton = null;
|
||||
if (_container == null) return;
|
||||
for (int i = _container.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var c = _container.GetChild(i).gameObject;
|
||||
if (Application.isPlaying) Destroy(c); else DestroyImmediate(c);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 回调 ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void Choose(DifficultyLevel level)
|
||||
// 显示当前选中难度的说明(轮询 EventSystem 选中项;手柄/鼠标导航通用,按钮无需额外组件)。
|
||||
private void Update()
|
||||
{
|
||||
var cb = _onModeChosen;
|
||||
SetVisible(false);
|
||||
_onModeChosen = null;
|
||||
_onBack = null;
|
||||
cb?.Invoke(level);
|
||||
if (_descText == null || EventSystem.current == null) return;
|
||||
var sel = EventSystem.current.currentSelectedGameObject;
|
||||
if (sel == _lastSelected) return;
|
||||
_lastSelected = sel;
|
||||
|
||||
string descKey = null;
|
||||
foreach (var (view, dk) in _options)
|
||||
if (view != null && view.Button != null && view.Button.gameObject == sel) { descKey = dk; break; }
|
||||
_descText.text = string.IsNullOrEmpty(descKey)
|
||||
? string.Empty
|
||||
: LocalizationManager.Get(descKey, LocalizationTable.UI);
|
||||
}
|
||||
|
||||
private void HandleBack()
|
||||
/// <summary>弹出难度选择并等待结果(DifficultyLevel / null=取消)。由导航器压栈管理。</summary>
|
||||
public Task<DifficultyLevel?> ShowAsync(CancellationToken ct = default)
|
||||
{
|
||||
var cb = _onBack;
|
||||
SetVisible(false);
|
||||
_onModeChosen = null;
|
||||
_onBack = null;
|
||||
cb?.Invoke();
|
||||
}
|
||||
|
||||
// ── 工具 ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void SetVisible(bool visible)
|
||||
{
|
||||
var go = _root != null ? _root : gameObject;
|
||||
go.SetActive(visible);
|
||||
var nav = GetService<IUINavigator>();
|
||||
if (nav == null)
|
||||
{
|
||||
Debug.LogError("[NewGameMode] 未找到 IUINavigator 服务,无法弹出模式选择。", this);
|
||||
return Task.FromResult<DifficultyLevel?>(null);
|
||||
}
|
||||
return nav.PushForResultAsync<DifficultyLevel?>(this, ct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user