Phaser 4 Pseudo 3D 레이싱 게임 - 가드레일, 저속 조향, 0-100km/h를 다시 잡기
Apex Seoul의 밤 다운힐 비주얼을 왼쪽 절벽·가드레일, 오른쪽 옹벽·수목 구조로 재정리하고, 가드레일 충돌, 저속 조향 pose, standing start 파워밴드까지 주행 로그와 자동 QA로 다시 맞췄습니다.
Cover image: OpenAI image generation — article illustration
지난 글에서는 Apex Seoul의 양옆을 비대칭으로 나눴다.
왼쪽 : 가드레일 / 절벽 / 먼 도시
오른쪽: 옹벽 / 가까운 수목
방향 자체는 맞았다. 하지만 실제로 달려 보니 장면은 아직 한 장의 밤 산길로 읽히지 않았다.
이번 작업은 새 배경을 하나 더 추가한 일이 아니다. 무엇을 어느 거리에서 보이게 하고, 무엇을 어디에 그려야 하는지를 다시 맞춘 작업이다.

이 글의 삽화는 실제 플레이 스크린샷이 아니라, 구현 목표와 장면 구성을 설명하기 위해 제작한 이미지다.
처음에는 오른쪽 나무 resource가 단순해서 어색하다고 생각했다. 물론 맞는 말이었다. 하지만 나무를 더 그려도 해결되지 않는 문제가 먼저 있었다.
옹벽 위의 숲은 road projection 사이의 빈 공간을 덮는 horizon occlusion보다 뒤에 그려지고 있었다. 그래서 나무가 수평선에서 잘린 것처럼 보였다.
처음 반응은 보통 이쪽이다.
나무를 더 밝게 한다.
검은 band를 지운다.
나무의 크기를 키운다.
전부 결과를 건드리는 방법이다. 검은 band는 crest와 내리막에서 road와 원경 사이의 빈 공간을 숨기는 데 필요했다. 없애면 숲은 보일 수 있지만, 도로의 지형은 무너진다.
그래서 먼저 draw order를 분리했다.
background / city / ridge
→ terrain horizon occlusion
→ left cliff canopy / right wall forest
→ road / wall / guardrail
→ headlight
→ player
→ HUD
RenderDepth enum으로 이 순서를 이름 붙여 두고, horizon occlusion을 road 본체와 별도 Graphics pass로 옮겼다. 이 변경 뒤에야 “숲이 수평선에 잘린다”는 문제가 나무 asset이 아니라 layer 충돌이었다는 것이 명확해졌다.
다음 문제는 실제로 나무였다.
기존의 wall-forest PNG는 한 장의 덩어리를 반복하는 방식이었다. 오른쪽 옹벽 위에서 수목의 밀도, 앞뒤 교차, 크기 변화를 제어하기에는 너무 거칠었다. 멀리서는 한 줄의 패턴으로 보이고, 가까이서는 숲이 아니라 이미지 타일처럼 보였다.
그래서 기존 resource를 제거하고 투명 배경 SVG 다섯 종을 새로 만들었다.
tree-01-tall-pine.svg
tree-02-wide-pine.svg
tree-03-cypress.svg
tree-04-leaning-pine.svg
tree-05-broadleaf.svg
중요한 점은 “나무 다섯 장을 만들었다”가 아니다. 한 그루가 아래에서 위까지 완성되는 자산을 만들고, 같은 자산을 어디서 얼마나 보여 줄지를 양쪽에서 다르게 썼다.

