using UnityEngine; using UnityEngine.InputSystem; // ReSharper disable once CheckNamespace namespace BlueWaterProject { public class IslandCameraController : MonoBehaviour { #region Property and variable [Header("카메라 설정")] [Tooltip("카메라의 중심축이 될 Transform")] [SerializeField] private Transform centerOfCamera; [Tooltip("줌 인, 아웃 속도")] [SerializeField] private float zoomSpeed = 5f; [Tooltip("줌 인을 통해 가능한 FieldOfView의 최솟값")] [SerializeField] private float minZoom = 20f; [Tooltip("줌 아웃을 통해 가능한 FieldOfView의 최댓값")] [SerializeField] private float maxZoom = 50f; [Tooltip("마우스 우클릭을 통해 움직이는 회전 속도")] [SerializeField] private float rotationSpeed = 10f; private float rotationInertia; // 마우스 우클릭을 뗏을 때, 회전 관성 private float desiredZoom; // 목표 Camera.fieldOfView 값 private float currentZoom; // 현재 Camera.fieldOfView 값 private bool isRotating; // 마우스 우클릭을 통한 회전 진행 여부 private float startCameraPosY; // 시작할 때, 카메라의 Y좌표 private Camera islandCamera; #endregion #region Unity built-in function private void Reset() { centerOfCamera = GameObject.Find("CenterOfCamera")?.transform; if (!centerOfCamera) { Debug.LogError("CenterOfCamera object not found"); } zoomSpeed = 5f; minZoom = 20f; maxZoom = 50f; rotationSpeed = 10f; } private void Awake() { islandCamera = Utils.GetComponentAndAssert(transform); desiredZoom = islandCamera.fieldOfView; startCameraPosY = islandCamera.transform.position.y; var controls = new BlueWater(); controls.Camera.Zoom.performed += OnZoom; controls.Camera.Rotate.performed += _ => isRotating = true; controls.Camera.Rotate.canceled += _ => isRotating = false; controls.Enable(); } private void LateUpdate() { SmoothZoom(); HandleRotation(); } #endregion #region New input system /// /// New input system을 이용한 줌 인, 아웃 기능 /// private void OnZoom(InputAction.CallbackContext context) { if (!UsableZoom()) return; var scrollValue = Mouse.current.scroll.ReadValue().normalized.y; desiredZoom = Mathf.Clamp(desiredZoom - scrollValue * zoomSpeed, minZoom, maxZoom); } #endregion #region Custom function /// /// Zoom 기능을 사용가능한 상태인지 확인하는 기능 /// private bool UsableZoom() { var mousePos = Input.mousePosition; // 백그라운드 실행이 아닌, 게임 화면을 실행한 상태인지 체크 var isFocusedGame = Application.isFocused; // 화면 내에 마우스가 존재하는지 체크 var isMouseWithinGameScreen = mousePos.x >= 0 && mousePos.x <= Screen.width && mousePos.y >= 0 && mousePos.y <= Screen.height; return isFocusedGame && isMouseWithinGameScreen; } /// /// 자연스러운 줌 인, 아웃 기능 /// private void SmoothZoom() { if (Mathf.Approximately(islandCamera.fieldOfView, desiredZoom)) return; islandCamera.fieldOfView = Mathf.Lerp(islandCamera.fieldOfView, desiredZoom, Time.deltaTime * zoomSpeed); if (islandCamera.fieldOfView > desiredZoom) return; var maxVisibleHeight = GetVisibleHeightAtZoom(maxZoom); var currentVisibleHeight = GetVisibleHeightAtZoom(islandCamera.fieldOfView); var allowedMovement = (maxVisibleHeight - currentVisibleHeight) / 2; var myPos = transform.position; var min = startCameraPosY - allowedMovement; var max = startCameraPosY + allowedMovement; if (myPos.y > max) { transform.position = Vector3.Lerp(transform.position, new Vector3(myPos.x, max, myPos.z), Time.deltaTime * zoomSpeed); } else if (myPos.y < min) { transform.position = Vector3.Lerp(transform.position, new Vector3(myPos.x, min, myPos.z), Time.deltaTime * zoomSpeed); } } /// /// 관성을 이용한 자연스러운 카메라 회전 기능 /// private void HandleRotation() { rotationInertia = isRotating ? Mouse.current.delta.ReadValue().x : Mathf.Lerp(rotationInertia, 0, Time.deltaTime * 2); var rotation = rotationInertia * rotationSpeed * Time.deltaTime; transform.RotateAround(centerOfCamera.position, Vector3.up, rotation); if (!isRotating) return; var maxVisibleHeight = GetVisibleHeightAtZoom(maxZoom); var currentVisibleHeight = GetVisibleHeightAtZoom(islandCamera.fieldOfView); var allowedMovement = (maxVisibleHeight - currentVisibleHeight) / 2; if (Mathf.Abs(allowedMovement) <= 0.01f) return; var verticalMove = -Mouse.current.delta.ReadValue().y * rotationSpeed * Time.deltaTime; var myPos = transform.position; var min = startCameraPosY - allowedMovement; var max = startCameraPosY + allowedMovement; var newY = Mathf.Clamp(myPos.y + verticalMove, min, max); transform.position = new Vector3(myPos.x, newY, myPos.z); } /// /// 카메라의 FieldOfView값에 따라서 보여지는 카메라 영역의 높이 반환 기능 /// private float GetVisibleHeightAtZoom(float fov) { var distanceToCenter = (centerOfCamera.position - transform.position).magnitude; return 2.0f * (distanceToCenter) * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad); } #endregion } }