탭 한 번 = 중력 반전: FLIP의 원버튼 설계
FLIP은 화면 어디를 눌러도 같은 일이 일어나는 게임이다. 누르면 중력이 반대 벽을 향한다. 입력을 이렇게 하나로 줄이면 조작은 쉬워지지만 판단할 거리가 사라질 위험이 있다. 이 글은 그 위험을 어떤 상수와 규칙으로 메웠는지, 그리고 설계 문서에서 구현으로 넘어가며 바뀐 부분을 정리한다.
입력이 하나면 무엇으로 깊이를 만드나
원버튼 게임에서 버튼 하나가 할 수 있는 말은 "지금"뿐이다. 무엇을 할지는 고정되어 있고 플레이어가 고르는 것은 시점이다. 그러면 게임의 깊이는 그 시점이 얼마나 다양한 결과로 갈라지느냐에 달려 있다. 점프형 러너는 보통 점프 높이를 누르는 시간으로 바꿔서 이 문제를 푸는데, FLIP은 그 길을 택하지 않았다. 대신 "어느 쪽 벽에 있느냐"라는 두 가지 상태를 만들고, 탭은 그 상태를 뒤집는 스위치로만 쓰기로 했다.
상태가 두 개뿐이라 단순해 보이지만, 두 상태 사이를 오가는 동안 기체는 공중에 떠 있다. 이 공중 구간이 세 번째 상태가 된다. 가시는 한쪽 벽에만 있으니 공중에서는 안전하고, 터널 한가운데의 블록은 벽 어디에 있든 안전하지만 공중에서는 죽는다. 같은 탭이라도 앞에 무엇이 오는지에 따라 정답 시점이 완전히 달라진다. 설계 문서는 장애물 세 종류를 "있어야 하는 쪽"으로 정의하는데, 그 정의가 곧 이 세 상태를 활용하는 방식이다.
왜 좌우 중력이고, 왜 세로 터널인가
위아래 중력을 뒤집는 구성은 가로로 흐르는 러너에서 흔하다. FLIP은 화면을 세로로 세우고 중력을 좌우로 눕혔다. 이 구성은 휴대폰을 세로로 잡고 엄지 하나로 하기에 잘 맞는다. 세로 화면에서는 앞을 내다볼 수 있는 거리가 위쪽으로 길게 확보되고, 기체는 렌더러에서 화면 높이의 72% 지점에 고정되어 있어 앞으로 올 장애물이 화면의 대부분을 차지한다.
터널 폭은 물리 계산에서 100유닛으로 정했다. 모든 수치가 이 폭에 맞춰져 있어서 렌더러는 화면 폭의 64%(최대 440px)를 터널로 쓰고 비율만 곱하면 된다. 기체 반지름은 6, 가로 중력은 초당 560유닛²이다. 이 두 숫자가 다음 절의 핵심 상수를 결정한다.

