using UnityEngine; using UnityEngine.EventSystems; // Камера карты мира, 2.5D (переход на 3D, 04.11.2026): наклонный вид под фиксированным углом, без вращения. // Перетаскивание (палец/мышь) двигает карту, два пальца/колёсико -- приближение, границы карты. // Отличает "тап" от "перетаскивания": тап -- это касание, при котором палец почти не сдвинулся. // Карта лежит на земле: координаты карты (x, y) -> мир (x, 0, y). [DefaultExecutionOrder(-10)] // считать тап раньше, чем его прочитает WorldMapController [RequireComponent(typeof(Camera))] public class MapCameraController : MonoBehaviour { [Header("Вид")] public float pitch = 55f; // наклон, градусы от горизонта public float yaw = 0f; // север карты -- вверху экрана, как было в 2D public float distance = 18f; [Header("Зум")] public float minDistance = 7f; public float maxDistance = 42f; public float mouseZoomSpeed = 2f; [Header("Тап")] [Tooltip("Если палец сдвинулся меньше, чем на столько пикселей, -- это нажатие, а не перетаскивание")] public float tapMaxMovePixels = 15f; // Границы карты в клетках Unity (задаёт WorldMapController) [HideInInspector] public Vector2 mapHalfSize = new Vector2(20f, 14f); private Camera cam; private Vector3 target; // точка на земле, на которую смотрит камера private Vector3 lastPointerPosition; private Vector3 pressPosition; private bool isPressed; private bool isDragging; private bool pressStartedOverUI; private bool hasTap; private Vector3 tapWorldPosition; void Awake() { cam = GetComponent(); cam.orthographic = false; cam.fieldOfView = 40f; cam.nearClipPlane = 0.3f; cam.farClipPlane = 300f; Map3D.UseRenderer3D(cam); Apply(); } // Было ли за этот кадр нажатие по карте (не по UI и не перетаскивание). Точка -- на земле (x, 0, z) public bool TryGetTap(out Vector3 worldPosition) { worldPosition = tapWorldPosition; return hasTap; } public void CenterOn(Vector3 worldPosition) { target = new Vector3(worldPosition.x, 0f, worldPosition.z); ClampToMap(); Apply(); } void Update() { hasTap = false; if (Input.touchCount >= 2) { HandlePinch(); isPressed = false; // после щипка не засчитываем отпускание как тап Apply(); return; } HandlePointer(); HandleMouseWheel(); ClampToMap(); Apply(); } void HandlePointer() { if (Input.GetMouseButtonDown(0)) { isPressed = true; isDragging = false; pressStartedOverUI = IsPointerOverUI(); pressPosition = Input.mousePosition; lastPointerPosition = Input.mousePosition; } if (!isPressed || pressStartedOverUI) { if (Input.GetMouseButtonUp(0)) isPressed = false; return; } if (Input.GetMouseButton(0)) { if (!isDragging && (Input.mousePosition - pressPosition).magnitude > tapMaxMovePixels) isDragging = true; if (isDragging) { // Сдвигаем камеру ровно на столько, на сколько сдвинулся палец по земле -- карта "прилипает" к пальцу target += GroundPoint(lastPointerPosition) - GroundPoint(Input.mousePosition); ClampToMap(); Apply(); } lastPointerPosition = Input.mousePosition; } if (Input.GetMouseButtonUp(0)) { if (!isDragging) { hasTap = true; tapWorldPosition = GroundPoint(Input.mousePosition); } isPressed = false; isDragging = false; } } void HandlePinch() { Touch a = Input.GetTouch(0); Touch b = Input.GetTouch(1); float previousDistance = ((a.position - a.deltaPosition) - (b.position - b.deltaPosition)).magnitude; float currentDistance = (a.position - b.position).magnitude; if (currentDistance <= 0.01f) return; distance = Mathf.Clamp(distance * previousDistance / currentDistance, minDistance, maxDistance); ClampToMap(); } void HandleMouseWheel() { float scroll = Input.GetAxis("Mouse ScrollWheel"); if (scroll == 0f || IsPointerOverUI()) return; distance = Mathf.Clamp(distance - scroll * mouseZoomSpeed * 10f, minDistance, maxDistance); } // Точка, на которую смотрит камера, не уходит за край карты void ClampToMap() { target.x = Mathf.Clamp(target.x, -mapHalfSize.x, mapHalfSize.x); target.z = Mathf.Clamp(target.z, -mapHalfSize.y, mapHalfSize.y); target.y = 0f; } void Apply() { Quaternion rot = Quaternion.Euler(pitch, yaw, 0); transform.rotation = rot; transform.position = target - rot * Vector3.forward * distance; } // Точка на земле (y = 0) под экранной точкой Vector3 GroundPoint(Vector3 screen) { Ray ray = cam.ScreenPointToRay(screen); var plane = new Plane(Vector3.up, Vector3.zero); return plane.Raycast(ray, out float t) ? ray.GetPoint(t) : target; } static bool IsPointerOverUI() { if (EventSystem.current == null) return false; if (Input.touchCount > 0) return EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId); return EventSystem.current.IsPointerOverGameObject(); } }