refactor(ui): SkillTreePanel 改名为 FormSkillPanel
该面板实为只读的形态技能一览(按形态翻页展示技能图标/名称/描述/消耗/冷却), 不含节点/前置/技能点/解锁交互。原名 SkillTree 易误解为可交互技能树,故改名为 FormSkillPanel 以反映真实职责。.meta GUID 保留,无 prefab/场景引用。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
220
Assets/_Game/Scripts/UI/FormSkillPanel.cs
Normal file
220
Assets/_Game/Scripts/UI/FormSkillPanel.cs
Normal file
@@ -0,0 +1,220 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Skills;
|
||||
using BaseGames.Localization;
|
||||
|
||||
namespace BaseGames.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 形态技能一览面板(架构 10_UIModule §Panel)。
|
||||
/// 只读面板:按形态展示各技能槽的技能资料(名称、描述、消耗、冷却),不含任何解锁/技能点交互。
|
||||
/// 支持左右翻页浏览各形态,并高亮当前实际使用的形态。
|
||||
///
|
||||
/// Inspector 必填:
|
||||
/// _formSkillSets — 每个形态对应一条配置(soulSkill + spirit1 + spirit2)
|
||||
/// _formTitleText — 显示当前形态标签名的文本
|
||||
/// _prevFormBtn / _nextFormBtn — 翻页按钮
|
||||
/// _activeFormIndicator — 高亮"当前形态"的标志(可选,如小圆点)
|
||||
/// _skillSlots — 3 个 SkillSlotView 引用(Soul / Spirit1 / Spirit2)
|
||||
/// _onFormChanged — IntEventChannelSO,FormController 广播当前形态 index
|
||||
/// </summary>
|
||||
public class FormSkillPanel : MonoBehaviour
|
||||
{
|
||||
// ── 子数据结构 ────────────────────────────────────────────────────────
|
||||
|
||||
[System.Serializable]
|
||||
public struct FormSkillConfig
|
||||
{
|
||||
[Tooltip("形态标题的本地化 Key,格式如 \"Form_SkyWarden_Label\",由 LocalizationManager.Get(…, LocalizationTable.UI) 查找。")]
|
||||
public string formLabelKey;
|
||||
[Tooltip("天魂/地魂/命魂的「魂技」")]
|
||||
public FormSkillSO soulSkill;
|
||||
[Tooltip("灵技 1(Y / Triangle / 手柄按钮)")]
|
||||
public FormSkillSO spiritSkill1;
|
||||
[Tooltip("灵技 2(Shift+Y / L2+Triangle 等)")]
|
||||
public FormSkillSO spiritSkill2;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct SkillSlotView
|
||||
{
|
||||
[Tooltip("技能图标")]
|
||||
public Image icon;
|
||||
[Tooltip("技能名称文本")]
|
||||
public TMP_Text nameText;
|
||||
[Tooltip("技能描述文本")]
|
||||
public TMP_Text descText;
|
||||
[Tooltip("消耗数值文本(如 \"魂力 25\")")]
|
||||
public TMP_Text costText;
|
||||
[Tooltip("冷却数值文本(如 \"CD 3.5s\")")]
|
||||
public TMP_Text cooldownText;
|
||||
[Tooltip("无技能时隐藏的根 GameObject")]
|
||||
public GameObject root;
|
||||
}
|
||||
|
||||
// ── Inspector 字段 ────────────────────────────────────────────────────
|
||||
|
||||
[Header("形态技能配置")]
|
||||
[Tooltip("每个元素对应一种形态的三项技能,顺序应与 FormType 枚举序号一致。")]
|
||||
[SerializeField] private FormSkillConfig[] _formSkillSets;
|
||||
|
||||
[Header("UI 导航")]
|
||||
[SerializeField] private TMP_Text _formTitleText;
|
||||
[SerializeField] private Button _prevFormBtn;
|
||||
[SerializeField] private Button _nextFormBtn;
|
||||
[Tooltip("高亮当前实际装备形态的指示器(可选)")]
|
||||
[SerializeField] private GameObject _activeFormIndicator;
|
||||
|
||||
[Header("技能卡槽(Soul / Spirit1 / Spirit2)")]
|
||||
[SerializeField] private SkillSlotView _soulSlot;
|
||||
[SerializeField] private SkillSlotView _spiritSlot1;
|
||||
[SerializeField] private SkillSlotView _spiritSlot2;
|
||||
|
||||
[Header("关闭按钮")]
|
||||
[SerializeField] private Button _btnClose;
|
||||
|
||||
[Header("Event Channels")]
|
||||
[Tooltip("FormController 广播当前形态切换:payload = FormType 的整数序号(0=天魂,1=地魂,2=命魂)")]
|
||||
[SerializeField] private IntEventChannelSO _onFormChanged;
|
||||
|
||||
// ── 私有状态 ──────────────────────────────────────────────────────────
|
||||
|
||||
private int _viewIndex = 0; // 当前正在查看的形态
|
||||
private int _activeIndex = 0; // 玩家当前实际使用的形态
|
||||
private readonly CompositeDisposable _subs = new();
|
||||
|
||||
// ── 生命周期 ──────────────────────────────────────────────────────────
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_prevFormBtn?.onClick.AddListener(PrevForm);
|
||||
_nextFormBtn?.onClick.AddListener(NextForm);
|
||||
_btnClose?.onClick.AddListener(OnCloseClicked);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_onFormChanged?.Subscribe(OnFormChanged).AddTo(_subs);
|
||||
// 打开时自动跳到当前形态
|
||||
_viewIndex = _activeIndex;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_subs.Clear();
|
||||
}
|
||||
|
||||
// ── 事件处理 ──────────────────────────────────────────────────────────
|
||||
|
||||
private void OnFormChanged(int formIndex)
|
||||
{
|
||||
_activeIndex = formIndex;
|
||||
if (_activeFormIndicator != null)
|
||||
_activeFormIndicator.SetActive(_viewIndex == _activeIndex);
|
||||
}
|
||||
|
||||
// ── 导航 ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void PrevForm()
|
||||
{
|
||||
if (_formSkillSets == null || _formSkillSets.Length == 0) return;
|
||||
_viewIndex = (_viewIndex - 1 + _formSkillSets.Length) % _formSkillSets.Length;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void NextForm()
|
||||
{
|
||||
if (_formSkillSets == null || _formSkillSets.Length == 0) return;
|
||||
_viewIndex = (_viewIndex + 1) % _formSkillSets.Length;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// ── 显示刷新 ──────────────────────────────────────────────────────────
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
if (_formSkillSets == null || _formSkillSets.Length == 0) return;
|
||||
_viewIndex = Mathf.Clamp(_viewIndex, 0, _formSkillSets.Length - 1);
|
||||
|
||||
var cfg = _formSkillSets[_viewIndex];
|
||||
|
||||
// 标题(走本地化管道,fallback 为 key 本身)
|
||||
if (_formTitleText != null)
|
||||
{
|
||||
string label = !string.IsNullOrEmpty(cfg.formLabelKey)
|
||||
? LocalizationManager.Get(cfg.formLabelKey, LocalizationTable.UI)
|
||||
: string.Empty;
|
||||
_formTitleText.text = string.IsNullOrEmpty(label) || label == cfg.formLabelKey
|
||||
? cfg.formLabelKey // fallback:直接显示 Key
|
||||
: label;
|
||||
}
|
||||
|
||||
// 当前形态高亮指示器
|
||||
if (_activeFormIndicator != null)
|
||||
_activeFormIndicator.SetActive(_viewIndex == _activeIndex);
|
||||
|
||||
// 翻页按钮可见性(只有一种形态时隐藏)
|
||||
bool multiForm = _formSkillSets.Length > 1;
|
||||
if (_prevFormBtn != null) _prevFormBtn.gameObject.SetActive(multiForm);
|
||||
if (_nextFormBtn != null) _nextFormBtn.gameObject.SetActive(multiForm);
|
||||
|
||||
// 技能卡槽
|
||||
FillSlot(ref _soulSlot, cfg.soulSkill);
|
||||
FillSlot(ref _spiritSlot1, cfg.spiritSkill1);
|
||||
FillSlot(ref _spiritSlot2, cfg.spiritSkill2);
|
||||
}
|
||||
|
||||
private static void FillSlot(ref SkillSlotView slot, FormSkillSO skill)
|
||||
{
|
||||
bool hasSkill = skill != null;
|
||||
if (slot.root != null) slot.root.SetActive(hasSkill);
|
||||
if (!hasSkill) return;
|
||||
|
||||
if (slot.icon != null)
|
||||
{
|
||||
slot.icon.sprite = skill.icon;
|
||||
slot.icon.enabled = skill.icon != null;
|
||||
}
|
||||
|
||||
if (slot.nameText != null)
|
||||
{
|
||||
string name = LocalizationManager.Get(skill.displayNameKey, LocalizationTable.UI);
|
||||
slot.nameText.text = string.IsNullOrEmpty(name) || name == skill.displayNameKey
|
||||
? skill.skillId
|
||||
: name;
|
||||
}
|
||||
|
||||
if (slot.descText != null)
|
||||
{
|
||||
string desc = LocalizationManager.Get(skill.descriptionKey, LocalizationTable.UI);
|
||||
slot.descText.text = string.IsNullOrEmpty(desc) || desc == skill.descriptionKey
|
||||
? string.Empty
|
||||
: desc;
|
||||
}
|
||||
|
||||
if (slot.costText != null)
|
||||
slot.costText.text = skill.baseCost > 0
|
||||
? $"{skill.resourceType} {skill.baseCost}"
|
||||
: string.Empty;
|
||||
|
||||
if (slot.cooldownText != null)
|
||||
slot.cooldownText.text = skill.cooldown > 0f
|
||||
? $"CD {skill.cooldown:0.#}s"
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
// ── 关闭 ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void OnCloseClicked()
|
||||
{
|
||||
var uiMgr = ServiceLocator.GetOrDefault<UIManager>();
|
||||
if (uiMgr != null) uiMgr.CloseTopPanel();
|
||||
else gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user