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);
}
}
}