MeowmentDebugTool/Packages/com.bywaystudios.meowmentdebugtool/Runtime/DraggableFloatingButton.cs
2025-12-19 12:14:42 +08:00

236 lines
7.6 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 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;
}
}
}