OldBlueWater/BlueWater/Assets/02.Scripts/IslandCameraController.cs

177 lines
6.5 KiB
C#
Raw Normal View History

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<Camera>(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 Custom function
/// <summary>
/// New input system을 이용한 줌 인, 아웃 기능
/// </summary>
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);
}
/// <summary>
/// Zoom 기능을 사용가능한 상태인지 확인하는 기능
/// </summary>
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;
}
/// <summary>
/// 자연스러운 줌 인, 아웃 기능
/// </summary>
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);
}
}
/// <summary>
/// 관성을 이용한 자연스러운 카메라 회전 기능
/// </summary>
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);
}
/// <summary>
/// 카메라의 FieldOfView값에 따라서 보여지는 카메라 영역의 높이 반환 기능
/// </summary>
private float GetVisibleHeightAtZoom(float fov)
{
var distanceToCenter = (centerOfCamera.position - transform.position).magnitude;
return 2.0f * (distanceToCenter) * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
}
#endregion
}
}