using UnityEngine; // КАМЕРА ГОРОДА (как в мобильных стратегиях): // - перетаскивание одним пальцем / левой кнопкой мыши -- двигает карту // - щипок двумя пальцами / колёсико мыши -- приближение и отдаление (к точке под пальцем/курсором) // - камера не уезжает за границы карты (bounds) // - над интерфейсом (HUD, панели) ничего не делает // - без поворотов и наклонов: изометрия уже нарисована в спрайтах // Вешается на Main Camera вместо CameraController / CameraZoom / FlyCamera. [RequireComponent(typeof(Camera))] public class CityCamera : MonoBehaviour { [Header("Зум (orthographic size)")] public float minZoom = 3f; public float maxZoom = 14f; public float wheelZoomSpeed = 2.5f; public float zoomSmooth = 12f; [Header("Границы карты (мировые координаты). Пусто = посчитать по объектам сцены")] public bool autoBounds = true; public Rect bounds = new Rect(-25, -15, 50, 30); [Tooltip("Запас за краем карты, чтобы край не прилипал к экрану")] public float boundsPadding = 4f; [Header("Инерция после свайпа")] public float inertiaDamping = 6f; Camera cam; float targetZoom; Vector3 lastWorld; bool dragging; Vector3 velocity; float lastPinchDist; void Awake() { cam = GetComponent(); cam.orthographic = true; transform.rotation = Quaternion.identity; transform.localScale = Vector3.one; // старый CameraZoom масштабировал камеру -- сбрасываем targetZoom = Mathf.Clamp(cam.orthographicSize, minZoom, maxZoom); } void Start() { if (autoBounds) CalculateBounds(); ClampPosition(); } // Границы по всем спрайтам и тайлмапам сцены (кроме UI) public void CalculateBounds() { bool any = false; Bounds b = new Bounds(); foreach (var r in FindObjectsByType(FindObjectsSortMode.None)) { string tn = r.GetType().Name; if (tn != "SpriteRenderer" && tn != "TilemapRenderer") continue; // только карта и здания if (r.gameObject.layer == 5) continue; // UI if (r.GetComponentInParent() != null) continue; // всё, что внутри интерфейса if (!any) { b = r.bounds; any = true; } else b.Encapsulate(r.bounds); } if (any) bounds = new Rect(b.min.x, b.min.y, b.size.x, b.size.y); } void Update() { bool overUI = UIBlocker.IsPointerOverUI(); if (Input.touchCount >= 2) HandlePinch(); else HandleDrag(overUI); // колёсико мыши -- зум к курсору float wheel = Input.GetAxis("Mouse ScrollWheel"); if (Mathf.Abs(wheel) > 0.001f && !overUI) ZoomTowards(Input.mousePosition, targetZoom - wheel * wheelZoomSpeed * 4f); // плавный зум if (Mathf.Abs(cam.orthographicSize - targetZoom) > 0.001f) { Vector3 before = cam.ScreenToWorldPoint(zoomAnchor); cam.orthographicSize = Mathf.Lerp(cam.orthographicSize, targetZoom, Time.unscaledDeltaTime * zoomSmooth); Vector3 after = cam.ScreenToWorldPoint(zoomAnchor); transform.position += before - after; // точка под пальцем остаётся на месте } // инерция if (!dragging && velocity.sqrMagnitude > 0.0001f) { transform.position += velocity * Time.unscaledDeltaTime; velocity = Vector3.Lerp(velocity, Vector3.zero, Time.unscaledDeltaTime * inertiaDamping); } ClampPosition(); } Vector3 zoomAnchor; void ZoomTowards(Vector3 screenPoint, float zoom) { zoomAnchor = screenPoint; targetZoom = Mathf.Clamp(zoom, minZoom, maxZoom); } void HandleDrag(bool overUI) { if (Input.GetMouseButtonDown(0)) { dragging = !overUI; // начали тащить на интерфейсе -- карту не двигаем velocity = Vector3.zero; lastWorld = cam.ScreenToWorldPoint(Input.mousePosition); } if (Input.GetMouseButtonUp(0)) dragging = false; if (!dragging || !Input.GetMouseButton(0)) return; Vector3 now = cam.ScreenToWorldPoint(Input.mousePosition); Vector3 delta = lastWorld - now; delta.z = 0; transform.position += delta; if (Time.unscaledDeltaTime > 0) velocity = Vector3.Lerp(velocity, delta / Time.unscaledDeltaTime, 0.5f); lastWorld = cam.ScreenToWorldPoint(Input.mousePosition); // после сдвига камеры } void HandlePinch() { dragging = false; velocity = Vector3.zero; Touch a = Input.GetTouch(0), b = Input.GetTouch(1); float dist = Vector2.Distance(a.position, b.position); if (a.phase == TouchPhase.Began || b.phase == TouchPhase.Began || lastPinchDist <= 0f) { lastPinchDist = dist; return; } if (dist > 1f) { float ratio = lastPinchDist / dist; ZoomTowards((a.position + b.position) * 0.5f, targetZoom * ratio); } lastPinchDist = dist; if (a.phase == TouchPhase.Ended || b.phase == TouchPhase.Ended) lastPinchDist = 0f; } void ClampPosition() { float halfH = cam.orthographicSize; float halfW = halfH * cam.aspect; Rect r = new Rect(bounds.xMin - boundsPadding, bounds.yMin - boundsPadding, bounds.width + boundsPadding * 2, bounds.height + boundsPadding * 2); Vector3 p = transform.position; p.x = r.width > halfW * 2 ? Mathf.Clamp(p.x, r.xMin + halfW, r.xMax - halfW) : r.center.x; p.y = r.height > halfH * 2 ? Mathf.Clamp(p.y, r.yMin + halfH, r.yMax - halfH) : r.center.y; transform.position = p; } void OnDrawGizmosSelected() { Gizmos.color = Color.cyan; Gizmos.DrawWireCube(bounds.center, bounds.size); } }