236 lines
7.6 KiB
C#
236 lines
7.6 KiB
C#
using UnityEngine;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.UI;
|
||
|
||
namespace MeowmentDebugTool
|
||
{
|
||
/// <summary>
|
||
/// 可拖动的悬浮按钮
|
||
/// 点击打开调试工具,可以在屏幕上自由拖动
|
||
/// </summary>
|
||
public class DraggableFloatingButton : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||
{
|
||
[Header("设置")]
|
||
[Tooltip("是否限制在屏幕范围内")]
|
||
public bool clampToScreen = true;
|
||
|
||
[Tooltip("与屏幕边缘的最小距离")]
|
||
public float edgePadding = 20f;
|
||
|
||
[Header("自动吸附")]
|
||
[Tooltip("是否自动吸附到屏幕边缘")]
|
||
public bool snapToEdge = true;
|
||
|
||
[Tooltip("吸附动画时间")]
|
||
public float snapDuration = 0.3f;
|
||
|
||
private RectTransform rectTransform;
|
||
private Canvas canvas;
|
||
private Vector2 dragOffset;
|
||
private bool isDragging = false;
|
||
private Vector2 targetPosition;
|
||
private bool isSnapping = false;
|
||
|
||
// 用于区分点击和拖动
|
||
private Vector2 dragStartPosition;
|
||
private const float clickThreshold = 5f; // 小于这个距离算点击
|
||
private bool wasDragging = false; // 标记刚才是否拖动过
|
||
|
||
private void Awake()
|
||
{
|
||
rectTransform = GetComponent<RectTransform>();
|
||
canvas = GetComponentInParent<Canvas>();
|
||
|
||
if (canvas == null)
|
||
{
|
||
Debug.LogError("DraggableFloatingButton 必须在Canvas下!");
|
||
}
|
||
|
||
targetPosition = rectTransform.anchoredPosition;
|
||
}
|
||
|
||
private void Update()
|
||
{
|
||
// 平滑移动到目标位置(吸附动画)
|
||
if (isSnapping && !isDragging)
|
||
{
|
||
rectTransform.anchoredPosition = Vector2.Lerp(
|
||
rectTransform.anchoredPosition,
|
||
targetPosition,
|
||
Time.deltaTime / snapDuration
|
||
);
|
||
|
||
if (Vector2.Distance(rectTransform.anchoredPosition, targetPosition) < 0.5f)
|
||
{
|
||
rectTransform.anchoredPosition = targetPosition;
|
||
isSnapping = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
public void OnBeginDrag(PointerEventData eventData)
|
||
{
|
||
isDragging = true;
|
||
isSnapping = false;
|
||
|
||
// 记录起始位置,用于判断是点击还是拖动
|
||
dragStartPosition = eventData.position;
|
||
|
||
// 计算拖动偏移
|
||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||
canvas.transform as RectTransform,
|
||
eventData.position,
|
||
eventData.pressEventCamera,
|
||
out Vector2 localPoint
|
||
);
|
||
|
||
dragOffset = rectTransform.anchoredPosition - localPoint;
|
||
}
|
||
|
||
public void OnDrag(PointerEventData eventData)
|
||
{
|
||
if (!isDragging) return;
|
||
|
||
// 转换屏幕坐标到Canvas局部坐标
|
||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||
canvas.transform as RectTransform,
|
||
eventData.position,
|
||
eventData.pressEventCamera,
|
||
out Vector2 localPoint
|
||
);
|
||
|
||
// 应用拖动偏移
|
||
Vector2 newPosition = localPoint + dragOffset;
|
||
|
||
// 限制在屏幕范围内
|
||
if (clampToScreen)
|
||
{
|
||
newPosition = ClampToScreen(newPosition);
|
||
}
|
||
|
||
rectTransform.anchoredPosition = newPosition;
|
||
}
|
||
|
||
public void OnEndDrag(PointerEventData eventData)
|
||
{
|
||
isDragging = false;
|
||
|
||
// 计算拖动距离
|
||
float dragDistance = Vector2.Distance(dragStartPosition, eventData.position);
|
||
|
||
// 如果拖动距离超过阈值,说明是拖动而不是点击
|
||
if (dragDistance > clickThreshold)
|
||
{
|
||
wasDragging = true;
|
||
// 在下一帧重置标记
|
||
StartCoroutine(ResetDragFlag());
|
||
}
|
||
|
||
// 自动吸附到最近的边缘
|
||
if (snapToEdge)
|
||
{
|
||
SnapToNearestEdge();
|
||
}
|
||
}
|
||
|
||
private System.Collections.IEnumerator ResetDragFlag()
|
||
{
|
||
yield return null; // 等待一帧
|
||
wasDragging = false;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查是否刚拖动过(用于Button点击事件判断)
|
||
/// </summary>
|
||
public bool GetWasDragging()
|
||
{
|
||
return wasDragging;
|
||
}
|
||
|
||
private Vector2 ClampToScreen(Vector2 position)
|
||
{
|
||
RectTransform canvasRect = canvas.transform as RectTransform;
|
||
Vector2 canvasSize = canvasRect.sizeDelta;
|
||
Vector2 buttonSize = rectTransform.sizeDelta;
|
||
|
||
// 计算可移动范围
|
||
float minX = -canvasSize.x / 2 + buttonSize.x / 2 + edgePadding;
|
||
float maxX = canvasSize.x / 2 - buttonSize.x / 2 - edgePadding;
|
||
float minY = -canvasSize.y / 2 + buttonSize.y / 2 + edgePadding;
|
||
float maxY = canvasSize.y / 2 - buttonSize.y / 2 - edgePadding;
|
||
|
||
position.x = Mathf.Clamp(position.x, minX, maxX);
|
||
position.y = Mathf.Clamp(position.y, minY, maxY);
|
||
|
||
return position;
|
||
}
|
||
|
||
private void SnapToNearestEdge()
|
||
{
|
||
RectTransform canvasRect = canvas.transform as RectTransform;
|
||
Vector2 canvasSize = canvasRect.sizeDelta;
|
||
Vector2 currentPos = rectTransform.anchoredPosition;
|
||
|
||
// 计算到各个边缘的距离
|
||
float distToLeft = Mathf.Abs(currentPos.x + canvasSize.x / 2);
|
||
float distToRight = Mathf.Abs(currentPos.x - canvasSize.x / 2);
|
||
float distToTop = Mathf.Abs(currentPos.y - canvasSize.y / 2);
|
||
float distToBottom = Mathf.Abs(currentPos.y + canvasSize.y / 2);
|
||
|
||
// 找到最近的边
|
||
float minDist = Mathf.Min(distToLeft, distToRight, distToTop, distToBottom);
|
||
|
||
Vector2 snapPosition = currentPos;
|
||
Vector2 buttonSize = rectTransform.sizeDelta;
|
||
|
||
if (minDist == distToLeft)
|
||
{
|
||
// 吸附到左边
|
||
snapPosition.x = -canvasSize.x / 2 + buttonSize.x / 2 + edgePadding;
|
||
}
|
||
else if (minDist == distToRight)
|
||
{
|
||
// 吸附到右边
|
||
snapPosition.x = canvasSize.x / 2 - buttonSize.x / 2 - edgePadding;
|
||
}
|
||
else if (minDist == distToTop)
|
||
{
|
||
// 吸附到顶部
|
||
snapPosition.y = canvasSize.y / 2 - buttonSize.y / 2 - edgePadding;
|
||
}
|
||
else if (minDist == distToBottom)
|
||
{
|
||
// 吸附到底部
|
||
snapPosition.y = -canvasSize.y / 2 + buttonSize.y / 2 + edgePadding;
|
||
}
|
||
|
||
// 确保在屏幕范围内
|
||
snapPosition = ClampToScreen(snapPosition);
|
||
|
||
targetPosition = snapPosition;
|
||
isSnapping = true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置悬浮按钮位置
|
||
/// </summary>
|
||
public void SetPosition(Vector2 position)
|
||
{
|
||
if (clampToScreen)
|
||
{
|
||
position = ClampToScreen(position);
|
||
}
|
||
rectTransform.anchoredPosition = position;
|
||
targetPosition = position;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前位置
|
||
/// </summary>
|
||
public Vector2 GetPosition()
|
||
{
|
||
return rectTransform.anchoredPosition;
|
||
}
|
||
}
|
||
}
|