using UnityEngine; namespace MazeGen { /// /// Top-down camera rig: smoothly glides to focus on newly entered rooms, and can be /// freely panned with WASD or by holding the middle mouse button and dragging. Any /// manual pan input cancels whatever auto-focus is currently in progress. /// public class MazeCameraController : MonoBehaviour { [SerializeField] private float panSpeed = 8f; [SerializeField] private float focusSharpness = 6f; [SerializeField] private float dragSensitivity = 1f; [SerializeField] private float zoomSpeed = 150f; [SerializeField] private float minOrthographicSize = 2f; [SerializeField] private float maxOrthographicSize = 150f; private Camera _camera; private Vector2? _focusTarget; private Vector2 _lastPointerScreenPos; private bool _isDragging; private void Awake() { _camera = GetComponent(); if (_camera == null) _camera = Camera.main; } /// Smoothly glides the camera to look at worldPosition. public void FocusOn(Vector2 worldPosition) { _focusTarget = worldPosition; } /// Instantly moves the camera to worldPosition, no smoothing. public void SnapTo(Vector2 worldPosition) { _focusTarget = null; transform.position = new Vector3(worldPosition.x, worldPosition.y, transform.position.z); } /// /// Instantly repositions and (if orthographic) zooms out/in so the whole of `bounds` /// is visible, e.g. for a "zoom to fit maze" button. Cancels any in-progress /// auto-focus and does not itself start following anything. /// public void FrameBounds(Bounds bounds, float padding = 1.2f) { _focusTarget = null; Vector2 center = bounds.center; transform.position = new Vector3(center.x, center.y, transform.position.z); if (_camera != null && _camera.orthographic) { float aspect = Mathf.Max(_camera.aspect, 0.01f); float requiredHalfHeight = Mathf.Max(bounds.extents.y, bounds.extents.x / aspect); _camera.orthographicSize = Mathf.Max(requiredHalfHeight * padding, 1f); } } private void Update() { HandleDrag(); HandleKeyboardPan(); HandleScrollZoomFOV(); HandleAutoFocus(); } private void HandleScrollZoomFOV() { if (_camera == null || _camera.orthographic) return; float scroll = MazeInput.GetScrollDelta(); if (Mathf.Approximately(scroll, 0f)) return; // Decreasing FOV zooms in, increasing FOV zooms out _camera.fieldOfView = Mathf.Clamp( _camera.fieldOfView - (scroll * zoomSpeed), minOrthographicSize, maxOrthographicSize ); } private void HandleKeyboardPan() { Vector2 input = MazeInput.ReadWasdAxis(); if (input.sqrMagnitude <= 0f) return; _focusTarget = null; // manual input takes over from auto-focus transform.position += new Vector3(input.x, input.y, 0f) * (panSpeed * Time.deltaTime); } private void HandleDrag() { bool middleHeldNow = MazeInput.IsMiddleMouseHeld(); Vector2 pointerNow = MazeInput.GetPointerScreenPosition(); if (middleHeldNow && !_isDragging) { _isDragging = true; _lastPointerScreenPos = pointerNow; return; } if (middleHeldNow) { Vector2 screenDelta = pointerNow - _lastPointerScreenPos; if (screenDelta.sqrMagnitude > 0f) { _focusTarget = null; // manual input takes over from auto-focus Vector2 worldDelta = ScreenDeltaToWorldDelta(screenDelta); transform.position -= new Vector3(worldDelta.x, worldDelta.y, 0f); } _lastPointerScreenPos = pointerNow; } else { _isDragging = false; } } private void HandleAutoFocus() { if (!_focusTarget.HasValue) return; Vector2 current = transform.position; Vector2 target = _focusTarget.Value; // Framerate-independent exponential smoothing rather than a constant-speed move, // so the glide eases in and settles instead of arriving abruptly. float t = 1f - Mathf.Exp(-focusSharpness * Time.deltaTime); Vector2 next = Vector2.Lerp(current, target, t); transform.position = new Vector3(next.x, next.y, transform.position.z); if (Vector2.Distance(next, target) < 0.01f) _focusTarget = null; } private Vector2 ScreenDeltaToWorldDelta(Vector2 screenDelta) { if (_camera == null || Screen.height <= 0) return Vector2.zero; float worldUnitsPerPixel; if (_camera.orthographic) { worldUnitsPerPixel = (_camera.orthographicSize * 2f) / Screen.height; } else { // Rooms sit on the z = 0 plane; use the camera's distance to it as the // reference plane for converting a screen-space drag into world units. float distance = Mathf.Abs(transform.position.z); worldUnitsPerPixel = (2f * distance * Mathf.Tan(_camera.fieldOfView * 0.5f * Mathf.Deg2Rad)) / Screen.height; } return screenDelta * worldUnitsPerPixel * dragSensitivity; } } }