Files
zeling_v2/Assets/_Game/Scripts/UI/FormSkillPanel.cs
Joywayer ff0f3bde54 refactor(ui): SkillTreePanel 改名为 FormSkillPanel
该面板实为只读的形态技能一览(按形态翻页展示技能图标/名称/描述/消耗/冷却),
不含节点/前置/技能点/解锁交互。原名 SkillTree 易误解为可交互技能树,故改名为
FormSkillPanel 以反映真实职责。.meta GUID 保留,无 prefab/场景引用。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 11:40:28 +08:00

221 lines
9.6 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 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 — IntEventChannelSOFormController 广播当前形态 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("灵技 1Y / Triangle / 手柄按钮)")]
public FormSkillSO spiritSkill1;
[Tooltip("灵技 2Shift+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);
}
}
}