어디를 눌러도 되는 새총: 당겨서 쏘기 입력 설계
SLINGSTAR의 조작은 "누르고, 당기고, 놓는다" 한 가지다. 버튼도 슬라이더도 없다. 그런데 이 한 동작이 손가락 하나로 조작하는 휴대폰과 정밀한 마우스에서 같은 게임처럼 느껴지려면 정해야 할 것이 여럿 있었다. 당긴 길이를 무엇으로 잴지, 어디를 눌러야 하는지, 실수로 놓았을 때 어떻게 되는지, 조준 중에 무엇을 보여 줄지. 이 글은 그 결정들을 실제 코드와 화면 크기별 수치로 설명한다.
포인터 이벤트 하나로 통일
입력은 캔버스의 Pointer Events 네 개(pointerdown, pointermove, pointerup, pointercancel)로 처리한다. 마우스, 터치, 펜이 모두 같은 경로로 들어오므로 기기별 분기가 없다. 누르는 순간 setPointerCapture를 걸어서, 당기는 도중 손가락이나 커서가 캔버스 밖으로 나가도 이벤트를 계속 받는다. 화면 아래쪽에서 아래로 길게 당기는 일이 잦은 게임이라 이 처리가 없으면 화면 끝에서 조준이 끊긴다.
브라우저 기본 동작도 꺼야 했다. 페이지 전체와 캔버스에 CSS touch-action: none을 걸어 당기는 동작이 스크롤이나 확대로 해석되지 않게 했고, 텍스트 선택과 탭 강조색도 껐다. 마우스 오른쪽 버튼으로 당기다 메뉴가 뜨지 않도록 contextmenu도 막았다. 메뉴의 버튼들만 touch-action: manipulation으로 두어 일반적인 탭 반응을 유지한다.
또 하나, 누르는 순간 오디오 컨텍스트를 깨운다. 브라우저는 사용자 동작 없이 소리를 내지 못하게 막기 때문에, 첫 샷의 발사음이 나려면 그 전에 사용자 제스처 안에서 오디오를 재개해 둬야 한다. pointerdown이 가장 확실한 자리였다.
당긴 길이는 픽셀이 아니라 필드 단위로
누른 지점과 현재 포인터 위치를 모두 필드 좌표(가로 100, 세로 160)로 바꾼 뒤 그 차이를 당김 벡터로 쓴다. 발사 속도는 당긴 길이의 3배이고 최대 130이다. 실제 함수는 이렇다.
function launchVel(dx,dy){
const l=Math.sqrt(dx*dx+dy*dy); if(l<3) return null;
const v=Math.min(MAXV,l*PULL);
return {vx:dx/l*v,vy:dy/l*v,power:v/MAXV}; }
최대 세기에 도달하는 당김 길이는 약 43.3 필드 단위, 필드 너비의 43%쯤이다. 렌더러는 필드를 화면에 꽉 차게 늘려 그리므로 이 길이를 픽셀로 바꾸면 기기마다 다르다. 렌더러의 배율 공식(위 64px, 아래 24px, 좌우 8px 여백)으로 계산해 보면 다음과 같다.
| 화면(CSS px) | 1 단위당 px | 최대 세기 당김 | 취소 반경 | 혜성 반지름 |
|---|---|---|---|---|
| 360 × 640 휴대폰 | 3.44 | 149px | 10px | 5.5px |
| 390 × 844 휴대폰 | 3.74 | 162px | 11px | 6.0px |
| 768 × 1024 태블릿 | 5.85 | 254px | 18px | 9.4px |
| 1920 × 1080 데스크톱 | 6.20 | 269px | 19px | 9.9px |
픽셀 길이는 화면마다 다르지만, 화면 위에 그려진 필드에 대한 비율은 언제나 같다. 휴대폰에서 필드 너비의 절반쯤 당긴 샷은 데스크톱에서도 필드 너비의 절반쯤 당기면 똑같이 나간다. DAILY 코스를 휴대폰으로 친 사람과 PC로 친 사람이 같은 감각을 공유하려면 이쪽이 맞다고 봤다. 픽셀 기준으로 잡았다면 큰 화면일수록 같은 손동작으로 약한 샷이 나가 화면 크기가 난이도가 되었을 것이다.
왜 혜성을 잡지 않고 아무 곳이나 누르는가
새총 게임의 흔한 조작은 쏠 물체를 직접 잡아 끄는 것이다. SLINGSTAR는 화면 어디를 눌러도 그 지점이 새총의 기준점이 된다. 위 표가 이유를 보여 준다. 혜성의 반지름은 휴대폰에서 6px 안팎이다. 지름 12px짜리 점을 손가락으로 정확히 잡는 것은 어렵고, 잡는 순간 손가락이 혜성과 점선 시작 부분을 가린다.
위치 문제도 있다. 시작점은 필드 세로 160 중 125~145 사이, 즉 화면 아래쪽에 생긴다. 블랙홀은 위쪽에 있으니 대부분의 샷은 위로 쏘고, 손가락은 아래로 당겨야 한다. 혜성을 직접 잡는 방식이라면 시작점 아래로 남은 공간이 필드 기준 15~35단위에 아래 여백 정도뿐이라 최대 세기(43단위)까지 당길 자리가 모자라는 홀이 생긴다. 아무 곳이나 누르는 방식에서는 화면 가운데를 누르고 아래로 당기면 되므로 이 제약이 사라진다.
대신 "어디를 눌러야 하지?"라는 첫 질문이 생긴다. 그래서 첫 플레이의 1번 홀에 "화면 아무 곳이나 누르고 뒤로 당겼다 놓으세요"라는 안내를 띄우고, 조준하지 않을 때는 혜성 둘레에 노란 링이 천천히 맥동하게 해서 무엇을 쏘는지는 분명히 보이게 했다.
조준 중에 보여 주는 것
조준 중에는 세 가지를 그린다. 첫째는 혜성에서 당긴 방향으로 뻗는 점선으로, 길이는 당긴 길이와 같고 세기가 셀수록 진해진다. 손가락이 혜성에서 멀리 있어도 고무줄이 혜성에 걸려 있는 것처럼 보이게 하는 장치다. 둘째는 혜성 둘레의 세기 링이다. 12시 방향에서 시작해 시계 방향으로 차오르고, 한 바퀴가 다 차면 최대 세기다. 최대에 도달한 뒤에도 계속 당길 수는 있지만 세기는 더 오르지 않는다는 것을 링이 알려 준다.
셋째가 궤적 점선이다. 포인터가 움직일 때마다 실제 물리로 0.6초(72단계)를 미리 돌려 점 18개로 찍는다. 앞쪽 점은 크고 밝게, 뒤로 갈수록 작고 흐리게 그려서 "가까운 미래일수록 확실하다"는 느낌을 준다. 실제로는 끝까지 정확한 값이지만, 0.6초 뒤의 일은 보여 주지 않으므로 흐려지는 표현이 정보의 끝을 자연스럽게 알린다. 이 계산은 포인터 이벤트마다 일어나지만 72단계는 가벼워서 휴대폰에서도 부담이 없다.

