144 lines
6.1 KiB
C#
144 lines
6.1 KiB
C#
using System;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
using BaseGames.Core.Save;
|
||
using BaseGames.Localization;
|
||
using BaseGames.World.Map;
|
||
|
||
namespace BaseGames.UI.Menus
|
||
{
|
||
/// <summary>
|
||
/// 单个存档槽卡片组件,负责显示存档摘要或空槽提示。
|
||
///
|
||
/// ⚠ 必须独立成文件:Unity 仅为与文件名同名的类生成 MonoScript 资产,
|
||
/// 若与 SaveSlotController 合并在同一文件,AddComponent 后脚本引用会丢失(Missing Script)。
|
||
/// </summary>
|
||
public class SaveSlotUI : MonoBehaviour
|
||
{
|
||
[Header("基本信息")]
|
||
[SerializeField] private TMP_Text _playtimeText;
|
||
[SerializeField] private TMP_Text _regionText;
|
||
[SerializeField] private TMP_Text _lastSavedText;
|
||
[SerializeField] private Image _formIcon;
|
||
|
||
[Header("扩展信息(可选)")]
|
||
[Tooltip("货币(灵珠)持有量文本。")]
|
||
[SerializeField] private TMP_Text _lingZhuText;
|
||
[Tooltip("生命(面具)上限文本。")]
|
||
[SerializeField] private TMP_Text _hpText;
|
||
[Tooltip("钢铁之魂徽章根节点,仅一命模式存档显示。")]
|
||
[SerializeField] private GameObject _steelSoulBadge;
|
||
|
||
[Header("区域背景图")]
|
||
[Tooltip("RegionRegistry.asset,用于根据 SceneName 查找区域背景图。")]
|
||
[SerializeField] private RegionRegistrySO _regionRegistry;
|
||
[Tooltip("存档点不在任何已注册区域时显示的默认背景图。")]
|
||
[SerializeField] private Sprite _fallbackBackground;
|
||
[Tooltip("显示区域背景图的 Image 组件。")]
|
||
[SerializeField] private Image _backgroundImage;
|
||
|
||
[Header("槽位状态")]
|
||
[SerializeField] private GameObject _emptyIndicator; // 空槽时显示的"新游戏"提示
|
||
[SerializeField] private GameObject _dataIndicator; // 有数据时显示的内容根
|
||
[SerializeField] private Button _selectButton;
|
||
[SerializeField] private Button _deleteButton;
|
||
|
||
private int _slotIndex;
|
||
private SaveSlotController _controller;
|
||
|
||
/// <summary>由 SaveSlotController 在 Awake 时调用以完成按钮绑定。</summary>
|
||
public void Init(int slotIndex, SaveSlotController controller)
|
||
{
|
||
_slotIndex = slotIndex;
|
||
_controller = controller;
|
||
|
||
if (_selectButton != null)
|
||
{
|
||
_selectButton.onClick.RemoveAllListeners();
|
||
_selectButton.onClick.AddListener(() => _controller.OnSlotSelected(_slotIndex));
|
||
}
|
||
|
||
if (_deleteButton != null)
|
||
{
|
||
_deleteButton.onClick.RemoveAllListeners();
|
||
// 删除统一走 Controller → 通用确认对话框(不再使用每卡内联确认)
|
||
_deleteButton.onClick.AddListener(() => _controller.OnSlotDeleteRequested(_slotIndex));
|
||
}
|
||
}
|
||
|
||
/// <summary>用摘要数据刷新显示;summary 为 null 表示空槽。</summary>
|
||
/// <param name="summary">槽位摘要,null 为空槽。</param>
|
||
/// <param name="mode">当前面板模式:继续模式下空槽不可选。</param>
|
||
public void Refresh(SlotSummary summary, SaveSlotPanelMode mode)
|
||
{
|
||
bool hasData = summary != null;
|
||
|
||
if (_emptyIndicator != null) _emptyIndicator.SetActive(!hasData);
|
||
if (_dataIndicator != null) _dataIndicator.SetActive(hasData);
|
||
if (_deleteButton != null) _deleteButton.gameObject.SetActive(hasData);
|
||
|
||
// 继续模式下空槽不可选;新游戏模式下空槽可选(建新档)
|
||
if (_selectButton != null)
|
||
_selectButton.interactable = hasData || mode == SaveSlotPanelMode.NewGame;
|
||
|
||
if (_steelSoulBadge != null)
|
||
_steelSoulBadge.SetActive(hasData && summary.IsSteelSoul);
|
||
|
||
if (!hasData) return;
|
||
|
||
if (_playtimeText != null) _playtimeText.text = FormatPlaytime(summary.Playtime);
|
||
|
||
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);
|
||
if (_lingZhuText != null) _lingZhuText.text = summary.CurrentLingZhu.ToString();
|
||
if (_hpText != null) _hpText.text = summary.MaxHP.ToString();
|
||
|
||
RefreshBackground(summary);
|
||
}
|
||
|
||
private void RefreshBackground(SlotSummary summary)
|
||
{
|
||
if (_backgroundImage == null) return;
|
||
|
||
Sprite bg = null;
|
||
if (summary != null && !string.IsNullOrEmpty(summary.SceneName))
|
||
bg = _regionRegistry?.FindBySceneName(summary.SceneName)?.saveSlotBackground;
|
||
|
||
_backgroundImage.sprite = bg != null ? bg : _fallbackBackground;
|
||
_backgroundImage.enabled = _backgroundImage.sprite != null;
|
||
}
|
||
|
||
// ── 格式化工具 ────────────────────────────────────────────────────────
|
||
|
||
private static string FormatPlaytime(float seconds)
|
||
{
|
||
int h = (int)(seconds / 3600);
|
||
int m = (int)((seconds % 3600) / 60);
|
||
int s = (int)(seconds % 60);
|
||
return $"{h:D2}:{m:D2}:{s:D2}";
|
||
}
|
||
|
||
private static string FormatDateTime(string iso8601)
|
||
{
|
||
if (string.IsNullOrEmpty(iso8601)) return string.Empty;
|
||
if (DateTime.TryParse(iso8601,
|
||
System.Globalization.CultureInfo.InvariantCulture,
|
||
System.Globalization.DateTimeStyles.RoundtripKind,
|
||
out DateTime dt))
|
||
{
|
||
return dt.ToLocalTime().ToString("yyyy-MM-dd HH:mm");
|
||
}
|
||
return iso8601;
|
||
}
|
||
}
|
||
}
|