Phaser 4 Pseudo 3D 레이싱 게임 - 다운힐 진행 구조와 RPM 계기판 만들기
Apex Seoul을 한 번 내려오는 다운힐 런으로 정리하고, 고속 조향과 코너 감속을 실제 주행 로그로 다시 튜닝한 뒤, FT86 기반 Raven Coupe에 NA 고회전 RPM, 기어, 토크 곡선, fuel cut 상태를 붙였습니다.
지난 글에서는 Apex Seoul을 한 번 내려오는 다운힐 런으로 만들고, 고속 코너에서 속도를 어떻게 잃게 할지와 RPM 계기판을 다뤘다.
그 다음에 남은 문제는 더 미묘했다.
브레이크를 밟으면 차가 미끄러진다.
그런데 그 미끄러짐을 플레이어가 "내가 잡고 있다"고 느끼는가?
이번 작업의 목표는 현실 물리 시뮬레이터가 아니었다. 화면 하단의 차를 읽을 수 있게 유지하면서도, 진입 조향으로 드리프트를 만들고 카운터와 엑셀로 각을 조절하는 다운힐 아케이드 감각을 만드는 것이었다.

위 이미지는 글의 분위기를 설명하기 위해 새로 만든 오리지널 비주얼이다. 실제 게임 스크린샷이나 외부 자동차 사진이 아니며, 실차 로고·차종 표식·인물·표지 텍스트를 넣지 않았다. 실제 구현 화면과 혼동하지 않도록 아래부터는 게임 캡처와 측정 이미지를 따로 사용한다.
현재 데모는 아래에서 볼 수 있다.
처음에는 브레이크 + 조향 = drift 정도로 시작했다. 그러면 진입 자체는 쉽게 보인다. 문제는 그 이후다.
그래서 차량 상태를 단순한 grip/drift 이분법 대신 아래처럼 나눴다.
grip
-> setup : 브레이크 또는 lift로 드리프트를 준비
-> drift : 횡속도를 유지하며 카운터로 각을 조절
-> recovery : 카운터를 유지한 채 엑셀을 놓으면 복귀
-> transition : 중립 lift -> 새 카운터 -> 재가속일 때만 반대 방향 전환
-> grip
핵심은 복귀와 전환은 같은 입력이 아니라는 점이다.
카운터를 넣고 있는 상태에서 엑셀을 놓는 것은 보통 “이 드리프트를 정리하겠다”는 입력이다. 반대로 반대 방향 드리프트로 넘기려면, 잠깐 중립으로 하중을 정리한 뒤 새 방향의 카운터와 재가속이 이어져야 한다.
이 구분을 하지 않으면 플레이어 입장에서는 차가 내 입력을 해석하는 대신, 멋대로 반대편으로 튀는 것처럼 느껴진다.

브레이크 입력은 스페이스바로 정리했다. 접근성이 좋은 기본 키이고, 좌우 조향을 유지한 채 진입을 만들기 쉽다.
다만 브레이크가 곧바로 큰 횡이동으로 바뀌면 차가 순간이동하는 것처럼 느껴진다. 이번에는 진입을 두 가지로 분리했다.
brake entry : 스페이스 + 조향. 가장 명확하고 강한 진입
lift entry : 엑셀을 잠깐 놓고 조향. 작은 속도 손실과 재가속 지연을 동반
여기서 lift 진입에는 작은 추가 속도 손실을 줬다. 하지만 코너 전체에 무거운 감속을 덧씌우지는 않았다. 코너를 잘 읽고 라인을 지킨 플레이까지 일괄적으로 벌 주면, 다운힐의 흐름 자체가 죽기 때문이다.
기본 grip 코너에서는 다른 해결을 택했다. 속도가 높을수록 최대 조향각을 줄여서, 고속에서 같은 입력이 너무 크게 꺾이지 않게 했다. 즉 “고속 코너는 무조건 느려진다”가 아니라, 먼저 차를 너무 많이 꺾을 수 없게 만들고 필요한 감속은 진입과 조향량에서 나오게 했다.

이번 튜닝에서 가장 많이 바뀐 부분이다.
처음에는 카운터 입력이 들어오면 횡속도를 빠르게 줄이는 방향으로 구현했다. 안전하고 이해하기 쉬운 출발점이지만, 실제 플레이에서는 “카운터를 누르면 차가 바닥에 붙는다”는 감각이 강했다.
원하는 감각은 조금 다르다.
진입 조향: 드리프트 각을 만든다.
카운터: 그 각을 줄이거나 유지한다.
카운터를 놓음: 원래 진행 방향의 드리프트가 다시 살아난다.
복귀: 카운터를 유지한 상태에서 엑셀을 놓아 명시적으로 끝낸다.
그래서 카운터는 횡속도를 즉시 0으로 만들지 않도록 바꿨다. 짧은 탭은 각을 다듬고, 오래 유지하면 점진적으로 복귀 쪽으로 이끈다. 반대 방향으로 전환하려면 위에서 말한 별도 입력 순서를 만족해야 한다.
시각 표현도 물리 상태와 완전히 같게 두지 않았다. 스프라이트가 브레이크 순간에 과하게 기울어지면 조작보다 애니메이션이 먼저 보인다. 기본 grip에서는 작은 조향 pose만 쓰고, drift에서만 진행 방향과 차체 방향의 차이를 더 크게 보여 준다. 물리와 비주얼을 같은 수치 하나로 밀어붙이지 않은 이유다.

