휴대폰에서 세로로 잡고 하는 배틀로얄 — Salmonholic 모바일 대응기
마우스 전용이던 Salmonholic을 휴대폰 세로 화면에서 플레이할 수 있게 만든 과정. 플로팅 조이스틱, 40px 터치 타깃, 같은 시야 면적 공식, 해상도 상한과 느린 폰 안전장치까지 커밋 기록으로 정리했다.

Salmonholic은 처음에 마우스로 하는 게임이었다. 고양이가 커서를 따라 움직이고, 공격은 자동으로 나간다. 휴대폰에서도 브라우저만 있으면 열리긴 했지만 “열린다”와 “할 만하다”는 거리가 멀었다. 이 글은 9월 24일부터 27일까지 모바일 대응에 들인 작업을 커밋 순서대로 정리한 기록이다.
시작은 내 손으로 해 본 모바일 플레이
CrazyGames에 제출하기 직전, 휴대폰으로 직접 몇 판을 해 봤다. 문제는 바로 드러났다. 손가락이 고양이를 가리고, 버튼은 너무 작고, 설정 화면의 슬라이더는 손끝으로 잡히지 않았다. 9월 24일 0.11.3 커밋 메시지 첫 줄이 “Developer mobile playtest before CrazyGames submission”인 이유다.
이 버전에서 두 가지를 넣었다.
- 플로팅 조이스틱: 화면 아무 곳이나 처음 닿은 자리가 중심이 된다. 반지름 44, 데드존 8%, 반지름의 25%만 밀어도 최고 속도가 나온다. 두 번째 손가락은 이동에 영향을 주지 않고, 강화 카드가 떠 있으면 카드 탭이 우선한다.
- 터치 타깃 40px: 터치 기기에서 누를 수 있는 모든 컨트롤을 최소 40 CSS 픽셀로 키웠다. 설정은 터치에서만 두 열로 다시 배치했다.
조이스틱 첫 검증 화면(0.11.3 무렵). 손가락이 닿은 곳에 원이 생기고, 안쪽 원을 끄는 방향으로 고양이가 걷는다.
엔진에 VirtualJoystick 같은 기성 노드가 있었지만 쓰지 않았다. 웹 용량을 줄이려고 고급 GUI 모듈을 뺀 슬림 템플릿으로 빌드하고 있어서다(이 이야기는 Godot 4 웹 내보내기 글에 따로 적었다). 대신 기존 HUD의 원 그리기만으로 조이스틱을 그렸다. 서버 프로토콜은 한 줄도 바꾸지 않았다. 조이스틱이 만드는 방향 벡터는 마우스와 똑같이 축마다 127을 곱해 전송된다.
터치 타깃 검증은 9개 언어 × 7개 화면 × 3개 환경으로 191개 상태, 컨트롤 1,138개를 자동으로 계측했다. 크기 미달·겹침은 0건이었고 가장 작은 버튼은 844×390 화면에서 43.33 CSS 픽셀이었다. 다만 조이스틱 작업 보고서에는 “모든 버튼 40px 조건은 아직 충족했다고 할 수 없다”는 문장이 먼저 남아 있었다. 두 작업을 합친 뒤에야 조건이 채워졌다.
회전 안내 카드를 지우고 세로로 플레이하기
0.11.3까지는 휴대폰을 세로로 들면 “가로로 돌려 주세요” 카드가 떴다. 그런데 포털 iframe 안에서는 전체 화면 권한이 막혀 있을 수 있어서 게임이 방향을 강제할 방법이 마땅치 않고, 사람들은 휴대폰을 세로로 든 채 게임을 연다. 9월 25일 0.14.0에서 회전 카드를 통째로 지우고 세로 플레이를 넣었다.
가장 고민한 건 공정성이다. 같은 방에서 가로로 하는 사람과 세로로 하는 사람이 만나는데, 한쪽이 더 넓게 보면 안 된다. 그래서 카메라 줌에 화면 비율 보정을 곱했다.
| 화면 | 논리 해상도 | 줌 1에서 보이는 월드 | 면적 |
|---|---|---|---|
| 기존 가로 | 640×360 | 640×360 | 230,400 |
| 390×844 세로 | 390×844 | 326×706 | 230,400 |
| 360×800 세로 | 390×867 | 322×716 | 230,400 |
실제 줌은 레벨 줌 × √(가로×세로 ÷ 230,400)이다. 세로는 좁고 길게, 가로는 넓고 짧게 보이지만 보이는 넓이는 같다. 시뮬레이션이 방향에 영향을 받지 않는지도 확인했다. 같은 시드로 20초를 돌렸을 때 세로와 가로의 결과는 연어 처치 1,057마리, 경험치 수집 836개까지 성능 수치를 뺀 전부가 같았다.
회전 자체에서도 버그가 하나 나왔다. 창 크기 변경 신호에 바로 배율을 적용하면, 엔진이 창 크기를 갱신하기 전에 계산해서 그림이 좁게 눌려 보였다. 신호 연결을 CONNECT_DEFERRED로 바꿔 한 프레임 뒤에 적용하니 해결됐다. 경기 도중 가로로 돌렸다 돌아와도 경기 번호와 시간이 그대로 유지되는 것까지 확인했다.
휴대폰에서 글자가 흐릿했던 이유
세로 플레이를 넣으면서 휴대폰 렌더링 상한도 다시 봤다. 원래는 폰에서 720p를 넘지 않게 막아 두었는데, DPR 3인 폰에서는 이게 곧 흐릿한 글자를 뜻했다. 390×844 화면이면 585×1266으로 그린 뒤 늘려 붙이는 식이었다.
HP 글자를 400%로 확대한 전후 비교. 위가 720p 상한 시절, 아래가 물리 해상도로 그린 뒤다.
새 상한은 폰 긴 변 2,880 / 짧은 변 1,440, 데스크톱 3,200 / 2,000이다. 상한 안이면 DPR을 그대로 쓰고, 넘으면 물리 해상도의 정수 약수로 그린다. 1.5배 같은 배율로 늘리면 픽셀 폭이 들쭉날쭉해져서 픽셀아트가 깨지기 때문이다. 같은 390×844 폰은 이제 1170×2532로 그린다.
픽셀이 네 배가 되면 느린 폰이 걱정이다. 그래서 안전장치를 하나 넣었다. 프레임 간격 중앙값이 4초 동안 28ms를 넘으면 해상도 단계를 한 칸 내리고, 최대 두 칸까지만 내린다. 0.14.1에는 ?fps=1 주소 인자로 켜는 프레임 표시를 넣어 휴대폰에서도 바로 확인할 수 있게 했다. 0.14.0 커밋의 A/B 기록을 보면 데스크톱 GPU 시간은 이전과 같았고, CPU를 4배 느리게 만든 폰 조건에서 최악 구간이 픽셀 네 배에 약 20% 늘었다. 이후 0.14.1 측정에서 폰 에뮬레이션은 120fps, 프레임 간격 p95 9.8ms였다. 솔직히 이 측정은 데스크톱 브라우저의 모바일 에뮬레이션이고, 실제 저가형 폰에서 발열까지 확인한 것은 아니다.
작은 화면에서 계속 나온 자잘한 문제
큰 틀을 바꾼 뒤에는 작은 문제가 줄줄이 나왔다.
| 버전 | 증상 | 처리 |
|---|---|---|
| 0.14.2 | 결과·설정 화면이 세로에서 잘림 | 관성 스크롤 추가, 하단 버튼 고정 |
| 0.14.4 | 스킨 색상 격자가 좁은 세로 화면 밖으로 넘침 | 좁은 세로에서 3열로 변경 |
| 페이지 설명 | 링크 미리보기에 “mouse only”가 뜸 | 터치·휴대폰 지원 문구로 수정 |
| 0.16.1 | 휴대폰에도 “CLICK A CARD · 1/2/3” 표시 | 터치에서는 “TAP A CARD”, 9개 언어 추가 |
마지막 줄은 부끄러운 실수였다. 숫자 키 1·2·3은 휴대폰에 없다. 이 문구 하나를 바꾸면서 9개 언어 번역을 새로 넣었다. 같은 날 브라우저·기기 조합 36개에서 결과 화면 후 재시작까지 모두 통과하는 것을 확인했다.
9월 27일 0.21.0에서는 모바일 첫 조작 흐름을 다시 다듬었다. 워밍업 안내가 “Hold or drag to move”였는데, 실제 터치 조작은 누른 자리를 기준으로 끌어야 움직인다. 안내와 조작이 달랐던 것이다. 터치용 문구로 바꾸고, 첫 드래그에 성공하거나 20초가 지날 때까지 큰 안내와 손가락 시범 원을 띄운다. 워밍업 중에 화면 절반을 가리던 출석 카드는 휴대폰에서 숨겼고, 포털 iframe 밖으로 스크롤이 튀는 것은 overscroll-behavior: none으로 막았다. 기기별 이탈을 볼 수 있게 서버 통계에 mobile/desktop/unknown 세 값만 보내도록 했다. 원본 사용자 에이전트는 보내지도 저장하지도 않는다.
아직 풀지 못한 것
모든 게 끝난 건 아니다. 같은 점검에서 두 가지가 남았다.
첫째, 첫 다운로드다. 압축된 초기 파일이 약 7MB라서 1.6Mbps 회선이면 전송만 이론상 35초가 걸린다. 안내 문구를 아무리 다듬어도 느린 회선에서 5초 안에 조작하게 만들 수는 없다. 둘째, WebKit 에뮬레이션에서 경기 후반 프레임 간격이 수백 ms까지 튀는 현상이 관측됐다. 같은 머신의 Chromium은 약 10ms였다. 실제 아이폰에서도 그런지 아직 확인하지 못했다.
휴대폰 대응은 한 번에 끝나는 작업이 아니었다. 조이스틱, 세로 화면, 해상도, 문구까지 네 번의 큰 커밋과 여러 번의 작은 수정이 필요했다. 휴대폰으로 Salmonholic 게임 페이지에서 직접 세로로 한 판 해 볼 수 있다. 이 과정에서 잡은 다른 버그들은 커밋 기록 속 버그 사냥기에 모았다.


