using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using TMPro; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Localization; namespace BaseGames.UI { /// /// 图鉴面板(数据驱动,仿 )。据 列出全部敌人, /// 经 (ServiceLocator) 读已发现态:已发现亮显,未发现 "???"+剪影+锁。 /// 订阅 EVT_BestiaryUpdated 实时刷新;点击格子在右侧详情区展示名/描述/数值/击杀数;显示完成度 X/Y。 /// 菜单态(无游戏,service=null)下全部显示未发现。 /// public class BestiaryPanel : MonoBehaviour, IFocusable { [Header("数据 / 单元格")] [SerializeField] private BestiaryDatabaseSO _database; [Tooltip("敌人格的父节点(通常挂 GridLayoutGroup)。")] [SerializeField] private Transform _container; [SerializeField] private BestiaryCellView _cellPrefab; [Header("头部 / 关闭")] [Tooltip("完成度文本(X / Y)。")] [SerializeField] private TMP_Text _completionText; [SerializeField] private Button _btnClose; [Header("详情区")] [SerializeField] private Image _detailIcon; [SerializeField] private LocalizedText _detailName; [SerializeField] private TMP_Text _detailDesc; [SerializeField] private TMP_Text _detailStats; [Header("Event Channels")] [Tooltip("图鉴更新广播(payload=enemyId),用于实时刷新。对应 EVT_BestiaryUpdated。")] [SerializeField] private StringEventChannelSO _onBestiaryUpdated; private readonly List<(BestiaryCellView view, BestiaryDatabaseSO.Entry entry)> _cells = new(); private readonly CompositeDisposable _subs = new(); private IBestiaryService _service; private void Awake() => _btnClose?.onClick.AddListener(OnClose); private void OnEnable() { _onBestiaryUpdated?.Subscribe(_ => RefreshStates()).AddTo(_subs); _service = ServiceLocator.GetOrDefault(); // 菜单态为 null → 全部未发现 BuildList(); ShowDetail(-1); } private void OnDisable() => _subs.Clear(); // ── 列表构建 ────────────────────────────────────────────────────────── public void BuildList() { ClearList(); if (_database == null || _container == null || _cellPrefab == null) return; var entries = _database.Entries; if (entries == null) return; for (int i = 0; i < entries.Length; i++) { var entry = entries[i]; var cell = Instantiate(_cellPrefab, _container); cell.gameObject.SetActive(true); cell.Bind(entry.displayNameKey, entry.icon, IsDiscovered(entry)); int idx = i; if (cell.SelectButton != null) cell.SelectButton.onClick.AddListener(() => ShowDetail(idx)); _cells.Add((cell, entry)); } UpdateCompletion(); } private void RefreshStates() { if (_service == null) _service = ServiceLocator.GetOrDefault(); foreach (var (view, entry) in _cells) if (view != null) view.SetDiscovered(IsDiscovered(entry)); UpdateCompletion(); } private bool IsDiscovered(BestiaryDatabaseSO.Entry entry) => _service != null && _service.IsDiscovered(entry.enemyId); private void UpdateCompletion() { if (_completionText == null) return; int total = _database != null && _database.Entries != null ? _database.Entries.Length : 0; int found = 0; foreach (var (_, entry) in _cells) if (IsDiscovered(entry)) found++; _completionText.text = $"{found} / {total}"; } // ── 详情 ────────────────────────────────────────────────────────────── private void ShowDetail(int index) { bool valid = index >= 0 && index < _cells.Count; var entry = valid ? _cells[index].entry : default; bool discovered = valid && IsDiscovered(entry); if (_detailIcon != null) { _detailIcon.sprite = discovered ? entry.icon : null; _detailIcon.enabled = discovered && entry.icon != null; } if (_detailName != null) _detailName.SetKey(!valid ? "" : (discovered ? entry.displayNameKey : "BESTIARY_LOCKED")); if (_detailDesc != null) { if (!valid) _detailDesc.text = ""; else if (!discovered) _detailDesc.text = LocalizationManager.Get("BESTIARY_LOCKED_DESC", LocalizationTable.UI); else { string d = LocalizationManager.Get(entry.descKey, LocalizationTable.UI); _detailDesc.text = string.IsNullOrEmpty(d) || d == entry.descKey ? "" : d; } } if (_detailStats != null) { if (!valid || !discovered) { _detailStats.text = ""; } else { int kills = _service != null ? _service.GetKillCount(entry.enemyId) : 0; string hpLabel = LocalizationManager.Get("BESTIARY_HP", LocalizationTable.UI); string killLabel = LocalizationManager.Get("BESTIARY_KILLS", LocalizationTable.UI); var sb = new System.Text.StringBuilder(); if (entry.displayMaxHP > 0) sb.Append(hpLabel).Append(' ').Append(entry.displayMaxHP).Append(" "); sb.Append(killLabel).Append(' ').Append(kills); _detailStats.text = sb.ToString(); } } } private void ClearList() { _cells.Clear(); 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 OnClose() { var uiMgr = ServiceLocator.GetOrDefault(); if (uiMgr != null) uiMgr.CloseTopPanel(); else gameObject.SetActive(false); } // ── IFocusable ──────────────────────────────────────────────────────── public void OnFocusRestored() { GameObject target = _cells.Count > 0 && _cells[0].view != null && _cells[0].view.SelectButton != null ? _cells[0].view.SelectButton.gameObject : _btnClose != null ? _btnClose.gameObject : null; if (target != null) EventSystem.current?.SetSelectedGameObject(target); } } }