| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- using UnityEngine;
- using UnityEngine.InputSystem;
- /// <summary>
- /// Simple camera movement controller for map navigation.
- /// Supports WASD/arrow keys, right-click drag, and Q/E or mouse wheel zoom.
- /// </summary>
- [RequireComponent(typeof(Camera))]
- public class MazeCameraController : MonoBehaviour
- {
- [Header("Movement")]
- [SerializeField] private float moveSpeed = 20f;
- [SerializeField] private float dragSpeed = 1f;
- [Header("Zoom")]
- [SerializeField] private float zoomSpeed = 25f;
- [SerializeField] private float zoomMin = 5f;
- [SerializeField] private float zoomMax = 120f;
- [Header("Optional Bounds")]
- [SerializeField] private bool useBounds = false;
- [SerializeField] private Vector2 minBounds = new Vector2(-100f, -100f);
- [SerializeField] private Vector2 maxBounds = new Vector2(100f, 100f);
- private Camera cameraComponent;
- private Vector3 targetPosition;
- private Vector3 lastMousePosition;
- private bool isDragging;
- // Smooth zoom and pan to location
- private bool isSmoothlyCentering = false;
- private Vector3 smoothTargetPosition;
- private float smoothTargetZoom;
- private float smoothCenteringSpeed = 5f;
- private float smoothZoomSpeed = 3f;
- private void Awake()
- {
- cameraComponent = GetComponent<Camera>();
- targetPosition = transform.position;
- }
- private void Update()
- {
- if (isSmoothlyCentering)
- {
- UpdateSmoothCentering();
- }
- HandleKeyboardMovement();
- HandleMouseDrag();
- HandleZoom();
- ApplyPosition();
- }
- private void HandleKeyboardMovement()
- {
- var keyboard = Keyboard.current;
- if (keyboard == null)
- {
- return;
- }
- float horizontal = 0f;
- if (keyboard.aKey.isPressed || keyboard.leftArrowKey.isPressed)
- {
- horizontal -= 1f;
- }
- if (keyboard.dKey.isPressed || keyboard.rightArrowKey.isPressed)
- {
- horizontal += 1f;
- }
- float vertical = 0f;
- if (keyboard.sKey.isPressed || keyboard.downArrowKey.isPressed)
- {
- vertical -= 1f;
- }
- if (keyboard.wKey.isPressed || keyboard.upArrowKey.isPressed)
- {
- vertical += 1f;
- }
- if (horizontal != 0f || vertical != 0f)
- {
- isSmoothlyCentering = false; // Interrupt smooth zoom on manual movement
- Vector3 right = transform.right;
- Vector3 forward = Vector3.Cross(transform.right, Vector3.up).normalized;
- Vector3 worldMove = (right * horizontal + forward * vertical).normalized;
- // Use unscaledDeltaTime so camera movement is unaffected by simulation speed/pause
- targetPosition += worldMove * moveSpeed * Time.unscaledDeltaTime;
- }
- if (keyboard.qKey.isPressed)
- {
- isSmoothlyCentering = false; // Interrupt smooth zoom on manual zoom
- Zoom(-zoomSpeed * Time.unscaledDeltaTime);
- }
- else if (keyboard.eKey.isPressed)
- {
- isSmoothlyCentering = false; // Interrupt smooth zoom on manual zoom
- Zoom(zoomSpeed * Time.unscaledDeltaTime);
- }
- }
- private void HandleMouseDrag()
- {
- var mouse = Mouse.current;
- if (mouse == null)
- {
- return;
- }
- if (mouse.rightButton.wasPressedThisFrame)
- {
- isSmoothlyCentering = false; // Interrupt smooth zoom on manual drag
- isDragging = true;
- Vector2 position = mouse.position.ReadValue();
- lastMousePosition = new Vector3(position.x, position.y, 0f);
- }
- if (mouse.rightButton.wasReleasedThisFrame)
- {
- isDragging = false;
- }
- if (isDragging && mouse.rightButton.isPressed)
- {
- Vector2 position = mouse.position.ReadValue();
- Vector3 currentMousePosition = new Vector3(position.x, position.y, 0f);
- if (currentMousePosition != lastMousePosition)
- {
- Vector3 dragDelta = GetDragWorldDelta(lastMousePosition, currentMousePosition);
- targetPosition += dragDelta * dragSpeed;
- lastMousePosition = currentMousePosition;
- }
- }
- }
- private void HandleZoom()
- {
- var mouse = Mouse.current;
- if (mouse != null)
- {
- float scroll = mouse.scroll.ReadValue().y;
- if (scroll != 0f)
- {
- isSmoothlyCentering = false; // Interrupt smooth zoom on manual zoom
- Zoom(-scroll * zoomSpeed * Time.unscaledDeltaTime);
- }
- }
- }
- private void Zoom(float amount)
- {
- if (cameraComponent.orthographic)
- {
- cameraComponent.orthographicSize = Mathf.Clamp(cameraComponent.orthographicSize + amount, zoomMin, zoomMax);
- }
- else
- {
- cameraComponent.fieldOfView = Mathf.Clamp(cameraComponent.fieldOfView + amount, zoomMin, zoomMax);
- }
- }
- private Vector3 GetDragWorldDelta(Vector3 previousScreenPosition, Vector3 currentScreenPosition)
- {
- Plane dragPlane = new Plane(-transform.forward, transform.position);
- Ray previousRay = cameraComponent.ScreenPointToRay(previousScreenPosition);
- Ray currentRay = cameraComponent.ScreenPointToRay(currentScreenPosition);
- if (dragPlane.Raycast(previousRay, out float previousDistance) && dragPlane.Raycast(currentRay, out float currentDistance))
- {
- Vector3 previousWorld = previousRay.GetPoint(previousDistance);
- Vector3 currentWorld = currentRay.GetPoint(currentDistance);
- return previousWorld - currentWorld;
- }
- return Vector3.zero;
- }
- private void ApplyPosition()
- {
- Vector3 newPosition = targetPosition;
- if (useBounds)
- {
- newPosition.x = Mathf.Clamp(newPosition.x, minBounds.x, maxBounds.x);
- newPosition.z = Mathf.Clamp(newPosition.z, minBounds.y, maxBounds.y);
- }
- transform.position = newPosition;
- }
- /// <summary>
- /// Smoothly zooms and pans the camera to a specific room
- /// </summary>
- /// <param name="room">The room to zoom to</param>
- /// <param name="zoomPadding">Multiplier for room size (1.0 = exactly room size, 1.3 = 30% larger)</param>
- /// <param name="transitionDuration">Time to complete the zoom/pan in seconds</param>
- public void ZoomToRoom(MazeRoom room, float zoomPadding = 1.3f, float transitionDuration = 0.5f)
- {
- if (room == null) return;
- // Stop any previous zoom to prevent stacking
- isSmoothlyCentering = false;
- // Calculate room center in world space (XZ plane only - don't change camera height Y)
- Vector2Int roomCenter = room.GetCenter();
- Vector3 roomCenterXZ = new Vector3(roomCenter.x + 0.5f, targetPosition.y, roomCenter.y + 0.5f); // Keep Y unchanged
- // Calculate required zoom level to fit room with padding
- float roomWidth = room.Width;
- float roomHeight = room.Height;
- float maxRoomDim = Mathf.Max(roomWidth, roomHeight) * zoomPadding;
- // Adjust for camera aspect ratio
- float requiredZoom = maxRoomDim / (2f * Mathf.Tan(cameraComponent.fieldOfView * Mathf.Deg2Rad / 2f));
- if (cameraComponent.orthographic)
- {
- requiredZoom = maxRoomDim * 0.5f;
- }
- // Set smooth transition targets
- smoothTargetPosition = roomCenterXZ; // Pan to room center on XZ plane only
- smoothTargetZoom = Mathf.Clamp(requiredZoom, zoomMin, zoomMax);
- smoothCenteringSpeed = 1f / transitionDuration;
- isSmoothlyCentering = true;
- }
- /// <summary>
- /// Updates the smooth camera transition toward target position and zoom
- /// </summary>
- private void UpdateSmoothCentering()
- {
- // Smooth pan to target position - use unscaledDeltaTime so it works while paused/slow
- float posDist = Vector3.Distance(targetPosition, smoothTargetPosition);
- if (posDist > 0.01f)
- {
- targetPosition = Vector3.Lerp(targetPosition, smoothTargetPosition, smoothCenteringSpeed * Time.unscaledDeltaTime);
- }
- else
- {
- targetPosition = smoothTargetPosition; // Snap to target
- }
- // Smooth zoom to target
- float currentZoom = cameraComponent.orthographic ? cameraComponent.orthographicSize : cameraComponent.fieldOfView;
- float zoomDist = Mathf.Abs(currentZoom - smoothTargetZoom);
- if (zoomDist > 0.01f)
- {
- if (cameraComponent.orthographic)
- {
- cameraComponent.orthographicSize = Mathf.Lerp(cameraComponent.orthographicSize, smoothTargetZoom, smoothZoomSpeed * Time.unscaledDeltaTime);
- }
- else
- {
- cameraComponent.fieldOfView = Mathf.Lerp(cameraComponent.fieldOfView, smoothTargetZoom, smoothZoomSpeed * Time.unscaledDeltaTime);
- }
- }
- else
- {
- // Snap to target zoom
- if (cameraComponent.orthographic)
- {
- cameraComponent.orthographicSize = smoothTargetZoom;
- }
- else
- {
- cameraComponent.fieldOfView = smoothTargetZoom;
- }
- }
- // Check if we've reached both targets
- posDist = Vector3.Distance(targetPosition, smoothTargetPosition);
- currentZoom = cameraComponent.orthographic ? cameraComponent.orthographicSize : cameraComponent.fieldOfView;
- zoomDist = Mathf.Abs(currentZoom - smoothTargetZoom);
- if (posDist < 0.01f && zoomDist < 0.01f)
- {
- isSmoothlyCentering = false;
- }
- }
- }
|