UI系统优化
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
using TMPro;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Core.Save;
|
||||
using BaseGames.Localization;
|
||||
|
||||
namespace BaseGames.UI.Menus
|
||||
{
|
||||
@@ -14,32 +17,49 @@ namespace BaseGames.UI.Menus
|
||||
/// </summary>
|
||||
public class SaveSlotController : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private SaveSlotUI[] _slotUIs; // 存档槽 UI(数量由 Inspector 决定)
|
||||
[SerializeField] private SaveSlotUI[] _slotUIs;
|
||||
|
||||
[Header("Event Channels")]
|
||||
[SerializeField] private IntEventChannelSO _onSlotConfirmed; // 携带槽索引,供 GameManager 监听
|
||||
[SerializeField] private IntEventChannelSO _onSlotConfirmed;
|
||||
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
for (int i = 0; i < _slotUIs.Length; i++)
|
||||
if (_slotUIs[i] != null) _slotUIs[i].Init(i, this);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
var task = RefreshAsync();
|
||||
_cts = new CancellationTokenSource();
|
||||
var ct = _cts.Token;
|
||||
var task = RefreshAsync(ct);
|
||||
task.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
if (t.IsFaulted && !(t.Exception?.InnerException is OperationCanceledException))
|
||||
Debug.LogException(t.Exception?.InnerException ?? t.Exception, this);
|
||||
}, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
}
|
||||
|
||||
private async Task RefreshAsync()
|
||||
private void OnDisable()
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
}
|
||||
|
||||
private async Task RefreshAsync(CancellationToken ct = default)
|
||||
{
|
||||
var svc = ServiceLocator.GetOrDefault<ISaveService>();
|
||||
if (svc == null) return;
|
||||
|
||||
// 并行加载所有槽位摘要,减少主菜单等待时间
|
||||
var tasks = new Task<SlotSummary>[_slotUIs.Length];
|
||||
for (int i = 0; i < _slotUIs.Length; i++)
|
||||
tasks[i] = svc.GetSlotSummaryAsync(i);
|
||||
|
||||
var summaries = await Task.WhenAll(tasks);
|
||||
ct.ThrowIfCancellationRequested(); // 组件已失活时不写 UI
|
||||
|
||||
for (int i = 0; i < _slotUIs.Length; i++)
|
||||
{
|
||||
@@ -80,7 +100,7 @@ namespace BaseGames.UI.Menus
|
||||
var svc = ServiceLocator.GetOrDefault<ISaveService>();
|
||||
if (svc == null) return;
|
||||
await svc.DeleteSlotAsync(slotIndex);
|
||||
await RefreshAsync();
|
||||
await RefreshAsync(_cts?.Token ?? CancellationToken.None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +118,14 @@ namespace BaseGames.UI.Menus
|
||||
[SerializeField] private Button _selectButton;
|
||||
[SerializeField] private Button _deleteButton;
|
||||
|
||||
[Header("删除确认(可选)")]
|
||||
[Tooltip("删除确认对话框根节点,为 null 时删除按钮直接执行。")]
|
||||
[SerializeField] private GameObject _deleteConfirmRoot;
|
||||
[Tooltip("确认删除按钮。")]
|
||||
[SerializeField] private Button _btnConfirmDelete;
|
||||
[Tooltip("取消删除按钮。")]
|
||||
[SerializeField] private Button _btnCancelDelete;
|
||||
|
||||
private int _slotIndex;
|
||||
private SaveSlotController _controller;
|
||||
|
||||
@@ -112,11 +140,31 @@ namespace BaseGames.UI.Menus
|
||||
_selectButton.onClick.RemoveAllListeners();
|
||||
_selectButton.onClick.AddListener(() => _controller.OnSlotSelected(_slotIndex));
|
||||
}
|
||||
|
||||
if (_deleteButton != null)
|
||||
{
|
||||
_deleteButton.onClick.RemoveAllListeners();
|
||||
_deleteButton.onClick.AddListener(() => _controller.OnSlotDeleteRequested(_slotIndex));
|
||||
// 配置了确认对话框时先弹出确认,未配置时直接删除
|
||||
_deleteButton.onClick.AddListener(ShowDeleteConfirm);
|
||||
}
|
||||
|
||||
if (_btnConfirmDelete != null)
|
||||
{
|
||||
_btnConfirmDelete.onClick.RemoveAllListeners();
|
||||
_btnConfirmDelete.onClick.AddListener(() =>
|
||||
{
|
||||
HideDeleteConfirm();
|
||||
_controller.OnSlotDeleteRequested(_slotIndex);
|
||||
});
|
||||
}
|
||||
|
||||
if (_btnCancelDelete != null)
|
||||
{
|
||||
_btnCancelDelete.onClick.RemoveAllListeners();
|
||||
_btnCancelDelete.onClick.AddListener(HideDeleteConfirm);
|
||||
}
|
||||
|
||||
HideDeleteConfirm();
|
||||
}
|
||||
|
||||
/// <summary>用摘要数据刷新显示;summary 为 null 表示空槽。</summary>
|
||||
@@ -128,13 +176,51 @@ namespace BaseGames.UI.Menus
|
||||
if (_dataIndicator != null) _dataIndicator.SetActive(hasData);
|
||||
if (_deleteButton != null) _deleteButton.gameObject.SetActive(hasData);
|
||||
|
||||
// 刷新时重置确认对话框状态
|
||||
HideDeleteConfirm();
|
||||
|
||||
if (!hasData) return;
|
||||
|
||||
if (_playtimeText != null) _playtimeText.text = FormatPlaytime(summary.Playtime);
|
||||
if (_regionText != null) _regionText.text = summary.SceneName ?? string.Empty;
|
||||
|
||||
if (_regionText != null)
|
||||
{
|
||||
string key = summary.SceneName ?? string.Empty;
|
||||
string loc = !string.IsNullOrEmpty(key)
|
||||
? LocalizationManager.Get(key, LocalizationTable.UI)
|
||||
: null;
|
||||
_regionText.text = !string.IsNullOrEmpty(loc) && loc != key ? loc : key;
|
||||
}
|
||||
|
||||
if (_lastSavedText != null) _lastSavedText.text = FormatDateTime(summary.LastSaved);
|
||||
}
|
||||
|
||||
// ── 删除确认 ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 显示删除确认对话框。
|
||||
/// 未配置 _deleteConfirmRoot 时直接执行删除(向下兼容旧 Prefab)。
|
||||
/// </summary>
|
||||
private void ShowDeleteConfirm()
|
||||
{
|
||||
if (_deleteConfirmRoot == null)
|
||||
{
|
||||
// 旧 Prefab 未添加确认根节点,直接删除
|
||||
_controller.OnSlotDeleteRequested(_slotIndex);
|
||||
return;
|
||||
}
|
||||
_deleteConfirmRoot.SetActive(true);
|
||||
// 手柄导航:将焦点移至"确认"按钮,防止误触选择按钮
|
||||
EventSystem.current?.SetSelectedGameObject(_btnConfirmDelete?.gameObject);
|
||||
}
|
||||
|
||||
private void HideDeleteConfirm()
|
||||
{
|
||||
if (_deleteConfirmRoot != null) _deleteConfirmRoot.SetActive(false);
|
||||
}
|
||||
|
||||
// ── 格式化工具 ────────────────────────────────────────────────────────
|
||||
|
||||
private static string FormatPlaytime(float seconds)
|
||||
{
|
||||
int h = (int)(seconds / 3600);
|
||||
|
||||
Reference in New Issue
Block a user