실수를 되돌리는 방법
당겼다가 마음이 바뀌면 누른 지점 가까이로 되돌아와 놓으면 된다. 당김 길이가 3단위 미만이면 발사 속도 계산이 null을 돌려주고, 놓아도 아무 일이 없다. 휴대폰 기준 반경 10~11px로, 손가락이 살짝 떨려 생기는 움직임보다는 크고 의도적으로 되돌아오기에는 충분히 작다. 누르고 움직이지 않은 채 바로 떼는 탭도 조준이 만들어지지 않으므로 발사되지 않는다. 화면을 실수로 건드려 샷 하나를 날리는 일은 없다.
전화가 오거나 시스템 제스처가 끼어들어 pointercancel이 오면 조준을 그냥 지운다. 이때 발사하는 쪽을 택하면 사용자가 의도하지 않은 샷이 기록된다. 혜성이 날아가는 동안이나 블랙홀에 들어간 뒤의 누름은 조준으로 받지 않는다. 다만 홀 완료 카드가 떠 있는 동안 누르면 2.2초 자동 대기를 건너뛰고 다음 홀로 넘어간다.
정리
당겨서 쏘기는 설명 한 줄이면 되는 조작이지만, 기기마다 같은 게임이 되게 하려면 측정 단위, 누르는 위치, 취소 거리, 시각 피드백을 각각 정해야 했다. SLINGSTAR는 모든 길이를 필드 단위로 재고, 혜성 대신 화면 전체를 누를 수 있게 해서 작은 화면의 불리함을 줄였다. 남은 과제는 키보드다. 지금 키보드는 ESC로 일시정지하고 Space·Enter로 메뉴를 넘기는 데만 쓰이고, 조준과 발사는 포인터로만 할 수 있다. 설계 문서에 적어 두고 1.1.1까지 빠져 있던 R 키 홀 재시작은 1.1.2에서 넣었다. 나쁜 행성에 착지했을 때 +1샷을 내고 시작점으로 돌아가는 탈출구이고, 키보드가 없는 기기를 위해 일시정지 메뉴에도 같은 버튼(↺ 시작점에서 다시)을 두었다. 중간에 나갔다 다시 들어오면 시작점으로 돌아가 이 벌타를 피해 갈 수 있었기 때문에, DAILY 진행 저장에 혜성 위치도 함께 넣었다. 방향키로 각도와 세기를 조절하는 조준 방식을 더하면 마우스를 쓰기 어려운 사람도 플레이할 수 있을 것이다.