Files
MVP1/Assets/Scripts/City3D/City3DCamera.cs
T
AlexanderandClaude Opus 5.5 a3c4a99a79 3D-поселение, этап I: прототип на примитивах (ТЗ от 04.11)
Bogatyri.Settlement.Placement (без Unity): сетка, зоны по Ратуше, река/скалы/деревья/валуны,
отступ, уклон и фундамент, поворот 0/90/180/270, атомарный перенос с тем же id, расчистка один раз; 7 тестов.
Сцена City3D (Tools -> Bogatyri -> 3D-город): местность, призрак, перенос, расчистка, камера 2.5D,
второй рендерер URP (3D) только для её камеры -- 2D-сцены не тронуты.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:50:22 +01:00

107 lines
4.2 KiB
C#

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;
float lastPinch;
// Перемещение камеры пальцем можно выключить на время перетаскивания здания
public bool AllowPan { get; set; } = true;
void Awake()
{
cam = GetComponent<Camera>();
}
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; }
public void Pan(Vector3 screen)
{
if (!panning || cam == null) return;
Vector3 a = GroundPoint(lastPointer), b = GroundPoint(screen);
target += a - b;
lastPointer = screen;
Clamp();
}
public void Zoom(float delta)
{
focusing = false;
distance = Mathf.Clamp(distance * (1f - delta), minDistance, maxDistance);
}
void Update()
{
// Колесо мыши (редактор, ПК)
float wheel = Input.mouseScrollDelta.y;
if (Mathf.Abs(wheel) > 0.01f) Zoom(wheel * 0.1f);
// Два пальца -- приближение
if (Input.touchCount == 2)
{
float d = Vector2.Distance(Input.GetTouch(0).position, Input.GetTouch(1).position);
if (Input.GetTouch(1).phase == TouchPhase.Began) lastPinch = d;
else if (lastPinch > 0) { Zoom((d - lastPinch) / Mathf.Max(200f, Screen.height)); lastPinch = d; }
}
else lastPinch = 0;
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;
}
}