플레이 로그는 매우 중요하지만, 재현이 어렵다. 특히 “긴 카운터가 너무 잘 먹히는가” 같은 문제는 같은 코너와 같은 타이밍을 다시 만들기 어렵다.
그래서 playerVehicleController를 Phaser scene에서 분리해 둔 구조를 이용해, 브라우저 없이 반복하는 핸들링 시뮬레이션을 늘렸다.
npm run qa:handling-sim --workspace @games/apex-seoul
이번에 고정한 핵심 시나리오는 다음과 같다.
| 시나리오 | 확인하는 것 |
|---|---|
counter-tap-release | 짧은 카운터 뒤 원래 drift가 다시 살아나는가 |
counter-hold / counter-long-hold | 긴 카운터가 횡속도를 비정상적으로 붙들지 않는가 |
counter-lift-exit | 카운터 유지 + 엑셀 오프가 반대 드리프트가 아니라 복귀가 되는가 |
explicit-recovery | 명시적 복귀 시간이 과도하게 길지 않은가 |
counter-transition | 중립 lift, 새 카운터, 재가속 순서일 때만 전환되는가 |
high-speed-grip-angle-cap | 고속 grip 입력이 과도한 조향각으로 보이지 않는가 |
최근 기준에서 counter-lift-exit은 약 200ms 안에 recovery로 들어가고 방향 전환은 0회여야 한다. counter-transition은 armed 상태를 거친 뒤에만 한 번 전환되며, 기준 시나리오에서는 commit이 0ms 지연으로 기록된다. 이 수치는 “재미 100점”을 자동으로 판정하려는 값이 아니다. 명백하게 잘못된 후보를 빠르게 버리는 안전장치다.

최근 주행 로그에는 우측 드리프트가 세 번 기록됐다.
drift duration: 2.00s / 0.88s / 14.03s
drift samples: 156
counter applied: 123 samples, about 79%
accel-off while drifting: 0 samples
road-edge clamp hit: 0 samples
이 로그로는 “카운터와 엑셀을 전혀 넣지 않으면 어떻게 되는가”를 검증할 수 없다. 실제로는 대부분의 드리프트 구간에서 카운터와 엑셀을 유지하고 있었기 때문이다.
이런 식으로 로그가 알려 주는 범위와, 아직 알 수 없는 범위를 나눴다. 감각에 대한 의견을 숫자로 덮어버리는 대신, 다음에 어떤 자동 시나리오가 필요한지를 정하는 데 로그를 썼다.
드리프트 게임에서 카운터도 엑셀 조절도 하지 않으면 결국 스핀하거나 코스를 잃는 것은 자연스럽다. 다만 지금 Apex Seoul에서 완전한 스핀은 구현 우선순위를 낮게 두었다.
현재 차량은 횡 이동값을 도로 경계 안에서 제한한다. 따라서 제어 실패가 180도 회전으로 이어지기보다, 우선 옆으로 밀리다가 경계에서 멈추는 구조다. 이 상태에서 스핀 애니메이션부터 넣으면 다음 원인이 섞인다.
그래서 스핀은 백로그로 남겼다. 먼저 카운터 미입력, 카운터 탭 후 해제, 엑셀 오프를 2~3초 유지하는 자동 시나리오를 만들 예정이다. 여기서 횡속도, 도로 이탈까지 걸린 시간, 속도 손실을 측정한다.
그 결과가 충분히 안정되면 다음 순서로 확장한다.
제어 불능 slip
-> 도로 이탈 또는 장애물 접촉
-> 짧은 스핀/감속 연출
-> 안전한 위치에서 복귀
즉 스핀은 “카운터를 놓으면 즉시 벌”이 아니라, 제어를 잃은 결과를 읽기 쉽게 전달하는 후속 연출로 만들 생각이다.
이번 작업에서 가장 큰 변화는 drift 값을 하나 더 올리거나 내린 것이 아니다.
카운터 = 반대 방향으로 돌리는 입력
가 아니라
카운터 = 이미 만들어진 slip을 제어하는 입력
으로 역할을 다시 정의한 일이다.
이제는 브레이크 진입, lift 진입, 카운터 탭, 긴 카운터, 복귀, 전환을 각각 측정할 수 있다. 다음 단계는 스핀을 서두르는 것이 아니라, 제어하지 않았을 때 차가 어떻게 점진적으로 무너지게 할지를 자동 시나리오로 확인하는 일이다.
속도계와 RPM이 차가 빠르다고 말해 준다면, 드리프트와 카운터는 그 속도를 내가 감당하고 있다고 느끼게 해야 한다.
Apex Seoul을 한 번 내려오는 다운힐 런으로 정리하고, 고속 조향과 코너 감속을 실제 주행 로그로 다시 튜닝한 뒤, FT86 기반 Raven Coupe에 NA 고회전 RPM, 기어, 토크 곡선, fuel cut 상태를 붙였습니다.
Apex Seoul의 주행 감각을 OutRun식 grip 기준으로 다시 보고, 고정 화면 비율, WebGL 전환, 속도 표시 보정, shader speed cue, 자동 handling simulation으로 핸들링 후보를 반복 비교했습니다.
Apex Seoul에 Bugak Ridge Downhill 단일 맵을 만들고, RoadSegment elevation, 고저차 projection, 차량 하단 고정, 레트로 차량 그림자를 구현했습니다.
Apex Seoul에 Kenney Car Kit 기반 임시 차량 스프라이트를 올리고, 좌우 조향, 악셀, 브레이크, 기본 주행 속도 복귀를 구현했습니다.
Apex Seoul의 임시 직선 도로를 RoadSegment 기반 렌더러로 분리하고, curve 값을 누적해 pseudo 3D 커브 도로를 구현했습니다.
Phaser 4와 TypeScript로 Apex Seoul 프로젝트를 세팅하고, horizon, FOV, camera height를 이용해 pseudo 3D 도로 카메라를 구현했습니다.