Files
GeoSusGame/Assets/Scripts/MapCameraController.cs
2026-04-26 13:30:33 +02:00

217 lines
8.4 KiB
C#

using UnityEngine;
/// <summary>
/// Attach to Main Camera in Client.unity.
/// Top-down perspective camera that follows the local player capsule.
///
/// Features:
/// • Auto-follow player when tracking (can be paused by dragging)
/// • Single-finger touch drag (or mouse drag) to pan
/// • Pinch gesture (or mouse scroll wheel) to zoom (changes camera height)
/// • Double-tap anywhere to instantly recenter on player
/// • Static Recenter() method called by the HUD recenter button
/// </summary>
public class MapCameraController : MonoBehaviour
{
// ── Singleton (weak — no DontDestroyOnLoad needed, camera lives in Client.unity) ──
public static MapCameraController Instance { get; private set; }
// ── Public API ────────────────────────────────────────────────────────────
public void SetTarget(GameObject target) { _target = target; }
public void Recenter() { _isTracking = true; _resumeTimer = 0f; }
// ── Tuning ────────────────────────────────────────────────────────────────
private const float FollowSmoothing = 8f; // lerp speed when tracking
private const float DefaultHeight = 150f; // camera Y (metres above ground)
private const float MinHeight = 30f; // closest zoom
private const float MaxHeight = 350f; // furthest zoom
private const float PinchZoomSens = 1.2f; // multiplier for pinch speed
private const float ScrollZoomSens = 30f; // world-units per scroll tick
private const float ResumeDelay = 3.5f; // s after drag ends before auto-tracking resumes
private const float DoubleTapWindow = 0.32f; // s between taps to count as double
private const float DragThreshold = 8f; // pixels moved before drag starts
// ── Runtime state ─────────────────────────────────────────────────────────
private Camera _cam;
private GameObject _target;
private float _currentHeight;
private bool _isTracking = true;
private float _resumeTimer;
// Drag
private bool _dragActive;
private Vector2 _lastDragScreen;
// Pinch
private float _pinchStartDist = -1f;
private float _pinchStartHeight;
// Double-tap
private int _tapCount;
private float _tapTimer;
// ── MonoBehaviour ─────────────────────────────────────────────────────────
void Awake()
{
Instance = this;
_cam = GetComponent<Camera>();
if (_cam == null) { Debug.LogError("[MapCamera] No Camera component!"); return; }
// Keep existing perspective mode — just ensure straight-down orientation
transform.rotation = Quaternion.Euler(90f, 0f, 0f);
_currentHeight = transform.position.y > 1f ? transform.position.y : DefaultHeight;
transform.position = new Vector3(transform.position.x, _currentHeight, transform.position.z);
}
void OnEnable() { Instance = this; }
void LateUpdate()
{
HandleInput();
FollowTarget();
}
// ── Target following ──────────────────────────────────────────────────────
void FollowTarget()
{
if (!_isTracking || _target == null) return;
Vector3 tp = _target.transform.position;
Vector3 dest = new Vector3(tp.x, _currentHeight, tp.z);
transform.position = Vector3.Lerp(transform.position, dest, Time.deltaTime * FollowSmoothing);
}
// ── Input ─────────────────────────────────────────────────────────────────
void HandleInput()
{
// Auto-resume tracking after a period of no dragging
if (!_isTracking)
{
_resumeTimer += Time.deltaTime;
if (_resumeTimer >= ResumeDelay) _isTracking = true;
}
// Double-tap timer
_tapTimer += Time.deltaTime;
if (_tapTimer > DoubleTapWindow) _tapCount = 0;
int tc = Input.touchCount;
if (tc == 2)
{
HandlePinch();
return;
}
_pinchStartDist = -1f; // reset pinch when not 2 fingers
if (tc == 1)
{
Touch t = Input.GetTouch(0);
switch (t.phase)
{
case TouchPhase.Began:
OnPointerDown(t.position);
break;
case TouchPhase.Moved:
case TouchPhase.Stationary:
OnPointerDrag(t.position);
break;
case TouchPhase.Ended:
case TouchPhase.Canceled:
OnPointerUp();
break;
}
return;
}
// Mouse fallback (editor / desktop)
if (Input.GetMouseButtonDown(0)) OnPointerDown(Input.mousePosition);
else if (Input.GetMouseButton(0)) OnPointerDrag(Input.mousePosition);
else if (Input.GetMouseButtonUp(0)) OnPointerUp();
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Abs(scroll) > 0.001f)
{
_currentHeight = Mathf.Clamp(_currentHeight - scroll * ScrollZoomSens, MinHeight, MaxHeight);
transform.position = new Vector3(transform.position.x, _currentHeight, transform.position.z);
}
}
void OnPointerDown(Vector2 screenPos)
{
_lastDragScreen = screenPos;
_dragActive = false;
// Double-tap detection
_tapCount++;
_tapTimer = 0f;
if (_tapCount >= 2)
{
_tapCount = 0;
Recenter();
}
}
void OnPointerDrag(Vector2 screenPos)
{
Vector2 screenDelta = screenPos - _lastDragScreen;
if (!_dragActive && screenDelta.magnitude > DragThreshold)
{
_dragActive = true;
_isTracking = false;
_resumeTimer = 0f;
}
if (_dragActive)
{
// Pan: move camera so that the world point under the finger stays fixed.
// Because the camera faces straight down, we can use a simpler formula:
// pixels → world = (camera height / focal length in pixels) ratio.
// For perspective: visible half-height at ground = height * tan(fov/2)
// world_per_pixel = 2 * height * tan(fov/2) / screenHeight
float halfFovRad = _cam.fieldOfView * 0.5f * Mathf.Deg2Rad;
float worldPerPixelY = 2f * _currentHeight * Mathf.Tan(halfFovRad) / Screen.height;
float worldPerPixelX = worldPerPixelY * ((float)Screen.width / Screen.height);
// Flip: dragging right moves world right (camera moves left)
transform.position += new Vector3(
-screenDelta.x * worldPerPixelX,
0f,
-screenDelta.y * worldPerPixelY
);
}
_lastDragScreen = screenPos;
}
void OnPointerUp()
{
_dragActive = false;
}
// ── Pinch zoom ────────────────────────────────────────────────────────────
void HandlePinch()
{
Touch t0 = Input.GetTouch(0);
Touch t1 = Input.GetTouch(1);
if (t0.phase == TouchPhase.Began || t1.phase == TouchPhase.Began)
{
_pinchStartDist = Vector2.Distance(t0.position, t1.position);
_pinchStartHeight = _currentHeight;
return;
}
if (_pinchStartDist <= 0f) return;
float currentDist = Vector2.Distance(t0.position, t1.position);
if (currentDist < 1f) return;
// Closer fingers = zoom in (lower height)
float ratio = _pinchStartDist / currentDist;
_currentHeight = Mathf.Clamp(_pinchStartHeight * ratio * PinchZoomSens, MinHeight, MaxHeight);
transform.position = new Vector3(transform.position.x, _currentHeight, transform.position.z);
}
}