Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/UI/SkillNodeEnterLineUI.cs

150 lines
7.6 KiB
C#
Raw Permalink 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;
namespace UI
{
/// <summary>
/// 技能节点入口连接线UI管理器。
/// 负责生成、管理和清除技能节点进入线的UI表示。
/// 这些线通常连接技能树中的多个起始点到一个公共的出口区域。
/// </summary>
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;
/// <summary>
/// 初始化连接线并根据起始点数组绘制连接路径。
/// </summary>
/// <param name="startPoints">连接线的起始点数组,通常按从高到低排列。</param>
/// <returns>所有连接线结束点所占据的最小总垂直高度如果无连接线则返回0。</returns>
public float Init(Vector2[] startPoints)
{
ClearExistingLines();
// 检查预制体是否已通过Inspector赋值如果为null则尝试从Resources加载。
if (skillNodeLinkLineUIPrefab == null)
{
// 从Resources文件夹加载预制体。
skillNodeLinkLineUIPrefab = Resources.Load<SkillNodeLinkLineUI>("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;
}
/// <summary>
/// 根据预期的连接线数量,预先计算所有连接线结束点将占据的最小总垂直高度。
/// 这个方法不会实际创建任何连接线,仅用于高度预估。
/// </summary>
/// <param name="numNodes">预期的连接线数量。</param>
/// <returns>所有连接线结束点所占据的最小总垂直高度如果无连接线则返回0。</returns>
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;
}
/// <summary>
/// 清除所有当前作为该GameObject子对象的连接线。
/// 在播放模式下使用 Destroy在编辑器模式下使用 DestroyImmediate。
/// </summary>
private void ClearExistingLines()
{
var childrenToDestroy = new List<GameObject>();
for (var i = transform.childCount - 1; i >= 0; i--) childrenToDestroy.Add(transform.GetChild(i).gameObject);
foreach (var child in childrenToDestroy) Destroy(child);
}
}
}