#nullable enable
using UnityEngine;
using UnityEngine.InputSystem;
namespace DTNavigation.CameraSystem
{
///
/// 마우스 기반 카메라 컨트롤러 (New Input System).
///
/// ■ 마우스 조작 (Inspector에서 바인딩 변경 가능)
/// - 휠 스크롤 : 줌 인/아웃
/// - 우클릭 드래그 : 자유 회전 (수평=Y축 / 수직=X축 피치)
/// - 좌클릭 드래그 : 화면 평면 이동 (패닝)
///
/// ■ UI 블로킹 규칙
/// - UI 위에서 새 드래그/줌 시작 불가
/// - 드래그 도중 UI 위로 이동해도 드래그 유지
///
/// ■ 카메라 내비게이션
/// - NavigateToTransform : 씬 오브젝트 Transform으로 부드럽게 이동
/// - NavigateToPosition : 직접 지정한 위치/회전값으로 부드럽게 이동
///
public sealed class CameraController : MonoBehaviour
{
// ── 속도 설정 ──────────────────────────────────────────────
[Header("줌 (마우스 휠)")]
[SerializeField] private float _zoomSpeed = 20f;
[Header("회전 (우클릭 드래그)")]
[SerializeField] private float _rotateSpeed = 120f;
[Header("이동 (좌클릭 드래그)")]
[SerializeField] private float _panSpeed = 0.05f;
[Header("카메라 내비게이션")]
[SerializeField] private float _defaultNavDuration = 1f;
// ── Input Actions (Inspector에서 바인딩 변경 가능) ─────────
[Header("Input Actions")]
[SerializeField] private InputAction _zoomAction = new(
"Zoom",
InputActionType.Value,
binding: "/scroll/y");
[SerializeField] private InputAction _panButtonAction = new(
"Pan",
InputActionType.Button,
binding: "/leftButton");
[SerializeField] private InputAction _rotateButtonAction = new(
"Rotate",
InputActionType.Button,
binding: "/rightButton");
[SerializeField] private InputAction _mouseDeltaAction = new(
"MouseDelta",
InputActionType.Value,
binding: "/delta");
// ── 드래그 상태 ────────────────────────────────────────────
private bool _isDraggingLeft;
private bool _isDraggingRight;
// ── UI 블로킹 ──────────────────────────────────────────────
private bool _isPointerOverUI;
// ── 카메라 내비게이션 ──────────────────────────────────────
private bool _isNavigating;
private Vector3 _navStartPos;
private Quaternion _navStartRot;
private Vector3 _navTargetPos;
private Quaternion _navTargetRot;
private float _navDuration;
private float _navElapsed;
// ── Unity 생명주기 ─────────────────────────────────────────
private void OnEnable()
{
_zoomAction.Enable();
_panButtonAction.Enable();
_rotateButtonAction.Enable();
_mouseDeltaAction.Enable();
}
private void OnDisable()
{
_zoomAction.Disable();
_panButtonAction.Disable();
_rotateButtonAction.Disable();
_mouseDeltaAction.Disable();
}
private void Update()
{
HandleMouseInput();
UpdateNavigation();
}
// ── UI 블로킹 API ──────────────────────────────────────────
/// UI 영역에 포인터가 진입했을 때 NavigationController에서 호출합니다.
public void OnUIPointerEnter() => _isPointerOverUI = true;
/// UI 영역에서 포인터가 벗어났을 때 NavigationController에서 호출합니다.
public void OnUIPointerLeave() => _isPointerOverUI = false;
// ── 카메라 내비게이션 공개 API ─────────────────────────────
///
/// 씬에 배치된 Transform의 위치/회전으로 부드럽게 이동합니다.
/// duration 이 0 이하면 Inspector의 기본값을 사용합니다.
///
public void NavigateToTransform(Transform target, float duration = -1f)
{
var dur = duration < 0f ? _defaultNavDuration : duration;
StartNavigation(target.position, target.rotation, dur);
}
///
/// 직접 지정한 위치/회전 값으로 부드럽게 이동합니다.
/// duration 이 0 이하면 Inspector의 기본값을 사용합니다.
///
public void NavigateToPosition(Vector3 position, Quaternion rotation, float duration = -1f)
{
var dur = duration < 0f ? _defaultNavDuration : duration;
StartNavigation(position, rotation, dur);
}
// ── 마우스 입력 처리 ───────────────────────────────────────
private void HandleMouseInput()
{
if (_isNavigating) return;
// ── 줌: UI 위가 아닐 때만 허용 ────────────────────────
var scroll = _zoomAction.ReadValue();
if (Mathf.Abs(scroll) > 0.0001f && !_isPointerOverUI)
HandleZoom(scroll);
// ── 좌클릭 드래그 시작/종료 ───────────────────────────
if (_panButtonAction.WasPressedThisFrame() && !_isPointerOverUI)
_isDraggingLeft = true;
if (_panButtonAction.WasReleasedThisFrame())
_isDraggingLeft = false;
// ── 우클릭 드래그 시작/종료 ───────────────────────────
if (_rotateButtonAction.WasPressedThisFrame() && !_isPointerOverUI)
_isDraggingRight = true;
if (_rotateButtonAction.WasReleasedThisFrame())
_isDraggingRight = false;
// ── 드래그 처리: 버튼을 떼지 않으면 UI 위에서도 유지 ──
var delta = _mouseDeltaAction.ReadValue();
if (_isDraggingLeft) HandlePan(delta);
if (_isDraggingRight) HandleRotate(delta);
}
private void HandleZoom(float scroll)
=> transform.Translate(0f, 0f, scroll * _zoomSpeed * Time.deltaTime * 60f, Space.Self);
private void HandlePan(Vector2 delta)
=> transform.Translate(-delta.x * _panSpeed, -delta.y * _panSpeed, 0f, Space.Self);
private void HandleRotate(Vector2 delta)
{
// 수평: 월드 Y축 기준 회전
transform.Rotate(Vector3.up, delta.x * _rotateSpeed * Time.deltaTime, Space.World);
// 수직: 로컬 X축 기준 피치
transform.Rotate(Vector3.right, -delta.y * _rotateSpeed * Time.deltaTime, Space.Self);
}
// ── 카메라 내비게이션 내부 ─────────────────────────────────
private void StartNavigation(Vector3 targetPos, Quaternion targetRot, float duration)
{
_navStartPos = transform.position;
_navStartRot = transform.rotation;
_navTargetPos = targetPos;
_navTargetRot = targetRot;
_navDuration = Mathf.Max(duration, 0.01f);
_navElapsed = 0f;
_isNavigating = true;
_isDraggingLeft = false;
_isDraggingRight = false;
}
private void UpdateNavigation()
{
if (!_isNavigating) return;
_navElapsed += Time.deltaTime;
var t = Mathf.SmoothStep(0f, 1f, Mathf.Clamp01(_navElapsed / _navDuration));
transform.position = Vector3.Lerp(_navStartPos, _navTargetPos, t);
transform.rotation = Quaternion.Slerp(_navStartRot, _navTargetRot, t);
if (_navElapsed >= _navDuration)
{
transform.position = _navTargetPos;
transform.rotation = _navTargetRot;
_isNavigating = false;
}
}
}
}