197 lines
8.8 KiB
C#
197 lines
8.8 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEditor; // 引入 UnityEditor 命名空间
|
||
|
||
namespace UI
|
||
{
|
||
[ExecuteInEditMode] // 允许脚本在编辑器模式下运行
|
||
public class SkillTreeNodeUI : MonoBehaviour
|
||
{
|
||
public SkillNodeEnterLineUI skillNodeEnterLineUI;
|
||
|
||
[Header("Editor Test Settings")]
|
||
public bool testModeActive = true; // 是否在编辑器模式下激活测试线的绘制
|
||
[Range(0, 20)] // 限制线条数量在0到20之间,防止意外生成过多
|
||
public int numberOfLinesToTest = 5; // 要生成多少条测试线
|
||
public Vector2 initialStartPointOffset = new Vector2(-300, 100); // 初始点的偏移
|
||
public Vector2 perLineOffset = new Vector2(0, -30); // 每条线之间的相对偏移
|
||
|
||
[Header("UI Node Settings")]
|
||
public float minNodeHeight = 70f; // UI节点的最小高度,即使没有线条也应保持此高度
|
||
|
||
private RectTransform _rectTransform; // 缓存自身的 RectTransform 组件
|
||
|
||
private void Awake()
|
||
{
|
||
_rectTransform = GetComponent<RectTransform>();
|
||
if (_rectTransform == null)
|
||
{
|
||
Debug.LogError("SkillTreeNodeUI requires a RectTransform component on this GameObject.", this);
|
||
}
|
||
}
|
||
|
||
private void Start()
|
||
{
|
||
// 确保 _rectTransform 在 Play Mode 中也可用
|
||
if (_rectTransform == null)
|
||
{
|
||
_rectTransform = GetComponent<RectTransform>();
|
||
}
|
||
|
||
// 在Play Mode启动时执行初始化。对于运行时,testModeActive(作为一个“编辑器测试设置”)通常不应阻止线条绘制。
|
||
// 因此,isEditorCall 参数传递 false。
|
||
InitializeLines(false);
|
||
}
|
||
|
||
// 当Inspector中的值发生改变或脚本被加载时调用
|
||
private void OnValidate()
|
||
{
|
||
// 确保只在编辑器模式且非运行时执行
|
||
if (!Application.isEditor || Application.isPlaying)
|
||
return;
|
||
|
||
// 调用初始化方法。对于编辑器调用,isEditorCall 参数传递 true,这将使 testModeActive 生效。
|
||
InitializeLines(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据当前设置初始化线条绘制。
|
||
/// 可以从外部调用以刷新线条。
|
||
/// </summary>
|
||
/// <param name="isEditorCall">如果为 true,表示是从编辑器上下文(如 OnValidate)调用,将尊重 testModeActive 设置。</param>
|
||
public void InitializeLines(bool isEditorCall)
|
||
{
|
||
if (_rectTransform == null)
|
||
{
|
||
_rectTransform = GetComponent<RectTransform>();
|
||
if (_rectTransform == null)
|
||
{
|
||
Debug.LogError("SkillTreeNodeUI requires a RectTransform component for initialization.", this);
|
||
ClearExistingLines(); // 即使缺少 RectTransform,也尝试清理避免残留
|
||
SetRectTransformHeight(0f); // 尝试重置高度
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (skillNodeEnterLineUI == null)
|
||
{
|
||
Debug.LogWarning("SkillNodeEnterLineUI reference is not set for initialization on " + gameObject.name + ". Cannot draw lines.", this);
|
||
ClearExistingLines();
|
||
SetRectTransformHeight(0f);
|
||
return;
|
||
}
|
||
|
||
ClearExistingLines(); // 始终在绘制新线前清理旧线
|
||
|
||
// 如果是编辑器调用且测试模式未激活,或者要绘制的线数为0,则不绘制新线并设置最小高度。
|
||
// 当 isEditorCall 为 false (运行时调用) 时,会忽略 testModeActive 的状态。
|
||
if ((isEditorCall && !testModeActive) || numberOfLinesToTest <= 0)
|
||
{
|
||
SetRectTransformHeight(0f);
|
||
return;
|
||
}
|
||
|
||
// 根据 Inspector 中的参数生成 startPoints
|
||
var startPoints = new List<Vector2>();
|
||
// 注意:transform.position 是世界坐标。对于UI,通常建议使用 RectTransform.anchoredPosition
|
||
// 或转换为 RectTransformUtility.ScreenPointToLocalPointInRectangle 的方式获取相对 Canvas 的本地坐标。
|
||
// 但为保持与原有逻辑一致性,且假设 skillNodeEnterLineUI 内部能正确处理世界坐标,暂不修改。
|
||
var currentPoint = (Vector2)transform.position + initialStartPointOffset;
|
||
startPoints.Add(currentPoint);
|
||
|
||
for (var i = 1; i < numberOfLinesToTest; i++)
|
||
{
|
||
currentPoint += perLineOffset; // 使用相对偏移
|
||
startPoints.Add(currentPoint);
|
||
}
|
||
|
||
// 调用 Init 来创建线并获取所需的高度
|
||
var requiredLineHeight = skillNodeEnterLineUI.Init(startPoints.ToArray());
|
||
|
||
// 立即设置 RectTransform 高度
|
||
SetRectTransformHeight(requiredLineHeight);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据所需行高设置自身的RectTransform高度。
|
||
/// 高度为 Max(requiredLineHeight + 40, minNodeHeight)。
|
||
/// </summary>
|
||
/// <param name="requiredLineHeight">SkillNodeEnterLineUI.Init方法返回的作为线终点区域的高度。</param>
|
||
private void SetRectTransformHeight(float requiredLineHeight)
|
||
{
|
||
if (_rectTransform == null)
|
||
{
|
||
Debug.LogWarning("RectTransform is null, cannot set height for " + transform.name + ".", this);
|
||
return;
|
||
}
|
||
|
||
var currentWidth = _rectTransform.sizeDelta.x; // 保持宽度不变
|
||
var currentHeight = _rectTransform.sizeDelta.y;
|
||
|
||
// 计算线条所需的最小高度,加上40的额外填充
|
||
var minNeededHeightWithPadding = requiredLineHeight + 40f;
|
||
|
||
// 目标高度为:线条所需的最小高度与 RectTransform 当前高度中的较大值,同时不小于 minNodeHeight。
|
||
// 这确保了RectTransform的高度只在需要时增加,且不会低于其当前的尺寸或设定的最小高度。
|
||
var targetHeight = Mathf.Max(minNeededHeightWithPadding, minNodeHeight);
|
||
|
||
// 只有当计算出的目标高度与当前高度存在显著差异时才进行更新,以避免不必要的UI刷新和场景脏化。
|
||
// 使用一个小小的epsilon值进行浮点数比较,以应对精度问题。
|
||
if (Mathf.Abs(currentHeight - targetHeight) > 0.1f)
|
||
{
|
||
_rectTransform.sizeDelta = new Vector2(currentWidth, targetHeight);
|
||
|
||
// 在编辑器模式下,标记RectTransform为脏(dirty),确保修改被保存到场景中。
|
||
// 仅当非Play Mode才需要SetDirty,且只对改变的组件。
|
||
if (!Application.isPlaying)
|
||
{
|
||
EditorUtility.SetDirty(_rectTransform);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除 skillNodeEnterLineUI 下所有已存在的子对象(连接线)。
|
||
/// 在编辑器模式下使用 EditorApplication.delayCall 延迟销毁,避免 OnValidate 的限制。
|
||
/// 在Play Mode下使用 Destroy。
|
||
/// </summary>
|
||
private void ClearExistingLines()
|
||
{
|
||
if (skillNodeEnterLineUI == null) return;
|
||
|
||
var childrenToDestroy = new List<GameObject>();
|
||
for (var i = skillNodeEnterLineUI.transform.childCount - 1; i >= 0; i--)
|
||
{
|
||
childrenToDestroy.Add(skillNodeEnterLineUI.transform.GetChild(i).gameObject);
|
||
}
|
||
|
||
if (Application.isPlaying)
|
||
{
|
||
foreach (var child in childrenToDestroy)
|
||
{
|
||
Destroy(child);
|
||
}
|
||
}
|
||
else // 在编辑器模式下
|
||
{
|
||
EditorApplication.delayCall += () => {
|
||
bool childrenWereDestroyed = false; // 增加一个标志,判断是否有子对象被实际销毁
|
||
foreach (var child in childrenToDestroy)
|
||
{
|
||
if (child != null)
|
||
{
|
||
DestroyImmediate(child);
|
||
childrenWereDestroyed = true;
|
||
}
|
||
}
|
||
// 如果有子对象被销毁且 skillNodeEnterLineUI 仍然存在,则标记其为脏
|
||
if (childrenWereDestroyed && skillNodeEnterLineUI != null)
|
||
{
|
||
EditorUtility.SetDirty(skillNodeEnterLineUI); // 标记 skillNodeEnterLineUI 组件为脏
|
||
}
|
||
};
|
||
}
|
||
}
|
||
}
|
||
}
|