UI 系统
This commit is contained in:
168
Assets/_Game/Scripts/UI/BestiaryPanel.cs
Normal file
168
Assets/_Game/Scripts/UI/BestiaryPanel.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user