223 lines
6.3 KiB
C#
223 lines
6.3 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|