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; } } }