Created
December 7, 2025 12:02
-
-
Save koras/85cc7657c303401ce5710b8f301092d1 to your computer and use it in GitHub Desktop.
pinch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using UnityEngine; | |
| using UnityEngine.InputSystem; | |
| using UnityEngine.InputSystem.EnhancedTouch; | |
| using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch; | |
| using TMPro; | |
| namespace Input | |
| { | |
| public class PinchToZoomAndPan : MonoBehaviour | |
| { | |
| [Header("Camera / Map")] | |
| public Camera targetCamera; // Камера, через которую смотрим на карту | |
| [SerializeField] public float zoomSpeed = 0.2f; // чувствительность пинч-зума | |
| [SerializeField] public float minZoom = 10f; // минимальный размер (минимальный Zoom) | |
| [SerializeField] private TMP_Text _textMinZoom = null; | |
| [SerializeField] private TMP_Text _textMaxZoom = null; | |
| [SerializeField] private TMP_Text _textCurrentZoom = null; | |
| [SerializeField] public float maxZoom = 20f; // максимальный Zoom | |
| [Header("Pan")] | |
| [SerializeField] public float panSpeed = 1f; // скорость перемещения карты (панорамирования) | |
| [Header("Mouse Wheel Zoom")] | |
| [SerializeField] public float scrollZoomSpeed = 0.02f; // скорость зума от колёсика мыши | |
| [Header("Camera Bounds")] | |
| [SerializeField] private float minX = -10f; // левая граница движения камеры | |
| [SerializeField] private float maxX = 10f; // правая граница | |
| [SerializeField] private float minY = -10f; // нижняя | |
| [SerializeField] private float maxY = 10f; // верхняя | |
| // внутренние данные пинча | |
| float _previousPinchDistance; | |
| bool _wasPinching; | |
| // внутренние данные пана | |
| bool _isPanning; // в данный момент тянем карту? | |
| Vector3 _lastPanWorldPos; // позиция курсора/пальца в мире на предыдущем кадре | |
| // шаг зума по кнопкам UI | |
| [SerializeField] private float buttonZoomStep = 1f; | |
| // Кнопка "приблизить" — уменьшает зум (orthoSize) | |
| public void ZoomInButton() | |
| { | |
| ApplyZoom(-buttonZoomStep); | |
| } | |
| // Кнопка "отдалить" | |
| public void ZoomOutButton() | |
| { | |
| ApplyZoom(buttonZoomStep); | |
| } | |
| // UI кнопка: увеличить minZoom | |
| public void AddMinZoom() | |
| { | |
| minZoom += 0.1f; | |
| UpdateMinZoomText(); | |
| } | |
| // UI кнопка: уменьшить minZoom | |
| public void SubMinZoom() | |
| { | |
| minZoom -= 0.1f; | |
| UpdateMinZoomText(); | |
| } | |
| // UI кнопка: увеличить maxZoom | |
| public void AddMaxZoom() | |
| { | |
| maxZoom += 1.0f; | |
| UpdateMaxZoomText(); | |
| } | |
| // UI кнопка: уменьшить maxZoom | |
| public void SubMaxZoom() | |
| { | |
| maxZoom -= 1.0f; | |
| UpdateMaxZoomText(); | |
| } | |
| // Перевод позиции экрана в мировую координату на плоскости Z=0 | |
| // Вся логика пана строится на разнице двух таких точек | |
| private Vector3 ScreenToWorldOnMap(Vector2 screenPos) | |
| { | |
| Plane plane = new Plane(Vector3.forward, Vector3.zero); // плоскость карты (XY) | |
| Ray ray = targetCamera.ScreenPointToRay(screenPos); | |
| if (plane.Raycast(ray, out float enter)) | |
| return ray.GetPoint(enter); | |
| return Vector3.zero; | |
| } | |
| // Обновление UI текущего зума | |
| void UpdateCurrentZoomText() | |
| { | |
| if (_textCurrentZoom == null) | |
| return; | |
| float zoomValue = | |
| targetCamera.orthographic ? targetCamera.orthographicSize : targetCamera.fieldOfView; | |
| _textCurrentZoom.text = "z " + zoomValue.ToString("0.0"); | |
| } | |
| // Инициализация текста | |
| void Start() | |
| { | |
| UpdateMinZoomText(); | |
| UpdateMaxZoomText(); | |
| UpdateCurrentZoomText(); | |
| } | |
| // Обновление текста maxZoom | |
| void UpdateMaxZoomText() | |
| { | |
| if (_textMaxZoom != null) | |
| _textMaxZoom.text = "" + maxZoom.ToString("0.0"); | |
| } | |
| // Обновление текста minZoom | |
| void UpdateMinZoomText() | |
| { | |
| if (_textMinZoom != null) | |
| _textMinZoom.text = "" + minZoom.ToString("0.0"); | |
| } | |
| void Awake() | |
| { | |
| // если пользователь камеру не указал | |
| if (targetCamera == null) | |
| targetCamera = Camera.main; | |
| } | |
| void OnEnable() | |
| { | |
| // новый Input System для тачей требует включения | |
| EnhancedTouchSupport.Enable(); | |
| } | |
| void OnDisable() | |
| { | |
| EnhancedTouchSupport.Disable(); | |
| } | |
| void Update() | |
| { | |
| // Если активны пальцы — работаем в режиме тачей | |
| if (Touch.activeTouches.Count > 0) | |
| HandleTouchInput(); | |
| else | |
| HandleMouseInput(); // иначе мышка | |
| } | |
| // ==================== TOUCH INPUT ==================== | |
| void HandleTouchInput() | |
| { | |
| int touchCount = Touch.activeTouches.Count; | |
| if (touchCount >= 2) | |
| { | |
| // ПИНЧ-ЗУМ | |
| var t0 = Touch.activeTouches[0]; | |
| var t1 = Touch.activeTouches[1]; | |
| float currentDistance = Vector2.Distance(t0.screenPosition, t1.screenPosition); | |
| if (!_wasPinching) | |
| { | |
| // начало пинча | |
| _previousPinchDistance = currentDistance; | |
| _wasPinching = true; | |
| } | |
| else | |
| { | |
| // изменение расстояния между пальцами | |
| float delta = currentDistance - _previousPinchDistance; | |
| _previousPinchDistance = currentDistance; | |
| // зумим карту | |
| float zoomChange = -delta * zoomSpeed * Time.deltaTime; | |
| ApplyZoom(zoomChange); | |
| } | |
| _isPanning = false; // при двух пальцах пан недоступен | |
| } | |
| else if (touchCount == 1) | |
| { | |
| var touch = Touch.activeTouches[0]; | |
| if (touch.phase == UnityEngine.InputSystem.TouchPhase.Began) | |
| { | |
| // начало пана | |
| _isPanning = true; | |
| _wasPinching = false; | |
| _lastPanWorldPos = ScreenToWorldOnMap(touch.screenPosition); | |
| } | |
| else if (touch.phase == UnityEngine.InputSystem.TouchPhase.Moved && _isPanning) | |
| { | |
| // продолжаем панить — считаем разницу позиций | |
| Vector3 curWorld = ScreenToWorldOnMap(touch.screenPosition); | |
| Vector3 delta = _lastPanWorldPos - curWorld; | |
| Pan(delta); | |
| _lastPanWorldPos = curWorld; | |
| } | |
| else if (touch.phase == UnityEngine.InputSystem.TouchPhase.Ended || | |
| touch.phase == UnityEngine.InputSystem.TouchPhase.Canceled) | |
| { | |
| // конец касания | |
| _isPanning = false; | |
| _wasPinching = false; | |
| } | |
| } | |
| else | |
| { | |
| // если нет пальцев — сброс | |
| _isPanning = false; | |
| _wasPinching = false; | |
| } | |
| } | |
| // ==================== MOUSE INPUT ==================== | |
| void HandleMouseInput() | |
| { | |
| // начало пана — ЛКМ нажата | |
| if (Mouse.current.leftButton.wasPressedThisFrame) | |
| { | |
| _isPanning = true; | |
| _lastPanWorldPos = ScreenToWorldOnMap(Mouse.current.position.ReadValue()); | |
| } | |
| else if (Mouse.current.leftButton.isPressed && _isPanning) | |
| { | |
| // движение мыши во время пана | |
| Vector3 curWorld = ScreenToWorldOnMap(Mouse.current.position.ReadValue()); | |
| Vector3 delta = _lastPanWorldPos - curWorld; | |
| Pan(delta); | |
| _lastPanWorldPos = curWorld; | |
| } | |
| else if (Mouse.current.leftButton.wasReleasedThisFrame) | |
| { | |
| // конец пана | |
| _isPanning = false; | |
| } | |
| // Зум колёсиком (без deltaTime — событие дискретное) | |
| float scroll = Mouse.current.scroll.ReadValue().y; | |
| if (Mathf.Abs(scroll) > 0.01f) | |
| { | |
| float zoomChange = -scroll * scrollZoomSpeed; | |
| ApplyZoom(zoomChange); | |
| } | |
| } | |
| // ==================== PAN LOGIC ==================== | |
| void Pan(Vector3 delta) | |
| { | |
| // смещаем камеру на разницу позиций курсора в мировых координатах | |
| targetCamera.transform.position += delta * panSpeed; | |
| // применяем границы движения | |
| Vector3 pos = targetCamera.transform.position; | |
| pos.x = Mathf.Clamp(pos.x, minX, maxX); | |
| pos.y = Mathf.Clamp(pos.y, minY, maxY); | |
| targetCamera.transform.position = pos; | |
| } | |
| // ==================== ZOOM LOGIC ==================== | |
| void ApplyZoom(float zoomChange) | |
| { | |
| // ортографическая камера — меняем orthographicSize | |
| if (targetCamera.orthographic) | |
| { | |
| float size = targetCamera.orthographicSize; | |
| size += zoomChange; | |
| targetCamera.orthographicSize = Mathf.Clamp(size, minZoom, maxZoom); | |
| } | |
| else | |
| { | |
| // перспектива — меняем fieldOfView | |
| float fov = targetCamera.fieldOfView; | |
| fov += zoomChange; | |
| targetCamera.fieldOfView = Mathf.Clamp(fov, minZoom, maxZoom); | |
| } | |
| UpdateCurrentZoomText(); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
chat gpt moment