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(); if (lineCanvas == null) { lineCanvas = gameObject.AddComponent(); } // 关键设置:确保Canvas在最上层 lineCanvas.renderMode = RenderMode.ScreenSpaceOverlay; lineCanvas.overrideSorting = true; lineCanvas.sortingOrder = sortingOrder; // 添加必要的Canvas组件 if (GetComponent() == null) { CanvasScaler scaler = gameObject.AddComponent(); scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; scaler.referenceResolution = new Vector2(1920, 1080); } if (GetComponent() == null) { gameObject.AddComponent(); } } // 创建用于显示连线的Image void SetupLineImage() { // 查找或创建连线Image lineImage = GetComponent(); if (lineImage == null) { lineImage = gameObject.AddComponent(); } // 使用纯色纹理作为连线 lineImage.color = lineColor; lineImage.raycastTarget = false; // 确保连线不阻挡UI交互 // 获取或创建RectTransform lineRect = GetComponent(); if (lineRect == null) { lineRect = gameObject.AddComponent(); } // 设置锚点,使连线位置计算更简单 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(); } // 更新连线的位置、角度和长度 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(), objectScreenPos, canvas.worldCamera, out objectUIPos ); // 转换为世界空间位置 Vector3 worldPosA = canvas.GetComponent().TransformPoint(objectUIPos); // 获取UI元素的世界空间位置 Vector3 worldPosB = uiElement.position; // 将两个点转换为连线Canvas的局部坐标 Vector2 localPosA = lineCanvas.GetComponent().InverseTransformPoint(worldPosA); Vector2 localPosB = lineCanvas.GetComponent().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(), objectScreenPos, canvas.worldCamera, out objectUIPos)) { Vector3 worldPosA = canvas.GetComponent().TransformPoint(objectUIPos); Vector3 worldPosB = uiElement.position; Gizmos.DrawLine(worldPosA, worldPosB); } } } // 当属性变化时更新 void OnValidate() { if (lineCanvas != null) { lineCanvas.sortingOrder = sortingOrder; } if (lineImage != null) { lineImage.color = lineColor; } } }