떼는 순간이 발사다
Ice Breaker는 조준 드래그를 떼는 순간 발리가 나간다. 그래서 누르고 살짝 움직인 입력, 주머니 속 접촉, 손끝 떨림이 발사가 되면 안 된다. release 처리에서 IsMeaningfulAimDrag를 통과한 경우에만 RunVolley 코루틴이 시작된다. 문턱에 미치지 못한 입력은 조준 점만 숨기고 아무 일도 하지 않는다.
화면 밀도를 반영한 픽셀 문턱
문턱은 AimDragThresholdPixels가 정한다. max(22px, dpi×0.10)다. dpi 320이면 32px, dpi를 모르는 환경이면 22px 하한이 적용된다. 고정 픽셀 문턱은 저밀도 폰에서는 너무 크고 고밀도 폰에서는 떨림 수준으로 작아지므로, 화면 밀도에 비례한 값에 하한을 둔다. 연결된 Unity API 문서는 Android에서 Screen.dpi가 실측이 아니라 densityDpi 논리 버킷을 따르고 알 수 없으면 0을 반환한다고 설명한다. dpi×0.10은 보고된 DPI 기준 약 0.1인치의 추정이고, 22px 하한과 기기별 DPI 오차 때문에 일정한 물리 길이를 보장하지 않는다.
테스트가 잠그는 것
TinyTapDoesNotQualifyAsAnAimGesture는 정적 함수 두 개를 경계 양쪽에서 검사한다.
이 테스트는 문턱 계산과 게이트 함수만 검사한다. 실제 터치 입력 경로를 시뮬레이션하지 않는다.
- 문턱 값: AimDragThresholdPixels(320)은 32다.
- dpi 적용 경계: 이동량 제곱이 31²이면 false, 33²이면 true다.
- 하한 경계: dpi 0에서 21²이면 false, 23²이면 true다.
소스에서 읽은 구조(테스트 밖)
- 최종 위치가 아니라 최대 이동량: _aimMaxTravelSq는 드래그 중 (현재 위치 − 누른 위치)의 제곱 거리 최댓값을 든다. 문턱 이상으로 이동했다가 시작점으로 돌아와 떼도 드래그로 인정된다.
- 제곱끼리 비교: maxTravelSq >= threshold²로 비교해 프레임마다 제곱근을 계산하지 않는다. 연결된 Vector2.sqrMagnitude 문서가 이 측정값을 설명한다.
- 포인터 고정: 누를 때 터치를 마우스보다 먼저 선택하고, 떼는 판정도 같은 포인터 출처로만 한다. 안드로이드에서 터치가 가상 마우스로도 보이는 경우를 막는다.
- UI 위에서는 시작 안 함: IsPointerOverInteractiveUi가 누른 지점에 상호작용 가능한 Selectable이 있으면 조준을 시작하지 않는다.
다른 프로젝트에서 따라 할 순서
- 탭과 의도한 드래그를 가르는 거리를 정한다.
- dpi 비례 값에 하한을 둬 밀도를 모르는 환경을 커버한다.
- 드래그 중 최대 이동량의 제곱을 추적해 제곱끼리 비교한다.
- 경계 양쪽과 하한 양쪽을 테스트로 고정한다.
- 누르기부터 떼기까지 같은 포인터 출처를 유지한다.
이 기록이 증명하지 않는 것
9월 8일 PlayMode 30/30 실행 기록과 고정 커밋의 소스를 9월 21-22일에 대조했다. 테스트는 문턱 함수의 경계만 잠근다. 0.10과 22px이 실제 손끝 떨림에 맞는 값인지, 실제 터치 입력이 이 게이트를 예상대로 지나는지, 누른 지점의 UI 차단이 모든 기기에서 동작하는지는 검증하지 않았다.
기존 실행기록·소스 대조
검증일
검증 환경Unity 6000.5.3f1 · Windows Editor · September 8 retained PlayMode receipts; GameSession input source review on September 21-22
Ice Breaker PR #40의 병합 소스와 2026년 9월 8일 Unity 6000.5.3f1 PlayMode 30/30 시험 XML을 대조했다. TinyTapDoesNotQualifyAsAnAimGesture는 그 실행 안에 포함된다. 글 작성 중 게임을 새로 실행하지 않았고, 0.10·22px 계수가 실제 손끝 떨림에 적합한지는 실기기에서 검증하지 않는다.