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); } }