using System.Collections.Generic; using Data; using Managers; using UnityEngine; using UnityEditor; using TMPro; using UnityEngine.EventSystems; using UnityEngine.UI; namespace UI { /// /// 技能树节点组件。 /// public class SkillTreeNodeUI : MonoBehaviour,IPointerClickHandler { /// /// 节点的RectTransform组件。 /// public RectTransform rectTransform; /// /// 节点的最小高度。 /// [SerializeField] private float NODE_MIN_HEIGHT = 39.0122f * 2; /// /// 两条连接线结束点之间的最小垂直间距。 /// [SerializeField] private float MIN_VERTICAL_SPACING = 12.9447f; /// /// 技能节点入口线UI组件。 /// public SkillNodeEnterLineUI skillNodeEnterLineUI; /// /// 输出线连接点的参考或容器。 /// public RectTransform outputHead; /// /// 文本显示组件。 /// public TMP_Text text; public SkillTreeDef SkillTreeDefine { get; private set; } public Image lockedNodeImage; [SerializeField] private Sprite unlockedSprite; [SerializeField] private Sprite lockedSprite; [SerializeField] private Color unlockedColor; [SerializeField] private Color lockedColor; [SerializeField] private GameObject shader; public void Init(SkillTreeDef skillTreeDef) { SkillTreeDefine = skillTreeDef; text.text = skillTreeDef.label; Refresh(); } public void Refresh() { var isSkillTreeUnlocked = SkillTreeManager.Instance.IsSkillTreeUnlocked(SkillTreeDefine.defName); shader.SetActive(!isSkillTreeUnlocked); lockedNodeImage.sprite = isSkillTreeUnlocked ? unlockedSprite : lockedSprite; lockedNodeImage.color = isSkillTreeUnlocked ? unlockedColor : lockedColor; } /// /// 初始化技能树节点UI。 /// /// 连接线的点数组。 public void LinkLine(Vector2[] linkLinePoints) { if (linkLinePoints is { Length: > 0 }) { var height = skillNodeEnterLineUI.GetRequiredHeight(linkLinePoints.Length); height = Mathf.Max(height + NODE_MIN_HEIGHT / 2 + 10, NODE_MIN_HEIGHT); rectTransform.sizeDelta = new Vector2(rectTransform.sizeDelta.x, height); skillNodeEnterLineUI.Init(linkLinePoints); } } /// /// 预先计算技能树节点在初始化后所需的最小高度。 /// 此方法可以在调用 Init 之前用于布局计算。 /// /// 连接线(入口线)的数量。 /// 节点所需的最小高度。 public float GetRequiredNodeHeight(int linkLineCount) { // 如果没有连接线或 skillNodeEnterLineUI 组件为空,则高度为最小高度 if (linkLineCount <= 0 || skillNodeEnterLineUI == null) { return NODE_MIN_HEIGHT; } // 获取 SkillNodeEnterLineUI 根据连接线数量计算出的所需高度 var calculatedLineHeight = skillNodeEnterLineUI.GetRequiredHeight(linkLineCount); // 结合最小节点高度和额外的填充,确保节点有足够的空间 // 逻辑与 Init() 方法中的高度计算保持一致 var finalHeight = Mathf.Max(calculatedLineHeight + NODE_MIN_HEIGHT / 2 + 10, NODE_MIN_HEIGHT); return finalHeight; } /// /// 查询出口位置。 /// /// 总数量。 /// 位置(从上往下,0-based)。 /// 世界坐标。 public Vector2 GetOutputPosition(int totalCount, int index) { // 输入验证,防止无效的totalCount或index导致错误 if (totalCount <= 0 || index < 0 || index >= totalCount) { Debug.LogWarning($"获取输出位置时,总数 ({totalCount}) 或索引 ({index}) 无效。返回outputHead的默认位置。"); // 返回outputHead的中心位置作为备用,或者Vector2.zero取决于具体需求 return outputHead.position; } // 1. 计算 X 坐标:outputHead 的右边界 var corners = new Vector3[4]; outputHead.GetWorldCorners(corners); // corners[2] 是右上角的世界坐标点 var worldX = corners[2].x; // 2. 计算 Y 坐标:以 outputHead 的中心Y坐标为基准进行偏移 var worldY = outputHead.position.y; if (totalCount == 2) { // 特殊情况:当输出点为2个时,点1为(x,y+d),点2为(x,y-d),d为MIN_VERTICAL_SPACING if (index == 0) // 第一个点 (最上面的点) { worldY += MIN_VERTICAL_SPACING; } else // 第二个点 (最下面的点) { worldY -= MIN_VERTICAL_SPACING; } } else { // 一般情况:多个点均匀纵向排列,以outputHead的Y为中心 // 计算最顶部的点 (index=0) 相对于中心点Y的偏移量 // 总共有 (totalCount - 1) 个 MIN_VERTICAL_SPACING 的间距 // 索引为 index 的点的偏移量为: // ( (totalCount - 1) / 2.0f - index ) * MIN_VERTICAL_SPACING var halfTotalSpacing = (totalCount - 1) / 2.0f; var yOffset = (halfTotalSpacing - index) * MIN_VERTICAL_SPACING; worldY += yOffset; } return new Vector2(worldX, worldY); } public void OnPointerClick(PointerEventData eventData) { if (eventData.button == PointerEventData.InputButton.Left) { SkillTreeManager.Instance.UnlockSkillTree(SkillTreeDefine.defName); Refresh(); } else if (eventData.button == PointerEventData.InputButton.Right) { SkillTreeManager.Instance.LockSkillTree(SkillTreeDefine.defName); Refresh(); } } } }