2025-09-24 15:27:06 +08:00

223 lines
6.3 KiB
C#
Raw 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 UnityEngine;
using UnityEngine.UI;
[ExecuteInEditMode]
public class ObjectUILinker : MonoBehaviour
{
[Tooltip("需要连接的3D物体")]
public Transform object3D; // 3D物体a
[Tooltip("2D UI提示牌")]
public RectTransform uiElement; // 2D UI提示牌b
[Tooltip("连线的颜色")]
public Color lineColor = Color.red;
[Tooltip("连线的宽度")]
public float lineWidth = 2f;
[Tooltip("编辑模式下是否显示连线")]
public bool showInEditMode = true;
[Tooltip("连线的渲染层级,值越大越靠上")]
public int sortingOrder = 30000;
private Image lineImage;
private RectTransform lineRect;
private Canvas canvas;
private Camera mainCamera;
private Canvas lineCanvas;
void Awake()
{
SetupLineCanvas();
SetupLineImage();
}
void Start()
{
UpdateReferences();
}
// 创建专门用于连线的Canvas
void SetupLineCanvas()
{
// 查找或创建连线专用Canvas
lineCanvas = GetComponent<Canvas>();
if (lineCanvas == null)
{
lineCanvas = gameObject.AddComponent<Canvas>();
}
// 关键设置确保Canvas在最上层
lineCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
lineCanvas.overrideSorting = true;
lineCanvas.sortingOrder = sortingOrder;
// 添加必要的Canvas组件
if (GetComponent<CanvasScaler>() == null)
{
CanvasScaler scaler = gameObject.AddComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = new Vector2(1920, 1080);
}
if (GetComponent<GraphicRaycaster>() == null)
{
gameObject.AddComponent<GraphicRaycaster>();
}
}
// 创建用于显示连线的Image
void SetupLineImage()
{
// 查找或创建连线Image
lineImage = GetComponent<Image>();
if (lineImage == null)
{
lineImage = gameObject.AddComponent<Image>();
}
// 使用纯色纹理作为连线
lineImage.color = lineColor;
lineImage.raycastTarget = false; // 确保连线不阻挡UI交互
// 获取或创建RectTransform
lineRect = GetComponent<RectTransform>();
if (lineRect == null)
{
lineRect = gameObject.AddComponent<RectTransform>();
}
// 设置锚点,使连线位置计算更简单
lineRect.anchorMin = Vector2.zero;
lineRect.anchorMax = Vector2.zero;
lineRect.pivot = new Vector2(0, 0.5f);
}
void Update()
{
// 实时更新渲染层级
if (lineCanvas != null && lineCanvas.sortingOrder != sortingOrder)
{
lineCanvas.sortingOrder = sortingOrder;
}
// 根据运行状态和设置决定是否更新连线
if (Application.isPlaying)
{
if (mainCamera == null) UpdateReferences();
UpdateLine();
}
else
{
if (showInEditMode)
{
UpdateReferences();
UpdateLine();
}
}
}
// 更新必要的引用
void UpdateReferences()
{
if (mainCamera == null)
mainCamera = Camera.main;
if (uiElement != null && canvas == null)
canvas = uiElement.GetComponentInParent<Canvas>();
}
// 更新连线的位置、角度和长度
void UpdateLine()
{
// 检查必要引用
if (object3D == null || uiElement == null || canvas == null || mainCamera == null)
{
if (lineImage != null)
lineImage.enabled = false;
return;
}
lineImage.enabled = true;
// 将3D物体位置转换为屏幕坐标
Vector3 objectScreenPos = mainCamera.WorldToScreenPoint(object3D.position);
// 转换屏幕坐标为UI坐标针对UI所在的Canvas
Vector2 objectUIPos;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
canvas.GetComponent<RectTransform>(),
objectScreenPos,
canvas.worldCamera,
out objectUIPos
);
// 转换为世界空间位置
Vector3 worldPosA = canvas.GetComponent<RectTransform>().TransformPoint(objectUIPos);
// 获取UI元素的世界空间位置
Vector3 worldPosB = uiElement.position;
// 将两个点转换为连线Canvas的局部坐标
Vector2 localPosA = lineCanvas.GetComponent<RectTransform>().InverseTransformPoint(worldPosA);
Vector2 localPosB = lineCanvas.GetComponent<RectTransform>().InverseTransformPoint(worldPosB);
// 计算连线长度
float distance = Vector2.Distance(localPosA, localPosB);
// 计算连线角度
float angle = Mathf.Atan2(localPosB.y - localPosA.y, localPosB.x - localPosA.x) * Mathf.Rad2Deg;
// 设置连线位置(起点)
lineRect.anchoredPosition = localPosA;
// 设置连线旋转
lineRect.rotation = Quaternion.Euler(0, 0, angle);
// 设置连线尺寸
lineRect.sizeDelta = new Vector2(distance, lineWidth);
}
// 在场景视图中绘制辅助线
void OnDrawGizmos()
{
if (!showInEditMode) return;
if (object3D != null && uiElement != null)
{
UpdateReferences();
if (mainCamera == null || canvas == null) return;
Gizmos.color = lineColor;
Vector3 objectScreenPos = mainCamera.WorldToScreenPoint(object3D.position);
Vector2 objectUIPos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
canvas.GetComponent<RectTransform>(),
objectScreenPos,
canvas.worldCamera,
out objectUIPos))
{
Vector3 worldPosA = canvas.GetComponent<RectTransform>().TransformPoint(objectUIPos);
Vector3 worldPosB = uiElement.position;
Gizmos.DrawLine(worldPosA, worldPosB);
}
}
}
// 当属性变化时更新
void OnValidate()
{
if (lineCanvas != null)
{
lineCanvas.sortingOrder = sortingOrder;
}
if (lineImage != null)
{
lineImage.color = lineColor;
}
}
}