159 lines
5.0 KiB
C#
159 lines
5.0 KiB
C#
|
|
using UnityEngine;
|
||
|
|
using UnityEngine.Events;
|
||
|
|
|
||
|
|
public class TouchToCloseOrDragCamera : MonoBehaviour
|
||
|
|
{
|
||
|
|
[Header("Targets")]
|
||
|
|
public Camera cam;
|
||
|
|
public Transform orbitTarget; // 기준점(없으면 월드원점)
|
||
|
|
|
||
|
|
[Header("Tap Settings")]
|
||
|
|
public float tapMaxTime = 0.25f; // 이 시간 이내면 탭
|
||
|
|
public float tapMaxMovePixels = 10f; // 이 픽셀 이내면 탭
|
||
|
|
|
||
|
|
[Header("Orbit Settings")]
|
||
|
|
public float orbitSpeedX = 0.2f; // 좌우 회전 감도
|
||
|
|
public float orbitSpeedY = 0.2f; // 상하 회전 감도
|
||
|
|
public float minPitch = -80f;
|
||
|
|
public float maxPitch = 80f;
|
||
|
|
public float distance = 6f; // 타깃까지 거리
|
||
|
|
|
||
|
|
[Header("On Tap Action")]
|
||
|
|
public UnityEvent OnSingleTap; // 탭 시 실행. 기본으로 현재 화면 비활성화하도록 세팅
|
||
|
|
|
||
|
|
// 내부 상태
|
||
|
|
int activePointerId = -1;
|
||
|
|
Vector2 downPos;
|
||
|
|
float downTime;
|
||
|
|
bool dragging;
|
||
|
|
float yaw, pitch;
|
||
|
|
|
||
|
|
void Reset()
|
||
|
|
{
|
||
|
|
cam = Camera.main;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Awake()
|
||
|
|
{
|
||
|
|
if (cam == null) cam = Camera.main;
|
||
|
|
if (orbitTarget == null) { var t = new GameObject("OrbitTarget").transform; t.position = Vector3.zero; orbitTarget = t; }
|
||
|
|
// 기본 탭 동작: 이 스크립트가 달린 루트 오브젝트 비활성화(화면 종료 역할)
|
||
|
|
if (OnSingleTap == null) OnSingleTap = new UnityEvent();
|
||
|
|
if (OnSingleTap.GetPersistentEventCount() == 0)
|
||
|
|
OnSingleTap.AddListener(() => gameObject.SetActive(false));
|
||
|
|
|
||
|
|
// 초기 카메라 각도 계산
|
||
|
|
var toCam = (cam.transform.position - orbitTarget.position).normalized;
|
||
|
|
yaw = Mathf.Atan2(toCam.x, toCam.z) * Mathf.Rad2Deg;
|
||
|
|
pitch = Mathf.Asin(toCam.y) * Mathf.Rad2Deg;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Update()
|
||
|
|
{
|
||
|
|
#if UNITY_EDITOR || UNITY_STANDALONE
|
||
|
|
HandleMouse();
|
||
|
|
#else
|
||
|
|
HandleTouch();
|
||
|
|
#endif
|
||
|
|
// 오빗 카메라 적용
|
||
|
|
ApplyOrbit();
|
||
|
|
}
|
||
|
|
|
||
|
|
void HandleMouse()
|
||
|
|
{
|
||
|
|
if (Input.GetMouseButtonDown(0))
|
||
|
|
{
|
||
|
|
activePointerId = 0;
|
||
|
|
downPos = Input.mousePosition;
|
||
|
|
downTime = Time.unscaledTime;
|
||
|
|
dragging = false;
|
||
|
|
}
|
||
|
|
else if (Input.GetMouseButton(0) && activePointerId == 0)
|
||
|
|
{
|
||
|
|
Vector2 cur = Input.mousePosition;
|
||
|
|
if (!dragging && Vector2.Distance(cur, downPos) > tapMaxMovePixels) dragging = true;
|
||
|
|
if (dragging)
|
||
|
|
{
|
||
|
|
Vector2 delta = (Vector2)Input.mousePosition - downPos;
|
||
|
|
OrbitByDelta(delta);
|
||
|
|
downPos = cur; // 연속 드래그
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (Input.GetMouseButtonUp(0) && activePointerId == 0)
|
||
|
|
{
|
||
|
|
Vector2 upPos = Input.mousePosition;
|
||
|
|
float dt = Time.unscaledTime - downTime;
|
||
|
|
float dist = Vector2.Distance(upPos, downPos);
|
||
|
|
|
||
|
|
if (!dragging && dt <= tapMaxTime && dist <= tapMaxMovePixels)
|
||
|
|
{
|
||
|
|
// 단일 탭
|
||
|
|
OnSingleTap?.Invoke();
|
||
|
|
}
|
||
|
|
activePointerId = -1;
|
||
|
|
dragging = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void HandleTouch()
|
||
|
|
{
|
||
|
|
if (Input.touchCount == 0) return;
|
||
|
|
|
||
|
|
// 이미 추적 중인 손가락이 있으면 그걸 우선
|
||
|
|
int idx = -1;
|
||
|
|
if (activePointerId != -1)
|
||
|
|
{
|
||
|
|
for (int i = 0; i < Input.touchCount; i++)
|
||
|
|
if (Input.touches[i].fingerId == activePointerId) { idx = i; break; }
|
||
|
|
}
|
||
|
|
|
||
|
|
// 없으면 첫 터치로 시작
|
||
|
|
if (idx == -1) { var t0 = Input.touches[0]; idx = 0; activePointerId = t0.fingerId; }
|
||
|
|
|
||
|
|
var t = Input.touches[idx];
|
||
|
|
|
||
|
|
switch (t.phase)
|
||
|
|
{
|
||
|
|
case TouchPhase.Began:
|
||
|
|
downPos = t.position;
|
||
|
|
downTime = Time.unscaledTime;
|
||
|
|
dragging = false;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case TouchPhase.Moved:
|
||
|
|
case TouchPhase.Stationary:
|
||
|
|
if (!dragging && Vector2.Distance(t.position, downPos) > tapMaxMovePixels) dragging = true;
|
||
|
|
if (dragging && t.phase == TouchPhase.Moved)
|
||
|
|
{
|
||
|
|
OrbitByDelta(t.deltaPosition);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case TouchPhase.Ended:
|
||
|
|
case TouchPhase.Canceled:
|
||
|
|
float dt = Time.unscaledTime - downTime;
|
||
|
|
float dist = Vector2.Distance(t.position, downPos);
|
||
|
|
if (!dragging && dt <= tapMaxTime && dist <= tapMaxMovePixels)
|
||
|
|
{
|
||
|
|
OnSingleTap?.Invoke();
|
||
|
|
}
|
||
|
|
activePointerId = -1;
|
||
|
|
dragging = false;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void OrbitByDelta(Vector2 delta)
|
||
|
|
{
|
||
|
|
yaw += delta.x * orbitSpeedX;
|
||
|
|
pitch -= delta.y * orbitSpeedY;
|
||
|
|
pitch = Mathf.Clamp(pitch, minPitch, maxPitch);
|
||
|
|
}
|
||
|
|
|
||
|
|
void ApplyOrbit()
|
||
|
|
{
|
||
|
|
Quaternion rot = Quaternion.Euler(pitch, yaw, 0f);
|
||
|
|
Vector3 pos = orbitTarget.position + rot * (Vector3.back * distance);
|
||
|
|
cam.transform.SetPositionAndRotation(pos, rot);
|
||
|
|
}
|
||
|
|
}
|