DEV NOTE · FLIP

벽에선 빨라지고 공중에선 느려진다: 속도 규칙과 BRAKE 게이지

FLIP 설계 문서는 "벽에 붙어 있으면 가속, 공중에서는 감속"을 이 게임의 독자 규칙이라고 적는다. 여기에 상승 속도를 절반으로 줄이는 BRAKE 게이지가 더해진다. 이 글은 두 규칙을 실제 코드의 수치로 풀어 보고, 글을 쓰면서 확인한 점수식과의 어긋남도 함께 기록한다.

목표 속도는 거리로 정해진다

기체의 상승 속도는 매 순간 "목표 속도"를 향해 다가간다. 목표 속도는 지금까지 올라간 거리만으로 결정된다.

const vt=dist=>110+Math.min(190,dist/12); // units/s, dist in m

단위를 풀어 보면 터널 폭 100유닛이 10m이고, 출발 속도 110유닛/초는 초당 11m다. 거리 12m마다 1유닛/초씩 오르고, 2,280m에서 상한 300유닛/초에 닿는다. 화면 위쪽 HUD의 배율은 현재 속도를 110으로 나눈 값이라서 출발할 때 ×1.0, 최고일 때 ×2.7이 된다. 배율이 2를 넘으면 HUD 숫자가 강조 색으로 바뀐다.

난이도를 시간이 아닌 거리에 묶은 덕분에, 오래 버틴 플레이어와 빨리 올라간 플레이어가 같은 높이에서 같은 속도를 만난다. 장애물 생성기도 같은 함수로 간격을 계산하므로 "이 높이에서는 이 속도"라는 약속이 물리와 레벨 양쪽에서 지켜진다.

벽 위와 공중, 두 개의 시간 상수

설계 문서에는 "벽 위에서는 0.8초 안에 목표 속도로 수렴, 공중에서는 목표의 0.8배로 즉시 감속"이라고 되어 있다. 구현은 두 경우 모두 지수 감쇠로 처리하고 시간 상수만 다르게 줬다.

if(w.onWall) w.vy+=(target-w.vy)*(1-Math.exp(-dt/0.35));
else         w.vy+=(target*0.8-w.vy)*(1-Math.exp(-dt/0.15));

시간 상수 0.35초짜리 지수 감쇠는 약 0.8초가 지나면 차이의 90%를 메운다. 문서의 "0.8초 안에 수렴"을 그대로 옮긴 값이다. 공중 쪽 0.15초는 건너는 시간 0.56초 동안 차이의 97% 이상을 줄이므로, 체감상 문서의 "즉시"와 같다. 이렇게 지수 감쇠로 쓰면 프레임 간격(dt)이 들쭉날쭉해도 결과가 거의 같다는 장점이 있다. 고정 비율로 매 프레임 조금씩 더하는 방식은 60Hz와 120Hz 화면에서 수렴 속도가 달라진다.

결과적으로 한 번 건널 때마다 속도는 약 20% 떨어졌다가, 벽에 붙은 뒤 0.8초 정도에 걸쳐 원래대로 돌아온다. 연속으로 빠르게 건너면 회복할 틈이 없어서 속도가 목표의 80% 근처에 머문다. 설계 문서가 말하는 "건널수록 느려진다"는 이 동작을 가리킨다.

BRAKE 게이지: 2.5초를 쓰고 4초에 채운다

브레이크는 누르고 있는 동안 상승 속도를 절반으로 만든다. 게이지는 0에서 1 사이 값이고, 소모와 회복 속도가 다르다.

w.braking=!!brakeHeld&&w.brake>0;
if(w.braking) w.brake=Math.max(0,w.brake-dt/2.5);
else if(w.onWall) w.brake=Math.min(1,w.brake+dt/4);
const vyEff=w.braking?w.vy*0.5:w.vy;

가득 찬 게이지는 2.5초면 바닥나고, 비어 있는 게이지는 벽에 붙어서 4초를 보내야 다 찬다. 공중에서는 회복되지 않는다. 그래서 브레이크를 자주 쓰려면 자주 건너지 말아야 하고, 이 점이 앞의 속도 규칙과 같은 방향을 가리킨다. 벽에 오래 머무는 플레이가 빨라지는 대신 브레이크라는 보험을 쌓는 구조다.

구현에서 눈여겨볼 부분은 브레이크가 w.vy 자체를 줄이지 않는다는 점이다. 실제 이동에 쓰는 값만 절반으로 만들고, 내부 속도는 그대로 목표를 따라간다. 버튼에서 손을 떼는 순간 원래 속도로 돌아오므로 브레이크는 "속도를 깎는 행동"이 아니라 "잠깐 시간을 사는 행동"이 된다. 또 브레이크는 가로 방향 운동에는 손대지 않는다. 건너는 시간은 여전히 0.56초이고, 그동안 올라가는 거리만 절반이 된다. 장애물 두 개가 빠듯하게 붙어 있을 때 브레이크가 정확히 도움이 되는 이유다.