0.56초라는 상수
기체는 벽에 붙어 있다가 탭하면 정지 상태에서 반대 벽까지 가속하며 떨어진다. 이동 거리는 터널 폭에서 양쪽 반지름을 뺀 88유닛이다. 등가속도 공식 그대로 건너는 시간을 구하면 다음과 같다.
const W=100, R=6, G=560;
const CROSS_T=Math.sqrt(2*(W-2*R)/G); // ~0.56s
이 값은 게임 전체에서 가장 자주 쓰이는 숫자다. 생성기는 장애물 간격을 "그 높이에서의 상승 속도 × CROSS_T"의 배수로 계산하고, 플레이어가 반대쪽으로 넘어갈 수 있는지를 판단할 때도 이 값을 쓴다. 이 길이가 주는 효과를 따져 보면 0.5초 근처가 왜 적당한지 보인다. 이보다 짧으면 탭과 착지가 거의 동시에 일어나 "떨어진다"는 느낌이 사라지고, 길면 공중 구간이 늘어 블록에 걸릴 확률이 올라가면서 게임이 답답해진다. 중력 상수를 바꾸면 이 시간이 바뀌고, 그러면 생성기의 간격도 자동으로 따라 바뀐다. 상수 하나에 물리와 레벨 디자인이 함께 묶여 있는 구조라서 튜닝할 때 한 군데만 고치면 된다.
건너는 시간은 속도와 관계없이 항상 같다는 점도 의도한 부분이다. 상승 속도가 두 배가 되면 건너는 동안 올라가는 거리도 두 배가 되지만 손이 느끼는 리듬은 그대로다. 플레이어는 "얼마나 일찍 눌러야 하는가"만 다시 익히면 된다.
브레이크를 탭에서 떼어 낸 이유
설계 문서에는 브레이크를 "길게 누르기(150ms 이상)"로 적어 두었다. 같은 화면 입력에서 짧게 누르면 반전, 길게 누르면 감속이 되는 방식이다. 실제 구현에서는 이 방식을 쓰지 않고 오른쪽 아래에 별도의 BRAKE 버튼을 두었다. 키보드에서는 Shift, ↓, B가 브레이크이고 게임패드에서는 L2와 R2가 브레이크다.
길게 누르기를 쓰면 탭인지 홀드인지 구분하기 위해 입력을 판정하는 순간을 늦추거나, 누르자마자 반전한 뒤 홀드가 확인되면 브레이크를 덧붙여야 한다. 앞의 방법은 반전이 최대 150ms 늦어지는데, 건너는 시간이 0.56초인 게임에서 그 지연은 전체의 4분의 1이 넘는다. 뒤의 방법은 브레이크를 걸려고 할 때마다 원치 않는 반전이 함께 일어난다. 어느 쪽이든 핵심 입력인 탭의 정확도를 깎는다. 그래서 탭은 pointerdown 순간 바로 처리하고, 브레이크는 다른 손가락이나 다른 키가 맡도록 나눴다. 엄밀히 말해 원버튼에서 한 발 물러선 셈이지만, 브레이크는 쓰지 않아도 게임이 굴러가는 보조 수단이라 첫 판에서 신경 쓸 입력은 여전히 하나다.
1.0.1: 건너는 도중에 다시 탭하면
공개 당일 고친 버그가 하나 있다. 1.0.0에서는 중력이 향하는 쪽 벽만 충돌 처리했다. 왼쪽 벽에서 출발해 오른쪽으로 건너다가 도중에 다시 탭하면 중력은 왼쪽을 향하지만 기체에는 이미 오른쪽으로 향하는 속도가 붙어 있다. 그 관성으로 오른쪽 벽에 닿았을 때 그 벽은 "중력 쪽 벽"이 아니어서 검사 대상이 아니었고, 기체가 터널 밖으로 빠져나갔다. 수정한 코드는 중력 방향과 상관없이 양쪽 벽을 항상 막는다.
if(w.x<=R){ w.x=R; if(w.vx<0) w.vx=0;
if(w.side<0){ w.vx=0; if(!w.onWall){ w.onWall=true; /* land */ } } }
반대쪽 벽에 부딪혔을 때는 위치만 막고 바깥으로 향하는 속도만 없앤다. 중력은 여전히 원래 벽 쪽이므로 기체는 곧 되돌아 떨어지고, 착지 판정은 중력 방향과 벽이 일치할 때만 일어난다. 원버튼 게임에서 같은 버튼을 연타하는 것은 예외 상황이 아니라 기본 동작이라는 점을 이 버그로 다시 확인했다.
정리
FLIP의 원버튼은 "벽 두 개와 그 사이 공중"이라는 세 상태로 깊이를 만든다. 건너는 시간 0.56초는 중력과 터널 폭에서 나오고, 생성기와 플레이어의 감각이 모두 이 숫자에 맞춰져 있다. 브레이크는 탭의 반응성을 지키려고 별도 버튼으로 분리했다. 설계 문서의 로드맵에는 움직이는 피스톤 장애물이 다음 후보로 올라가 있는데, 그것이 들어오면 "언제 공중에 있어야 하는가"라는 네 번째 질문이 생긴다. 그 장애물도 탭 하나로 풀 수 있게 만드는 것이 다음 과제다.