using System; using UnityEngine; using UnityEngine.UI; using TMPro; using BaseGames.Core.Save; using BaseGames.Localization; using BaseGames.World.Map; namespace BaseGames.UI.Menus { /// /// 单个存档槽卡片组件,负责显示存档摘要或空槽提示。 /// /// ⚠ 必须独立成文件:Unity 仅为与文件名同名的类生成 MonoScript 资产, /// 若与 SaveSlotController 合并在同一文件,AddComponent 后脚本引用会丢失(Missing Script)。 /// 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; /// 由 SaveSlotController 在 Awake 时调用以完成按钮绑定。 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 为 null 表示空槽。 /// 槽位摘要,null 为空槽。 /// 当前面板模式:继续模式下空槽不可选。 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; } } }