using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using BaseGames.Input; namespace BaseGames.World.Map { /// /// 全屏地图输入处理器(架构 15_MapShopModule §1.3.1)。 /// 挂在与 MapPanel 相同的 GameObject 上(MapPanel OnEnable/OnDisable 联动启停)。 /// /// 鼠标滚轮缩放(以鼠标位置为缩放中心) /// 方向键 / 摇杆平移(经由 InputReaderSO.NavigateEvent 派发) /// /// [RequireComponent(typeof(MapPanel))] public class MapInputHandler : MonoBehaviour, IScrollHandler { [SerializeField] private InputReaderSO _inputReader; [SerializeField] private ScrollRect _scrollRect; [SerializeField] private RectTransform _zoomTarget; // 通常为 _roomContainer(格子根节点) [Header("缩放")] [SerializeField, Range(0.2f, 1f)] private float _zoomMin = 0.4f; [SerializeField, Range(1f, 5f)] private float _zoomMax = 3.0f; [SerializeField, Range(0.05f, 0.5f)] private float _zoomStep = 0.12f; [Header("平移")] [SerializeField] private float _keyPanSpeed = 600f; // px / 秒 private Vector2 _navInput; private MapPanel _panel; private void Awake() { _panel = GetComponent(); #if UNITY_EDITOR || DEVELOPMENT_BUILD // R25-N2 _zoomTarget 须配置为 MapPanel 的格子根节点(与 _roomContainer 相同), // 否则 OnScroll 写入的 scale 与 MapPanel.CurrentZoom 读取的 scale 将来自不同节点,导致静默状态分裂。 if (_zoomTarget == null) Debug.LogWarning("[MapInputHandler] _zoomTarget 未配置,缩放功能将失效。请在 Inspector 中指定 MapPanel 的格子根节点(_roomContainer)。", this); #endif } private void OnEnable() { if (_inputReader != null) { _inputReader.NavigateEvent += OnNavigate; // R13-N4 居中到玩家快捷键 _inputReader.MapCenterEvent += OnMapCenter; } } private void OnDisable() { _navInput = Vector2.zero; if (_inputReader != null) { _inputReader.NavigateEvent -= OnNavigate; _inputReader.MapCenterEvent -= OnMapCenter; } } private void OnNavigate(Vector2 dir) => _navInput = dir; // R13-N4 将视图居中到玩家所在房间 private void OnMapCenter() => _panel?.CenterOnCurrentRoom(); private void Update() { if (_scrollRect == null || _navInput == Vector2.zero) return; // R18-N1 contentSize 需乘以当前缩放系数,否则缩放后平移速度感知偏差。 // R19-N1 / R24-N2 统一读 _panel.CurrentZoom(_zoomTarget.localScale.x), // 消除独立 _zoom 字段与 CurrentZoom 的双份状态——OnScroll 直接写 localScale, // CurrentZoom 即时反映最新值,不再需要额外同步。 var content = _scrollRect.content; var viewport = _scrollRect.viewport != null ? _scrollRect.viewport : (RectTransform)_scrollRect.transform; Vector2 contentSize = content.rect.size; Vector2 viewportSize = viewport.rect.size; float currentZoom = _panel != null ? _panel.CurrentZoom : (_zoomTarget != null ? _zoomTarget.localScale.x : 1f); float rangeX = contentSize.x * currentZoom - viewportSize.x; float rangeY = contentSize.y * currentZoom - viewportSize.y; if (rangeX <= 0f && rangeY <= 0f) return; // 内容比视口小,无需平移 Vector2 delta = _navInput * (_keyPanSpeed * Time.unscaledDeltaTime); Vector2 norm = _scrollRect.normalizedPosition; if (rangeX > 0f) norm.x = Mathf.Clamp01(norm.x + delta.x / rangeX); if (rangeY > 0f) norm.y = Mathf.Clamp01(norm.y + delta.y / rangeY); _scrollRect.normalizedPosition = norm; } // ── 鼠标滚轮缩放 ───────────────────────────────────────────────────── public void OnScroll(PointerEventData eventData) { if (_zoomTarget == null) return; // R24-N2 直接读 _zoomTarget.localScale.x 作为当前缩放,消除独立 _zoom 字段 float currentZoom = _zoomTarget.localScale.x; float newZoom = Mathf.Clamp( currentZoom + eventData.scrollDelta.y * _zoomStep, _zoomMin, _zoomMax); if (Mathf.Approximately(newZoom, currentZoom)) return; // 将鼠标屏幕坐标转为 zoomTarget 本地坐标(缩放前) RectTransformUtility.ScreenPointToLocalPointInRectangle( _zoomTarget, eventData.position, eventData.pressEventCamera, out Vector2 pivotBefore); _zoomTarget.localScale = new Vector3(newZoom, newZoom, 1f); // 将同一屏幕点再次映射(缩放后),计算偏移量保持鼠标下方内容不动 RectTransformUtility.ScreenPointToLocalPointInRectangle( _zoomTarget, eventData.position, eventData.pressEventCamera, out Vector2 pivotAfter); Vector2 offset = pivotAfter - pivotBefore; _zoomTarget.anchoredPosition += offset * newZoom; } } }