162 lines
6.4 KiB
C#
162 lines
6.4 KiB
C#
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<Camera>();
|
||
|
|
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<Renderer>(FindObjectsSortMode.None))
|
||
|
|
{
|
||
|
|
string tn = r.GetType().Name;
|
||
|
|
if (tn != "SpriteRenderer" && tn != "TilemapRenderer") continue; // только карта и здания
|
||
|
|
if (r.gameObject.layer == 5) continue; // UI
|
||
|
|
if (r.GetComponentInParent<Canvas>() != 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);
|
||
|
|
}
|
||
|
|
}
|