169 lines
7.4 KiB
C#
169 lines
7.4 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 图鉴面板(数据驱动,仿 <see cref="DataDrivenAbilityPanel"/>)。据 <see cref="BestiaryDatabaseSO"/> 列出全部敌人,
|
||
/// 经 <see cref="IBestiaryService"/>(ServiceLocator) 读已发现态:已发现亮显,未发现 "???"+剪影+锁。
|
||
/// 订阅 EVT_BestiaryUpdated 实时刷新;点击格子在右侧详情区展示名/描述/数值/击杀数;显示完成度 X/Y。
|
||
/// 菜单态(无游戏,service=null)下全部显示未发现。
|
||
/// </summary>
|
||
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<IBestiaryService>(); // 菜单态为 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<IBestiaryService>();
|
||
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<UIManager>();
|
||
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);
|
||
}
|
||
}
|
||
}
|