using UnityEngine; // КАМЕРА 3D-ПОСЕЛЕНИЯ (2.5D): наклонный вид под фиксированным углом, без вращения. // Перемещение -- перетаскиванием (палец / мышь), приближение -- двумя пальцами или колесом. // Не уходит за пределы полезной карты. FocusOn -- плавно показать область (открытие новой зоны). public class City3DCamera : MonoBehaviour { public float pitch = 52f; // наклон, градусы от горизонта public float yaw = 45f; // поворот вида вокруг вертикали public float distance = 26f; public float minDistance = 8f; public float maxDistance = 55f; public Vector3 target; // точка на земле, на которую смотрит камера public Rect bounds = new Rect(0, 0, 48, 48); // где может быть target (x, z) Camera cam; Vector3 focusTarget; float focusDistance; bool focusing; Vector3 lastPointer; bool panning; // Перемещение камеры пальцем можно выключить на время перетаскивания здания public bool AllowPan { get; set; } = true; void Awake() { cam = GetComponent(); } public void FocusOn(Rect area) { focusTarget = new Vector3(area.center.x, 0, area.center.y); // отойти так, чтобы область поместилась по высоте экрана (с запасом) float size = Mathf.Max(area.width, area.height); focusDistance = Mathf.Clamp(size * 1.15f, minDistance, maxDistance); focusing = true; } // Начать / продолжить / закончить перемещение камеры. Зовёт контроллер ввода (он решает, что под пальцем) public void BeginPan(Vector3 screen) { panning = AllowPan; lastPointer = screen; focusing = false; } public void EndPan() { panning = false; } // Палец ещё не сдвинулся настолько, чтобы считать это перетаскиванием: камера стоит, но точка запоминается -- // когда перетаскивание начнётся, карта не прыгнет (как на карте мира, 05.11) public void Track(Vector3 screen) { lastPointer = screen; } public void Pan(Vector3 screen) { if (!panning || cam == null) return; Vector3 a = GroundPoint(lastPointer), b = GroundPoint(screen); target += a - b; lastPointer = screen; Clamp(); Apply(); // сразу: иначе следующий шаг считался бы от старого положения камеры -- камера дёргалась (05.11) } public void Zoom(float delta) { focusing = false; distance = Mathf.Clamp(distance * (1f - delta), minDistance, maxDistance); } void Update() { // Колесо мыши (редактор, ПК) float wheel = Input.mouseScrollDelta.y; if (UIBlocker.IsPointerOverUI()) wheel = 0f; // колесо над окнами HUD -- не приближать камеру (05.11) if (Mathf.Abs(wheel) > 0.01f) Zoom(wheel * 0.1f); // Два пальца -- приближение, как на карте мира: расстояние камеры меняется во столько раз, // во сколько изменилось расстояние между пальцами за кадр (не зависит от того, увидели ли касание) (05.11) if (Input.touchCount >= 2) { Touch a = Input.GetTouch(0), b = Input.GetTouch(1); float previous = ((a.position - a.deltaPosition) - (b.position - b.deltaPosition)).magnitude; float current = (a.position - b.position).magnitude; if (previous > 0.01f && current > 0.01f) { focusing = false; distance = Mathf.Clamp(distance * previous / current, minDistance, maxDistance); } } if (focusing) { target = Vector3.Lerp(target, focusTarget, Time.unscaledDeltaTime * 2.5f); distance = Mathf.Lerp(distance, focusDistance, Time.unscaledDeltaTime * 2.5f); if (Vector3.Distance(target, focusTarget) < 0.05f && Mathf.Abs(distance - focusDistance) < 0.05f) focusing = false; } Apply(); } void Clamp() { target.x = Mathf.Clamp(target.x, bounds.xMin, bounds.xMax); target.z = Mathf.Clamp(target.z, bounds.yMin, bounds.yMax); target.y = 0; } public 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; } }