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 { /// /// 形态技能一览面板(架构 10_UIModule §Panel)。 /// 只读面板:按形态展示各技能槽的技能资料(名称、描述、消耗、冷却),不含任何解锁/技能点交互。 /// 支持左右翻页浏览各形态,并高亮当前实际使用的形态。 /// /// Inspector 必填: /// _formSkillSets — 每个形态对应一条配置(soulSkill + spirit1 + spirit2) /// _formTitleText — 显示当前形态标签名的文本 /// _prevFormBtn / _nextFormBtn — 翻页按钮 /// _activeFormIndicator — 高亮"当前形态"的标志(可选,如小圆点) /// _skillSlots — 3 个 SkillSlotView 引用(Soul / Spirit1 / Spirit2) /// _onFormChanged — IntEventChannelSO,FormController 广播当前形态 index /// 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(); if (uiMgr != null) uiMgr.CloseTopPanel(); else gameObject.SetActive(false); } } }