This commit is contained in:
2026-06-07 11:49:55 +08:00
parent ff0f3bde54
commit 1897658a00
98 changed files with 9903 additions and 13907 deletions

View File

@@ -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);
}
}
}