Files
Cielonos/Assets/Scripts/MainGame/UI/PlayerUI/MainGamePages/Map/MapPanZoom.cs
2026-05-10 11:47:55 -04:00

139 lines
5.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;
namespace Cielonos.MainGame.UI
{
/// <summary>
/// 地图容器的平移和缩放控制器。
/// 拖拽平移地图内容,滚轮以光标为中心缩放。
/// 挂载在地图页面的可交互区域(需要 Graphic + raycastTarget
/// 操作指定的 content RectTransform 的 anchoredPosition 和 localScale。
/// </summary>
[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;
// ================================================================
// 运行时状态
// ================================================================
/// <summary>上次拖拽时指针在视口本地空间中的位置。</summary>
private Vector2 _lastPointerLocalPos;
/// <summary>本组件所在的 RectTransform作为视口参考坐标系。</summary>
private RectTransform _viewport;
// ================================================================
// 生命周期
// ================================================================
private void Awake()
{
_viewport = GetComponent<RectTransform>();
}
// ================================================================
// 事件处理
// ================================================================
/// <summary>指针按下时记录起始位置,用于后续拖拽计算。</summary>
public void OnPointerDown(PointerEventData eventData)
{
if (content == null) return;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
_viewport, eventData.position, eventData.pressEventCamera, out _lastPointerLocalPos);
}
/// <summary>
/// 拖拽时平移 content 的 anchoredPosition。
/// 通过计算指针在视口本地空间中的位移量来移动内容。
/// </summary>
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;
}
}
/// <summary>
/// 滚轮缩放,以光标位置为缩放中心。
/// 数学原理:保持光标下的地图坐标点在缩放前后映射到同一屏幕位置。
/// c' = cursor - (cursor - c) * (newScale / oldScale)
/// </summary>
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
// ================================================================
/// <summary>重置为默认视角:居中且缩放为 1。</summary>
public void ResetView()
{
if (content == null) return;
content.anchoredPosition = Vector2.zero;
content.localScale = Vector3.one;
}
/// <summary>
/// 将视角平移,使指定的 content 内部坐标出现在视口中心。
/// 坐标系与节点的 RunMapNode.position 一致。
/// </summary>
/// <param name="contentLocalPosition">content 内部的本地坐标。</param>
public void CenterOn(Vector2 contentLocalPosition)
{
if (content == null) return;
content.anchoredPosition = -contentLocalPosition * content.localScale.x;
}
}
}