Files
zeling_v2/Assets/_Game/Scripts/UI/BestiaryPanel.cs
2026-06-08 11:26:17 +08:00

169 lines
7.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
}
}
}