DEV NOTE · FLIP

탭 한 번 = 중력 반전: FLIP의 원버튼 설계

FLIP은 화면 어디를 눌러도 같은 일이 일어나는 게임이다. 누르면 중력이 반대 벽을 향한다. 입력을 이렇게 하나로 줄이면 조작은 쉬워지지만 판단할 거리가 사라질 위험이 있다. 이 글은 그 위험을 어떤 상수와 규칙으로 메웠는지, 그리고 설계 문서에서 구현으로 넘어가며 바뀐 부분을 정리한다.

입력이 하나면 무엇으로 깊이를 만드나

원버튼 게임에서 버튼 하나가 할 수 있는 말은 "지금"뿐이다. 무엇을 할지는 고정되어 있고 플레이어가 고르는 것은 시점이다. 그러면 게임의 깊이는 그 시점이 얼마나 다양한 결과로 갈라지느냐에 달려 있다. 점프형 러너는 보통 점프 높이를 누르는 시간으로 바꿔서 이 문제를 푸는데, FLIP은 그 길을 택하지 않았다. 대신 "어느 쪽 벽에 있느냐"라는 두 가지 상태를 만들고, 탭은 그 상태를 뒤집는 스위치로만 쓰기로 했다.

상태가 두 개뿐이라 단순해 보이지만, 두 상태 사이를 오가는 동안 기체는 공중에 떠 있다. 이 공중 구간이 세 번째 상태가 된다. 가시는 한쪽 벽에만 있으니 공중에서는 안전하고, 터널 한가운데의 블록은 벽 어디에 있든 안전하지만 공중에서는 죽는다. 같은 탭이라도 앞에 무엇이 오는지에 따라 정답 시점이 완전히 달라진다. 설계 문서는 장애물 세 종류를 "있어야 하는 쪽"으로 정의하는데, 그 정의가 곧 이 세 상태를 활용하는 방식이다.

왜 좌우 중력이고, 왜 세로 터널인가

위아래 중력을 뒤집는 구성은 가로로 흐르는 러너에서 흔하다. FLIP은 화면을 세로로 세우고 중력을 좌우로 눕혔다. 이 구성은 휴대폰을 세로로 잡고 엄지 하나로 하기에 잘 맞는다. 세로 화면에서는 앞을 내다볼 수 있는 거리가 위쪽으로 길게 확보되고, 기체는 렌더러에서 화면 높이의 72% 지점에 고정되어 있어 앞으로 올 장애물이 화면의 대부분을 차지한다.

터널 폭은 물리 계산에서 100유닛으로 정했다. 모든 수치가 이 폭에 맞춰져 있어서 렌더러는 화면 폭의 64%(최대 440px)를 터널로 쓰고 비율만 곱하면 된다. 기체 반지름은 6, 가로 중력은 초당 560유닛²이다. 이 두 숫자가 다음 절의 핵심 상수를 결정한다.

FLIP 플레이 화면. 세로 네온 터널 양쪽 벽과 장애물, 기체가 보인다
세로 터널과 좌우 벽. 기체는 화면 아래쪽에 고정되고 터널이 내려온다.

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초는 중력과 터널 폭에서 나오고, 생성기와 플레이어의 감각이 모두 이 숫자에 맞춰져 있다. 브레이크는 탭의 반응성을 지키려고 별도 버튼으로 분리했다. 설계 문서의 로드맵에는 움직이는 피스톤 장애물이 다음 후보로 올라가 있는데, 그것이 들어오면 "언제 공중에 있어야 하는가"라는 네 번째 질문이 생긴다. 그 장애물도 탭 하나로 풀 수 있게 만드는 것이 다음 과제다.