using System.Collections.Generic; using UnityEngine; namespace UI { /// /// 技能节点入口连接线UI管理器。 /// 负责生成、管理和清除技能节点进入线的UI表示。 /// 这些线通常连接技能树中的多个起始点到一个公共的出口区域。 /// public class SkillNodeEnterLineUI : MonoBehaviour { private const float MIN_VERTICAL_SPACING = 12.9447f; // 两条连接线结束点之间的最小垂直间距 private const float FIRST_POINT_Y_OFFSET = 39.0122f; // 第一个(最低的)结束点相对于RectTransform底部的Y轴偏移量 [SerializeField] private SkillNodeLinkLineUI skillNodeLinkLineUIPrefab; [SerializeField] private RectTransform rectTransform; /// /// 初始化连接线并根据起始点数组绘制连接路径。 /// /// 连接线的起始点数组,通常按从高到低排列。 /// 所有连接线结束点所占据的最小总垂直高度,如果无连接线则返回0。 public float Init(Vector2[] startPoints) { ClearExistingLines(); // 检查预制体是否已通过Inspector赋值,如果为null,则尝试从Resources加载。 if (skillNodeLinkLineUIPrefab == null) { // 从Resources文件夹加载预制体。 skillNodeLinkLineUIPrefab = Resources.Load("Prefab/SkillTree/linkLine"); if (skillNodeLinkLineUIPrefab == null) { // 打印错误日志。 Debug.LogError("初始化失败:技能节点连接线预制体未在 'Prefab/SkillTree/linkLine' 路径找到。请检查路径或在Inspector中赋值。"); return 0f; // 预制体缺失,返回0高度。 } } // 获取起始点的数量。 var numLines = startPoints.Length; if (numLines == 0) // 没有起始点,无需绘制,返回0高度。 return GetRequiredHeight(0); // 使用统一的获取高度方法 // 计算所有连接线共享的基准结束点X坐标。 var commonEndPointX = transform.position.x - 6f; // 计算RectTransform的世界坐标底部Y值。 var rectBottomWorldY = rectTransform.position.y + rectTransform.rect.yMin * rectTransform.lossyScale.y; // 最低结束点Y坐标在此基础上向上偏移。 var lowestEndPointBaseY = rectBottomWorldY + FIRST_POINT_Y_OFFSET; // 初始化所有线结束点所占据的总垂直跨度。 var requiredHeight = 0f; // 根据连接线的数量,应用不同的布局逻辑。 if (numLines == 1) { // 只有一个起始点时,其结束点直接放在最低点偏移量上。 var endPointY = lowestEndPointBaseY; var endPoint = new Vector2(commonEndPointX, endPointY); var line = Instantiate(skillNodeLinkLineUIPrefab, transform); line.SetConnectionPoints(startPoints[0], endPoint); // 只有一个点时,垂直高度跨度为0。 requiredHeight = GetRequiredHeight(1); } else if (numLines == 2) { // 仅有两个点时,它们占据“1号位”和“3号位”(即间隔是2倍的最小间距)。 // startPoints[0](最高的起始点)连接到最上面的结束点。 // startPoints[1](最低的起始点)连接到最下面的结束点。 var endPointY_for_startPoints0 = lowestEndPointBaseY + 2 * MIN_VERTICAL_SPACING; // 上方的结束点 var endPointY_for_startPoints1 = lowestEndPointBaseY; // 下方的结束点 var line0 = Instantiate(skillNodeLinkLineUIPrefab, transform); line0.SetConnectionPoints(startPoints[0], new Vector2(commonEndPointX, endPointY_for_startPoints0)); var line1 = Instantiate(skillNodeLinkLineUIPrefab, transform); line1.SetConnectionPoints(startPoints[1], new Vector2(commonEndPointX, endPointY_for_startPoints1)); // 两个点的高度跨度为它们之间的垂直距离。 requiredHeight = GetRequiredHeight(2); } else // numLines > 2 (通用情况:从 lowestEndPointBaseY 向上累积) { // 遍历起始点数组,创建并设置每条连接线。 // startPoints[i] 映射到相应的结束点Y坐标,确保从上到下顺序连接,避免交叉。 for (var i = 0; i < numLines; i++) { // 计算当前线的结束点Y坐标。 // 逻辑:i=0(最高的startPoint) 映射到 highestEndPoint (lowestEndPointBaseY + (numLines - 1) * MIN_VERTICAL_SPACING) // i=numLines-1(最低的startPoint) 映射到 lowestEndPoint (lowestEndPointBaseY + 0 * MIN_VERTICAL_SPACING) var currentEndPointY = lowestEndPointBaseY + (numLines - 1 - i) * MIN_VERTICAL_SPACING; var endPoint = new Vector2(commonEndPointX, currentEndPointY); var line = Instantiate(skillNodeLinkLineUIPrefab, transform); line.SetConnectionPoints(startPoints[i], endPoint); } // 计算所有连接线结束点所占据的总垂直高度。 requiredHeight = GetRequiredHeight(numLines); } // 返回所有连接线的结束点所占据的最小总垂直高度。 return requiredHeight; } /// /// 根据预期的连接线数量,预先计算所有连接线结束点将占据的最小总垂直高度。 /// 这个方法不会实际创建任何连接线,仅用于高度预估。 /// /// 预期的连接线数量。 /// 所有连接线结束点所占据的最小总垂直高度,如果无连接线则返回0。 public float GetRequiredHeight(int numNodes) { var requiredHeight = 0f; if (numNodes <= 0) requiredHeight = 0f; else if (numNodes == 1) // 与Init方法中只有一个起始点时的逻辑保持一致 requiredHeight = 0f; else if (numNodes == 2) // 与Init方法中只有两个起始点时的特殊逻辑保持一致 // 两个点的高度跨度为2倍的最小垂直间距 requiredHeight = 2 * MIN_VERTICAL_SPACING; else // numNodes > 2 (通用情况逻辑) // 与Init方法中多于两个起始点时的通用逻辑保持一致 // n个点占据 (n-1) * 最小垂直间距 requiredHeight = (numNodes - 1) * MIN_VERTICAL_SPACING; return requiredHeight; } /// /// 清除所有当前作为该GameObject子对象的连接线。 /// 在播放模式下使用 Destroy,在编辑器模式下使用 DestroyImmediate。 /// private void ClearExistingLines() { var childrenToDestroy = new List(); for (var i = transform.childCount - 1; i >= 0; i--) childrenToDestroy.Add(transform.GetChild(i).gameObject); foreach (var child in childrenToDestroy) Destroy(child); } } }