126 lines
6.0 KiB
C#
126 lines
6.0 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;
|
|
|
|
// Перемещение камеры пальцем можно выключить на время перетаскивания здания
|
|
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; }
|
|
|
|
// Палец ещё не сдвинулся настолько, чтобы считать это перетаскиванием: камера стоит, но точка запоминается --
|
|
// когда перетаскивание начнётся, карта не прыгнет (как на карте мира, 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 (City3DInput.OverHud(Input.mousePosition)) 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;
|
|
}
|
|
|
|
// Дымка начинается за городом, а не на фиксированном расстоянии от камеры: иначе при отдалении весь город
|
|
// уходил в серую дымку -- казалось, что камера не отдаляется, а сцена сереет (05.11)
|
|
public float fogStartBehind = 4f, fogEndBehind = 59f;
|
|
|
|
public void Apply()
|
|
{
|
|
Quaternion rot = Quaternion.Euler(pitch, yaw, 0);
|
|
transform.rotation = rot;
|
|
transform.position = target - rot * Vector3.forward * distance;
|
|
if (RenderSettings.fog && RenderSettings.fogMode == FogMode.Linear)
|
|
{
|
|
RenderSettings.fogStartDistance = distance + fogStartBehind;
|
|
RenderSettings.fogEndDistance = distance + fogEndBehind;
|
|
}
|
|
}
|
|
|
|
// Точка на плоскости земли (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;
|
|
}
|
|
}
|