Babylon.js로 만든 레이싱 게임 구현기 [2] - 계획 및 에이전트 설계
구체적인 계획 설정
그러면 장르는 정했는데 이제 무엇을, 어떻게 구현할 것인지를 정해야했다. 먼저, 레이싱 게임에 필요한 요소가 뭐가 있는지 적어봤다.
- 자동차
- 트랙
- 속도계
- 랩타임
- 기록 저장
코딩 에이전트로는 Claude Code를 사용했다. 그리고 Claude에서 새롭게 나온 기능인 Ultraplan을 이용하여 프로젝트를 계획했다.
1. 모델(차량/트랙)
자동차나 트랙 모델을 어떻게 할 것인지 생각했다. Blender 모델을 불러와서 게임에 쓸 생각이지만, 처음부터 구현하기보단 Babylon.js로 기본적인 모델을 만들어서 조작에 쓰기로 했다. 따라서, 맵과 자동차는 메시를 부르는 함수를 구현하여 추후 외부 모델을 불러올 시 해당 함수만 수정해서 사용할 수 있도록 설계했다.
2. 물리 엔진
물리엔진은 Babylon.js에서 제공하는 Havok 엔진을 사용하기로 했다. Havok은 하프라이프 2, 스카이림 등의 게임에 사용된만큼 충분히 검증된 물리엔진인만큼, 외부 물리엔진 라이브러리를 부르기보단 Babylon.js에서 제공되는 것을 그대로 쓰는게 좋다고 생각했다.
3. 지원 환경
우선은 PC 환경부터 우선 지원하기로 했다. 모바일 버전은 기초적인 설계가 끝나고 추후 업데이트를 통해 추가하기로 했다.
4. 최고기록 저장
최고 기록은 브라우저의 localStorage에 저장하기로 했다. 현재는 싱글 플레이로 만들 생각이기에, 데이터베이스 연동 같은 것은 배제하기로 했다.
5. 오디오 구현
게임 사운드는 플레이에 있어서 몰입감을 높여주는 중요한 요소다. 하지만 사운드가 없다고 게임 플레이를 못하는건 아니기에, 기본적인 구현이 완료된 1단계 이후에 추가하기로 계획했다.
계획을 짜면서 놓칠 수 있는 부분을 Ultraplan을 통해서 많이 보완했다. Havok WASM의 경우 SharedArrayBuffer를 사용하기 때문에
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
이 두가지 옵션이 반드시 필요하다거나, WebGPU를 베이스로 하되, WebGPU를 지원하지 않는 브라우저를 위한 WebGL 엔진 하위호환 처리 등 게임 플레이에 오류가 발생할 수 있는 부분을 사전에 방지할 수 있었다.
에이전트 설계(Agent Design)
rules
프로젝트를 어떻게 구현할지 계획했으니, 그 다음으로 Claude Code에게 어떤 지침을 지켜서 작업해야할지 지시사항을 명확히 설정해야했다. 우선 작업 중 Claude Code가 시키지 않은 작업을 진행하지 않도록 rules에 준수사항과 금지사항을 명시했다.
# Racing 기능 Rules
## R1 — 3D 모델 교체 가능 구조
모든 3D 메시 생성은 `buildMesh()` 메서드 내부로 격리한다.
추후 Babylon.js `SceneLoader.ImportMesh`로 Blender .glb 교체 시 해당 메서드만 수정하면 된다.
## R2 — 물리 엔진 고정
물리 엔진은 **Havok**만 사용한다. Cannon.js, Ammo.js 사용 금지.
`PhysicsAggregate` API를 사용하여 충돌체를 정의한다.
...
## R8 — 오디오 미구현 (1단계)
1단계(Mini Drift Island)에서는 오디오를 구현하지 않는다.
오디오 관련 코드(Babylon.js Sound 등)는 2단계 이후에 추가한다.
## R9 — 맵 추가 시 Coming Soon 처리
아직 구현되지 않은 맵은 `MapSelectScene`의 `MAPS` 배열에서 `available: false`로 표시한다.
비활성 카드는 클릭 불가, "COMING SOON" 문구 표시, 흐린 색상 처리.
skills
다음으로 어떤 방식으로 코드를 구현해야할지, 기본적인 템플릿을 skills를 통해 제공했다. 이를 통해 Claude Code가 코드 패턴을 일관성있게 작성할 수 있도록 했다.
# Racing 기능 Skills
## S1 — 씬 전환
// main.ts 패턴 — 씬 간 의존성을 콜백으로 주입
function goToMapSelect(): void {
const scene = new MapSelectScene(app.getEngine(), {
onMapSelected: (mapId) => goToGame(mapId),
onBack: goToMainMenu,
});
app.switchScene(scene); // 이전 씬 dispose → 새 씬 load()
}
...
## S9 — 프로젝트 개발 서버 실행
```bash
# apps/racing 디렉토리에서
pnpm dev
# 또는 모노레포 루트에서
pnpm --filter racing dev
에이전트 설계까지 끝냈으니 이제 남은건 실제 코드를 구현하는 것이었다.
