EnergyEfficiencyManagement/Assets/Zion/Scripts/Utils/DragUI.cs

226 lines
7.9 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.EventSystems;
using System.Collections.Generic;
[RequireComponent(typeof(RectTransform))]
public class DragUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
// 拖拽参考点
private Vector2 m_dragOffset = Vector2.zero;
private RectTransform m_rectTransform;
private Canvas m_canvas;
[Header("边界限制")]
[Tooltip("是否将UI限制在屏幕边界内")]
public bool clampToScreen = true;
[Tooltip("限制的边距(像素)")]
public Vector4 screenMargin = Vector4.zero; // 左, 下, 右, 上
[Header("避让其他UI (可选)")]
[Tooltip("需要避开的UI矩形列表。如果列表为空则不检查遮挡。")]
public List<RectTransform> avoidTheseRects = new List<RectTransform>();
[Tooltip("避让的缓冲间距(像素)")]
public float avoidancePadding = 10f;
private void Start()
{
m_rectTransform = GetComponent<RectTransform>();
// 向上查找Canvas
m_canvas = GetComponentInParent<Canvas>();
if (m_canvas == null)
{
Debug.LogError("此脚本需要在一个Canvas的子物体上", this);
}
}
public void OnBeginDrag(PointerEventData eventData)
{
// 计算鼠标点击点与UI中心点的偏移量
Vector2 localPointerPosition;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_rectTransform, eventData.position, eventData.pressEventCamera, out localPointerPosition))
{
m_dragOffset = m_rectTransform.anchoredPosition - localPointerPosition;
}
}
public void OnDrag(PointerEventData eventData)
{
if (m_rectTransform == null || m_canvas == null) return;
// 1. 先将UI移动到鼠标位置应用偏移
Vector2 localPointerPosition;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_rectTransform.parent as RectTransform, eventData.position, eventData.pressEventCamera, out localPointerPosition))
{
m_rectTransform.anchoredPosition = localPointerPosition + m_dragOffset;
}
// 2. 应用屏幕边界约束
if (clampToScreen)
{
ClampToScreen();
}
// 3. 应用避让规则
if (avoidTheseRects != null && avoidTheseRects.Count > 0)
{
AvoidOverlap();
}
}
public void OnEndDrag(PointerEventData eventData)
{
// 拖拽结束时再次确保约束,防止在结束瞬间甩出去
if (clampToScreen)
{
ClampToScreen();
}
if (avoidTheseRects != null && avoidTheseRects.Count > 0)
{
AvoidOverlap();
}
}
/// <summary>
/// 将UI限制在屏幕范围内
/// </summary>
private void ClampToScreen()
{
Vector3[] corners = new Vector3[4];
m_rectTransform.GetWorldCorners(corners);
// 根据Canvas的渲染模式处理屏幕坐标
Rect canvasRect = GetCanvasRect();
Rect uiRect = GetWorldRectFromCorners(corners);
Vector2 clampedPosition = m_rectTransform.anchoredPosition;
// 左边界
if (uiRect.xMin < canvasRect.xMin + screenMargin.x)
{
float overflow = canvasRect.xMin + screenMargin.x - uiRect.xMin;
clampedPosition.x += overflow;
}
// 右边界
if (uiRect.xMax > canvasRect.xMax - screenMargin.z)
{
float overflow = uiRect.xMax - (canvasRect.xMax - screenMargin.z);
clampedPosition.x -= overflow;
}
// 下边界
if (uiRect.yMin < canvasRect.yMin + screenMargin.y)
{
float overflow = canvasRect.yMin + screenMargin.y - uiRect.yMin;
clampedPosition.y += overflow;
}
// 上边界
if (uiRect.yMax > canvasRect.yMax - screenMargin.w)
{
float overflow = uiRect.yMax - (canvasRect.yMax - screenMargin.w);
clampedPosition.y -= overflow;
}
m_rectTransform.anchoredPosition = clampedPosition;
}
/// <summary>
/// 尝试调整位置以避免与指定的UI重叠
/// </summary>
private void AvoidOverlap()
{
Rect myRect = GetWorldRect(m_rectTransform);
foreach (var otherRectTransform in avoidTheseRects)
{
if (otherRectTransform == null || otherRectTransform == m_rectTransform) continue;
Rect otherRect = GetWorldRect(otherRectTransform);
otherRect.xMin -= avoidancePadding;
otherRect.xMax += avoidancePadding;
otherRect.yMin -= avoidancePadding;
otherRect.yMax += avoidancePadding;
if (myRect.Overlaps(otherRect))
{
// 计算重叠区域
float overlapX = Mathf.Min(myRect.xMax, otherRect.xMax) - Mathf.Max(myRect.xMin, otherRect.xMin);
float overlapY = Mathf.Min(myRect.yMax, otherRect.yMax) - Mathf.Max(myRect.yMin, otherRect.yMin);
// 向重叠较少的方向移动
Vector2 shift = Vector2.zero;
if (overlapX < overlapY)
{
// 水平方向重叠少,水平移动
float shiftDirection = (myRect.center.x < otherRect.center.x) ? -1f : 1f;
shift.x = overlapX * shiftDirection;
}
else
{
// 垂直方向重叠少,垂直移动
float shiftDirection = (myRect.center.y < otherRect.center.y) ? -1f : 1f;
shift.y = overlapY * shiftDirection;
}
// 应用偏移
Vector2 newScreenPos = RectTransformUtility.WorldToScreenPoint(null, m_rectTransform.position) + shift;
Vector2 localPos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(m_rectTransform.parent as RectTransform, newScreenPos, null, out localPos))
{
m_rectTransform.anchoredPosition = localPos;
}
// 移动后重新计算自己的矩形,并重新进行屏幕边界约束
if (clampToScreen)
{
ClampToScreen();
}
// 跳出循环,一次只处理一个重叠(移动后可能又和另一个重叠,这是简化处理)
break;
}
}
}
/// <summary>
/// 获取UI的世界空间矩形
/// </summary>
private Rect GetWorldRect(RectTransform rt)
{
Vector3[] corners = new Vector3[4];
rt.GetWorldCorners(corners);
return GetWorldRectFromCorners(corners);
}
private Rect GetWorldRectFromCorners(Vector3[] corners)
{
Vector2 min = corners[0];
Vector2 max = corners[2];
for (int i = 1; i < 4; i++)
{
min.x = Mathf.Min(min.x, corners[i].x);
min.y = Mathf.Min(min.y, corners[i].y);
max.x = Mathf.Max(max.x, corners[i].x);
max.y = Mathf.Max(max.y, corners[i].y);
}
return new Rect(min.x, min.y, max.x - min.x, max.y - min.y);
}
/// <summary>
/// 获取Canvas对应的屏幕矩形世界坐标
/// 注意此方法适用于Screen Space - Overlay模式。其他模式需调整。
/// </summary>
private Rect GetCanvasRect()
{
if (m_canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
return new Rect(0, 0, Screen.width, Screen.height);
}
else
{
// 对于World Space或Screen Space - Camera这里需要更复杂的转换
// 为简化假设Canvas充满屏幕
RectTransform canvasRect = m_canvas.GetComponent<RectTransform>();
Vector3[] canvasCorners = new Vector3[4];
canvasRect.GetWorldCorners(canvasCorners);
return GetWorldRectFromCorners(canvasCorners);
}
}
}