브레이크를 쓰는 동안 화면 전체에 옅은 푸른 막이 덮이고 추진 입자 색도 푸르게 바뀐다. 오른쪽 아래 버튼 안의 막대는 게이지 양을 그대로 보여 주고, 게이지가 비면 버튼이 꺼진 모양이 된다.

소리로 듣는 속도

배경음은 샘플 없이 Web Audio로 만드는 베이스 펄스이고, 템포가 현재 속도에 연동된다. 공식은 bpm = 90 + min(90, vy × 0.3)이다. 출발 속도 110에서는 123BPM, 최고 속도 300에서는 상한 180BPM이 된다. 건너는 동안 속도가 20% 떨어지면 템포도 함께 느려졌다가 벽에 붙으면 다시 빨라진다. 눈으로 HUD를 보지 않아도 지금 가속 중인지 감속 중인지 귀로 알 수 있게 하려는 장치다. 화면 쪽에서도 실제 이동 속도가 170을 넘으면 스피드 라인이 생기고, 속도가 높을수록 선이 많아진다.

점수식과 맞지 않는 부분

이 글을 쓰려고 코드를 다시 읽으면서 짚어 둘 점이 하나 있었다. FLIP의 점수는 floor(거리) + 별 × 25이고 시간 항이 없다. 판은 충돌하는 순간 끝나므로, 같은 거리에 도달했다면 빨리 갔든 천천히 갔든 점수가 같다. 그런데 게임 안 HOW TO PLAY 화면은 "속도 배율이 점수를 좌우해요"라고 안내한다. 현재 규칙에서는 정확한 문장이 아니다.

더 따져 보면 방향이 오히려 반대인 면도 있다. 생성기는 장애물 간격을 그 높이의 목표 속도 기준으로 계산한다. 플레이어가 공중 감속이나 브레이크로 목표보다 느리게 움직이면 같은 간격을 더 긴 시간에 지나게 되어 여유가 생긴다. 즉 감속은 벌칙이라기보다 안전 마진이다. 그래도 자주 건너는 플레이가 유리하지는 않은데, 건너는 순간마다 블록과 부딪힐 위험에 노출되고 요구 방향을 틀릴 기회도 늘기 때문이다. 지금 이 규칙이 실제로 하는 일은 점수 보상이 아니라 플레이의 리듬을 바꾸는 것이다. 벽에 오래 붙어 있으면 템포가 올라가고 긴장이 커지며, 건너면 잠깐 숨이 돌아온다.

정리할 방법은 두 가지가 있다. 안내 문구를 실제 규칙에 맞게 고치거나, 높은 배율로 달린 구간에 가산점을 주는 식으로 점수식에 속도를 넣는 것이다. 뒤쪽을 택하면 설계 문서가 말하는 "필요한 순간에만 건너는 것이 고득점의 핵심"이 문자 그대로 성립하게 된다. 다만 DAILY RUN 기록의 의미가 바뀌므로 버전을 올려 따로 공지해야 한다. 어느 쪽으로 갈지는 아직 정하지 않았고, 다음 버전의 검토 항목으로 남겨 둔다.

1.0.2에서 앞쪽을 택했다. 점수식은 그대로 두고 HOW TO PLAY 문장을 "속도는 거리가 늘수록 올라가요. 화면의 속도 배율(×)은 점수에 들어가지 않고, 점수는 거리와 별로만 정해져요"로 바꿨다. 첫 플레이 안내 둘째 단계의 "꼭 필요할 때만 건너는 게 고득점 비결" 역시 "공중에선 보라 블록에 부딪히니 꼭 필요할 때만 건너세요"로 고쳤고, 설계 문서의 규칙 3도 "필요한 순간에만 건너는 것이 오래 사는 핵심"으로 맞췄다. DAILY RUN 기록의 의미는 바뀌지 않는다.

정리

FLIP의 속도는 거리에서 나오는 목표 속도, 벽과 공중의 서로 다른 시간 상수(0.35초와 0.15초), 그리고 이동량만 절반으로 만드는 브레이크로 이루어진다. 게이지는 2.5초 소모와 벽 위에서만의 4초 회복으로 "벽에 머물기"와 묶여 있다. 수치는 설계 문서의 의도를 그대로 옮겼지만, 점수식에 시간이 없으므로 "빠를수록 고득점"이라는 안내는 사실이 아니었고, 1.0.2에서 문구를 점수식에 맞췄다. 속도 배율은 지금 얼마나 빠듯한지를 알려 주는 표시로 남는다.