using UnityEngine;
using UnityEngine.EventSystems;
namespace Cielonos.MainGame.UI
{
///
/// 地图容器的平移和缩放控制器。
/// 拖拽平移地图内容,滚轮以光标为中心缩放。
/// 挂载在地图页面的可交互区域(需要 Graphic + raycastTarget)上,
/// 操作指定的 content RectTransform 的 anchoredPosition 和 localScale。
///
[RequireComponent(typeof(RectTransform))]
public class MapPanZoom : MonoBehaviour, IPointerDownHandler, IDragHandler, IScrollHandler
{
// ================================================================
// 常量
// ================================================================
private const float DEFAULT_MIN_SCALE = 0.3f;
private const float DEFAULT_MAX_SCALE = 3f;
private const float DEFAULT_SCROLL_SENSITIVITY = 0.1f;
// ================================================================
// 配置
// ================================================================
[Header("缩放设置")]
[SerializeField] private float minScale = DEFAULT_MIN_SCALE;
[SerializeField] private float maxScale = DEFAULT_MAX_SCALE;
[SerializeField] private float scrollSensitivity = DEFAULT_SCROLL_SENSITIVITY;
[Header("目标")]
[Tooltip("被平移/缩放的内容 RectTransform(通常是 MapContainer)")]
[SerializeField] private RectTransform content;
// ================================================================
// 运行时状态
// ================================================================
/// 上次拖拽时指针在视口本地空间中的位置。
private Vector2 _lastPointerLocalPos;
/// 本组件所在的 RectTransform,作为视口参考坐标系。
private RectTransform _viewport;
// ================================================================
// 生命周期
// ================================================================
private void Awake()
{
_viewport = GetComponent();
}
// ================================================================
// 事件处理
// ================================================================
/// 指针按下时记录起始位置,用于后续拖拽计算。
public void OnPointerDown(PointerEventData eventData)
{
if (content == null) return;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
_viewport, eventData.position, eventData.pressEventCamera, out _lastPointerLocalPos);
}
///
/// 拖拽时平移 content 的 anchoredPosition。
/// 通过计算指针在视口本地空间中的位移量来移动内容。
///
public void OnDrag(PointerEventData eventData)
{
if (content == null) return;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
_viewport, eventData.position, eventData.pressEventCamera, out Vector2 localPoint))
{
Vector2 delta = localPoint - _lastPointerLocalPos;
content.anchoredPosition += delta;
_lastPointerLocalPos = localPoint;
}
}
///
/// 滚轮缩放,以光标位置为缩放中心。
/// 数学原理:保持光标下的地图坐标点在缩放前后映射到同一屏幕位置。
/// c' = cursor - (cursor - c) * (newScale / oldScale)
///
public void OnScroll(PointerEventData eventData)
{
if (content == null) return;
float scrollDelta = eventData.scrollDelta.y;
if (Mathf.Approximately(scrollDelta, 0f)) return;
float oldScale = content.localScale.x;
float newScale = Mathf.Clamp(oldScale * (1f + scrollDelta * scrollSensitivity), minScale, maxScale);
if (Mathf.Approximately(oldScale, newScale)) return;
// 以光标为中心缩放:保持光标下的地图点不动
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
_viewport, eventData.position, eventData.pressEventCamera, out Vector2 localCursor))
{
float ratio = newScale / oldScale;
content.anchoredPosition = localCursor - (localCursor - content.anchoredPosition) * ratio;
}
content.localScale = new Vector3(newScale, newScale, 1f);
}
// ================================================================
// 公共 API
// ================================================================
/// 重置为默认视角:居中且缩放为 1。
public void ResetView()
{
if (content == null) return;
content.anchoredPosition = Vector2.zero;
content.localScale = Vector3.one;
}
///
/// 将视角平移,使指定的 content 内部坐标出现在视口中心。
/// 坐标系与节点的 RunMapNode.position 一致。
///
/// content 内部的本地坐标。
public void CenterOn(Vector2 contentLocalPosition)
{
if (content == null) return;
content.anchoredPosition = -contentLocalPosition * content.localScale.x;
}
}
}