139 lines
5.6 KiB
C#
139 lines
5.6 KiB
C#
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;
|
||
}
|
||
}
|
||
}
|