이 이미지는 실제 asset의 스크린샷이 아니라, 아래 배치 규칙을 설명하는 제작 이미지다.
처음에는 옹벽 가장자리에 나무를 몇 개 올렸다. 정지 화면에서는 그럴듯해도 주행하면 간격이 드러났다. 오른쪽은 숲의 시작점이 아니라, 가까운 벽과 수목이 화면 밖까지 계속 이어지는 닫힌 공간이어야 했다.
그래서 segment마다 back/front 두 군집을 만들고, SVG 종류·크기·위치를 교차시켰다.
right wall top
→ back forest cluster
→ front forest cluster
→ next segment의 cluster와 겹침
→ screen-right edge까지 연속
이 구조는 숲의 디테일을 늘리는 것보다, 숲의 끊기지 않는 경계선을 먼저 보장한다.
왼쪽 가드레일 너머에는 줄기까지 보이는 나무가 필요하지 않았다. 오히려 줄기와 접지부가 화면에 들어오면 절벽의 높이와 가드레일 위치가 어색해졌다.
그래서 같은 SVG를 쓰되 상단 수관만 crop했다. 줄기는 절벽 아래에 숨기고, 수관만 guardrail 너머에서 보이게 했다. 이 군집을 왼쪽 화면 끝까지 이어 주면, 도로 옆에 나무를 세운 것이 아니라 절벽 아래 숲이 보이는 느낌이 난다.
right: full tree silhouette above retaining wall
left : cropped canopy below guardrail
양쪽은 자산이 아니라 접지선이 다르다. 이 차이가 “벽 위의 숲”과 “절벽 아래의 숲”을 구분한다.
SVG 종류와 크기를 랜덤으로 고르면 처음에는 반복이 줄어든 것처럼 보인다. 하지만 달리는 게임에서는 더 큰 문제가 생긴다. 멀리 있던 나무가 매 프레임 다른 형태로 바뀌거나, 새 군집이 갑자기 나타나는 느낌이 남는다.
해결은 random을 없애는 것이었다. 정확히는 매번 다른 random 대신 segment index 기반의 결정적 seed를 사용했다.
same segment index
→ same SVG variant
→ same scale / x offset
→ same back/front layer
여기에 crest visibility와 distance fade를 붙였다. 먼 수관은 “생성”되는 것이 아니라, 지형 뒤와 어둠 속에서 서서히 열린다. 이 규칙은 오른쪽 옹벽 숲과 왼쪽 절벽 수관에 같이 적용했다.
수목을 정리하고 나니 또 다른 문제가 보였다. roadside는 멀리서 사라지는데 road body와 차선은 계속 밝았다. 결과적으로 숲만 안개에 잠기고 도로는 멀리까지 떠 있는 것처럼 보였다.
처음에는 alpha를 낮추는 식의 fade를 생각했다. 하지만 alpha는 뒤의 배경을 드러낼 뿐, 같은 장면 안의 다른 요소를 같은 색으로 수렴시키지 않는다.
그래서 fog를 투명도 효과가 아니라 color compositing 규칙으로 바꿨다.
near : asphalt, rail, foliage의 형태를 읽을 수 있다.
mid : asphalt contrast와 edge line이 함께 약해진다.
far : road, wall, rail, foliage가 같은 blue-black에 가까워진다.
road, shoulder, lane mark, retaining wall, guardrail, forest가 같은 거리 기준을 공유해야 했다. 특히 차선과 edge line은 asphalt보다 먼저 contrast를 잃게 했다. 그렇지 않으면 멀리 있는 차선이 야간 도로가 아니라 화면 위에 그은 밝은 선처럼 보인다.
이 작업 뒤의 기준은 단순하다.
멀리 있는 요소는 투명해지는 것이 아니라, 같은 밤색 속으로 들어가야 한다.
원근 안개를 넣으면 가까운 road에 작은 밝기 기준점이 필요해진다. 이 단계의 시행착오는 가장 많았다.
처음에는 road polygon 위에 사다리꼴 cone을 다시 그렸다. 하지만 차량 sprite는 화면 하단의 road contact anchor에 고정돼 있고, cone은 camera near-plane을 기준으로 시작했다.
그래서 가장 밝은 부분이 차량 앞이 아니라 차량 아래나 뒤에 나타났다.
이건 cone의 색이나 alpha 문제가 아니었다. 서로 다른 좌표 기준을 같은 광원으로 착각한 문제였다. 이 pass는 롤백했다.
다음에는 road만 밝히기 위해 RenderTexture mask를 시도했다. 방향은 좋았다. 급커브에서도 guardrail과 숲에 빛이 번지지 않게 만들 수 있기 때문이다.
하지만 Phaser의 offscreen Graphics capture와 shader output을 연결하는 단계에서 mask가 결과를 전부 막았다. Shader 객체에 일반 GameObject mask API를 붙이는 시도도 맞지 않았다.
이때 중요한 선택은 mask 문제를 더 오래 붙잡지 않는 것이었다. 우선 차량과 확실히 연동되고, 화면에서 보이는 구조를 만든다. road mask는 별도 POC가 성공할 때만 다시 붙인다.
현재는 full-screen additive shader 한 장에서 좌·우 헤드라이트 pool을 따로 계산한다.
visible car front anchor
→ left flattened oval pool
+ right flattened oval pool
→ overlap = brighter
uCarFront는 atlas의 투명 여백을 보정한 visible car front다.처음에는 동심원을 넣어 광원 두 개를 읽히게 하려 했다. 하지만 결과는 헤드라이트 반사보다 HUD target에 가까웠다. 반대로 타원을 너무 넓히면 두 개가 하나의 푸른 구름으로 합쳐졌다.
현재도 실제 주행 화면에서 크기·간격·감쇠를 계속 조정 중이다. 여기서의 결론은 “완벽한 물리 조명”이 아니라, rear-view pseudo-3D 화면에서 두 개의 광원이 도로 위에 존재한다는 최소 단서를 남기는 것이다.
시각적인 잡음이 줄어들자 Raven Coupe의 변속 리듬이 더 선명하게 어색해졌다. 이전 자동 변속은 사실상 speed threshold를 넘으면 다음 단으로 바뀌었다. 프로필에 있던 RPM 값은 실제 upshift 조건에 충분히 쓰이지 않았다.
그래서 2.0L 자연흡기 FR coupe의 감각을 목표로 기어 구간을 다시 배치했다. 실차 기어비를 복제한 것은 아니다. 고회전에서 변속하고, 다음 단에서도 다시 토크 구간에 들어오는 리듬을 참고한 것이다.
| 단수 | 주된 사용 구간 | 변속 지점 |
|---|---|---|
| 1단 | 0–36km/h | 약 36km/h |
| 2단 | 36–67km/h | 약 67km/h |
| 3단 | 67–97km/h | 약 97km/h |
| 4단 | 97–127km/h | 약 127km/h |
| 5단 | 127–157km/h | 약 157km/h |
| 6단 | 157–185km/h | 고속 순항 |
함께 바꾼 점은 다음과 같다.
7,400rpm 근처에서 일어난다.5,400rpm 이상을 목표로 둔다.5,500rpm 이후 올라가 6,600~7,000rpm 부근에서 가장 즐겁게 느껴지게 한다.qa:standing-start 결과는 Raven 기준 0–60km/h 9.917초, 0–100km/h 13.133초였다. 숫자 자체보다 중요한 것은 30km/h에서 아직 1단을 쓰고, 60km/h가 2단 후반을 거쳐, 100km/h가 4단 초반으로 이어진다는 점이다.
이번 작업은 숲, 안개, 헤드라이트, 파워밴드라는 서로 다른 항목처럼 보인다. 하지만 모두 같은 질문에서 시작했다.
이 요소는 언제 보이기 시작해야 하는가?
무엇과 같은 좌표 기준을 공유해야 하는가?
전환 뒤에도 이전 상태와 자연스럽게 이어지는가?
아직 남은 일도 있다.
정지 화면을 예쁘게 만드는 일과 달리는 화면을 믿게 만드는 일은 다르다. 이번 반복은 그 차이를 확인하고, 각 요소가 갑자기 나타나거나 엉뚱한 기준에서 빛나지 않게 만드는 과정이었다.
Apex Seoul의 밤 다운힐 비주얼을 왼쪽 절벽·가드레일, 오른쪽 옹벽·수목 구조로 재정리하고, 가드레일 충돌, 저속 조향 pose, standing start 파워밴드까지 주행 로그와 자동 QA로 다시 맞췄습니다.
Apex Seoul에서 200km/h grip 주행이 너무 쉽게 느껴진 이유를 주행 로그로 분해하고, 코스 곡률을 바꾸지 않은 채 하향 코너 속도 예산·understeer·안전 여유 비용·자동 검증으로 다운힐의 선택을 만들었습니다.
Apex Seoul에 스페이스 브레이크 기반 드리프트와 카운터 스티어를 넣고, 입력을 놓았을 때의 복귀·전환을 별도 상태로 분리했습니다. 주행 로그와 자동 시나리오로 감각을 수치화하며, 스핀은 아직 구현하지 않기로 한 이유도 정리합니다.
Apex Seoul을 한 번 내려오는 다운힐 런으로 정리하고, 고속 조향과 코너 감속을 실제 주행 로그로 다시 튜닝한 뒤, FT86 기반 Raven Coupe에 NA 고회전 RPM, 기어, 토크 곡선, fuel cut 상태를 붙였습니다.
Apex Seoul의 주행 감각을 OutRun식 grip 기준으로 다시 보고, 고정 화면 비율, WebGL 전환, 속도 표시 보정, shader speed cue, 자동 handling simulation으로 핸들링 후보를 반복 비교했습니다.
Apex Seoul의 approved 128px 차량 스프라이트를 런타임에 연결하고, 조향 3way, 고저차 접지감, 주행 telemetry, silhouette shadow pass로 실제 게임 화면에서 검증했습니다.