177 lines
6.5 KiB
C#
177 lines
6.5 KiB
C#
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
|
|
}
|
|
} |