204 lines
8.8 KiB
C#
204 lines
8.8 KiB
C#
#nullable enable
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
|
|
namespace DTNavigation.CameraSystem
|
|
{
|
|
/// <summary>
|
|
/// 마우스 기반 카메라 컨트롤러 (New Input System).
|
|
///
|
|
/// ■ 마우스 조작 (Inspector에서 바인딩 변경 가능)
|
|
/// - 휠 스크롤 : 줌 인/아웃
|
|
/// - 우클릭 드래그 : 자유 회전 (수평=Y축 / 수직=X축 피치)
|
|
/// - 좌클릭 드래그 : 화면 평면 이동 (패닝)
|
|
///
|
|
/// ■ UI 블로킹 규칙
|
|
/// - UI 위에서 새 드래그/줌 시작 불가
|
|
/// - 드래그 도중 UI 위로 이동해도 드래그 유지
|
|
///
|
|
/// ■ 카메라 내비게이션
|
|
/// - NavigateToTransform : 씬 오브젝트 Transform으로 부드럽게 이동
|
|
/// - NavigateToPosition : 직접 지정한 위치/회전값으로 부드럽게 이동
|
|
/// </summary>
|
|
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: "<Mouse>/scroll/y");
|
|
|
|
[SerializeField] private InputAction _panButtonAction = new(
|
|
"Pan",
|
|
InputActionType.Button,
|
|
binding: "<Mouse>/leftButton");
|
|
|
|
[SerializeField] private InputAction _rotateButtonAction = new(
|
|
"Rotate",
|
|
InputActionType.Button,
|
|
binding: "<Mouse>/rightButton");
|
|
|
|
[SerializeField] private InputAction _mouseDeltaAction = new(
|
|
"MouseDelta",
|
|
InputActionType.Value,
|
|
binding: "<Mouse>/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 ──────────────────────────────────────────
|
|
/// <summary>UI 영역에 포인터가 진입했을 때 NavigationController에서 호출합니다.</summary>
|
|
public void OnUIPointerEnter() => _isPointerOverUI = true;
|
|
|
|
/// <summary>UI 영역에서 포인터가 벗어났을 때 NavigationController에서 호출합니다.</summary>
|
|
public void OnUIPointerLeave() => _isPointerOverUI = false;
|
|
|
|
// ── 카메라 내비게이션 공개 API ─────────────────────────────
|
|
/// <summary>
|
|
/// 씬에 배치된 Transform의 위치/회전으로 부드럽게 이동합니다.
|
|
/// duration 이 0 이하면 Inspector의 기본값을 사용합니다.
|
|
/// </summary>
|
|
public void NavigateToTransform(Transform target, float duration = -1f)
|
|
{
|
|
var dur = duration < 0f ? _defaultNavDuration : duration;
|
|
StartNavigation(target.position, target.rotation, dur);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 직접 지정한 위치/회전 값으로 부드럽게 이동합니다.
|
|
/// duration 이 0 이하면 Inspector의 기본값을 사용합니다.
|
|
/// </summary>
|
|
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<float>();
|
|
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<Vector2>();
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|