VibeDaily
← 목록
쇼케이스트러블슈팅

기획서 한 장 넣고 PR 26개 — Claude Code로 3D 로그라이트 완주

2026.09.09👁 13🔗 원문 보기

게임 기획서 마크다운 한 장을 Claude Code에 넣고 이틀을 돌렸어요. 나온 건 브라우저에서 도는 3D 던전 로그라이트입니다. 방에 들어가면 적이 몰려오고, 다 잡으면 문이 열려요. 문 위에 뜬 아이콘을 보고 다음 방을 고르고, 3층 보스를 잡으면 끝납니다.

플레이 → bonemaze.kk-flow.com (모바일 세로 권장)

1층 전투 화면. 기사가 뼈다귀 적 세 마리에 둘러싸여 있고 상단에 체력바와 웨이브 표시가 있다

항목 수치
기간 2일 · 머지된 PR 26개
코드 TypeScript 101파일 · 12,169줄
테스트 160개 (전부 순수 로직)
콘텐츠 영웅 4 · 적 8 · 보스 3 · 유물 20 · 층 3
3D 에셋 CC0 7개 · 3.5MB

한 층은 입구 1 + 일반 5 + 보스 1, 총 일곱 방이에요. 층마다 유물 2개·상점 1·회복 1은 반드시 나오고 나머지는 무작위입니다. 영웅 넷은 공격 방식이 달라요. 기사는 3타 콤보, 야만인은 360도 도끼, 도적은 뒤치기가 2배, 마법사는 투사체를 던집니다.

three.js가 뭐냐면요

브라우저에서 3D를 그리는 자바스크립트 라이브러리예요. 유니티 같은 게임 엔진과 달리 따로 설치할 게 없고, 웹페이지 안에서 그냥 돌아갑니다.

그래서 이 게임도 주소만 누르면 바로 시작돼요. 앱 설치도, 내려받기도 없이요. 요즘 보이는 AI 3D 데모가 대부분 이걸로 만들어진 것도 같은 이유고요. AI가 코드를 뱉으면 브라우저에서 즉시 확인되니까 주고받기가 빠릅니다.

캐릭터값은 한 푼도 안 들었어요

기사·야만인·도적·마법사에 적들까지, 전부 무료 모델이에요. 파일 7개 다 합쳐 3.5MB고요.

CC0라는 라이선스가 붙은 것만 골랐어요. 저작권 표시조차 필요 없고 상업적으로 써도 되는 조건이라, 나중에 딴소리 나올 일이 없습니다. 이런 곳에서 받을 수 있어요.

  • 폴리 피자(poly.pizza) — 로그인 없이 바로 내려받고, 모델이 1만 개 넘어요
  • Quaternius · Kenney · KayKit — 게임용으로 다듬은 세트를 통째로 푸는 제작자들이에요

나머지는 에셋 없이 코드로 만들었습니다. 검과 도끼 같은 무기, 보스의 왕관, 상점 좌판은 전부 상자와 원기둥을 조합해서 그렸어요. 어울리는 무료 모델을 찾아 헤매는 것보다 그게 빨랐거든요.

프롬프트 대신 발주서를 썼어요

처음에 넣은 건 "던전 게임 만들어줘"가 아니었어요. 기획서를 먼저 쓰고, 그 안에 개발 단계 표를 넣었습니다. 기획서 docs/GAME_DESIGN.md의 12장이 그 표예요. 문서의 열두 번째 장 하나에, 만들 순서가 0단계부터 7단계까지 여덟 칸으로 적혀 있습니다.

  • 0단계 프로젝트 골격 — 타이틀 화면, 빈 방, 기사 서 있기
  • 1단계 방 한 개 전투 — 적 등장, 자동 공격, 피격, 결과 화면
  • 2단계 던전 지도와 방 이동, 문 보상 6종
  • 3단계 유물 20종, 금화 줍기, 보물 상자
  • 4단계 영웅 4종과 스킬, 영웅 선택 화면
  • 5단계 적 8종, 엘리트 2종, 1층 보스
  • 6단계 2·3층과 보스 2종, 승리 화면
  • 7단계 사운드, 입자 효과, 모바일 손보기

칸마다 "여기까지 되면 완료"를 한 줄씩 박아뒀어요.

첫 지시는 이것뿐이었습니다.

이 저장소는 비어 있다. docs/GAME_DESIGN.md를 읽고 12장 "개발 단계"의 0단계를 구현해라. 완료 기준을 만족하면 테스트·린트·빌드를 통과시키고 커밋해라.

0단계가 끝난 다음부터는 이 한 줄만 반복했어요.

12장의 다음 단계를 구현해라. 통과시키고 커밋해라.

장 번호는 계속 12장 그대로고, 그 안의 단계 번호만 1 → 2 → … → 7로 올라갑니다. 같은 문장을 일곱 번 더 넣으면 게임이 끝나요. 뭘 만들지 매번 설명할 필요가 없었습니다. 문서에 이미 다 적혀 있으니까요. 큰 작업은 계획부터 받으라고 썼던 것의 확장판인 셈이에요. 차이가 있다면 계획을 한 번 받고 마는 게 아니라, 기획서 자체를 티켓 목록으로 만들어뒀다는 점입니다.

규칙은 말이 아니라 lint로 박았어요

저장소 루트 CLAUDE.md에 규칙을 적었어요. 길게 쓰지 않았습니다. "sim 폴더는 순수 로직이니 Three.js를 import하지 마라", "매직 넘버는 data 폴더에 모아라", "300줄 넘으면 파일을 나눠라" 정도였어요.

그런데 문장으로만 두면 사흘 뒤에 깨집니다. 그래서 lint 규칙으로 박아뒀어요. sim 폴더에서 Three.js나 window를 쓰면 아예 에러가 나게요.

lint가 뭔가요? 코드용 맞춤법 검사기예요. 문법이 틀려서 아예 안 돌아가는 걸 잡는 게 아니라, "돌아가긴 하는데 이렇게 쓰면 안 좋다"를 실행 전에 짚어줍니다. 안 쓰는 변수, 오타 난 이름 같은 것들이요. 여기에 내가 만든 규칙을 얹을 수 있어서, 지시가 "지켜주면 좋겠는 부탁"에서 "안 지키면 커밋이 안 되는 벽"으로 바뀝니다.

이 몇 줄이 실제로 한 일이 컸어요.

  • 테스트 160개가 전부 순수 함수에 붙었어요. 회피 무적 시간이나 유물 3중첩 상한 같은 걸 브라우저 없이 검증합니다
  • 300줄 제한이 리팩터를 자동으로 시켰어요. 상점을 만들다 파일이 넘치자 방 흐름과 HUD 갱신을 알아서 갈랐습니다
  • 밸런스 수치가 한 파일에 모였어요. "이 적 공격이 느리다"는 피드백에 손댄 건 숫자 두 개였습니다

제일 컸던 건 AI가 자기 결과를 보게 만든 것

브라우저 게임은 테스트가 다 통과해도 화면이 이상할 수 있어요. 검이 옆으로 누워 있거나, 문 아이콘이 벽에 가리거나, 도끼가 적을 때린 뒤에 휘둘러지거나요. 유닛 테스트로는 절대 안 잡힙니다. 눈으로 봐야 알아요.

그래서 게임 안에 디버그 훅을 심었어요.

// 게임을 전역에 꺼내 밖에서 조작해요
globalThis.bonemaze = app;
 
// 층·방·적 수를 한 번에 확인해요
bonemaze.snapshot();
 
// 방을 즉시 비워 다음으로 넘어가요
bonemaze.cheat('clear');
 
// 카메라를 원하는 각도로 옮겨요
bonemaze.world.rig.setDebugCamera(
  [0, 8, 6],
  [0, 0, 0],
);

여기에 헤드리스 브라우저를 붙이면 AI가 직접 플레이하고 스크린샷을 찍어서 확인합니다. 보스방까지 방을 비우며 자동으로 이동하고, 보스 체력을 페이즈 전환 직전으로 맞춘 다음, 그 순간을 찍는 식이에요.

이 루프에서 상점도 바뀌었어요. 처음엔 카드 팝업이었는데, 스크린샷을 보니 게임이 아니라 메뉴 화면처럼 보이더라고요. 그래서 방 안에 좌판과 상인을 두고, 받침대 앞에 서면 살 수 있는 방식으로 다시 만들었습니다.

뼈 상인의 좌판. 받침대마다 가격표가 떠 있고 하단에 숫돌 45금화 구매 안내가 보인다

막혔던 지점 셋

1. 마법탄 한 발 쏠 때마다 화면이 멎어요

증상이 이상했어요. 프레임이 낮은 것도 아닌데 처음 몇 분만 툭툭 멎다가, 뒤로 갈수록 멀쩡해지는 거예요. 보통 버그는 반대잖아요.

원인은 조명이었습니다. 마법탄이 날아갈 때 주변 바닥이 환해지죠? 그 빛 하나가 조명이에요.

게임은 화면을 그리기 전에 "조명 3개짜리 화면은 이렇게 그린다"는 레시피를 미리 써둡니다. 그런데 조명이 하나 늘거나 줄면 그 레시피를 못 써요. 새로 써야 하고, 쓰는 동안 화면이 멈춥니다.

이 게임은 마법탄을 쏠 때마다 조명이 하나 켜지고, 탄이 사라지면 꺼졌어요. 문이 열릴 때도요. 마법탄 한 발마다 레시피를 새로 쓰고 있었던 거예요.

뒤로 갈수록 나아진 것도 이제 설명이 돼요. 한 번 쓴 레시피는 보관해두거든요. "조명 3개"는 처음 만날 때만 멎고, 두 번째부터는 꺼내 씁니다. 처음 겪는 조합이 몰린 초반에만 멎었던 거죠.

셰이더 재컴파일 횟수 비교. 플레이 중 28회에서 4회로 줄고, 부팅 때는 31회에서 49회로 옮겨갔다

고친 방법은 싱거워요. 조명을 미리 정해진 개수만큼 만들어두고, 켜고 끄는 대신 밝기만 0으로 낮췄어요. 개수가 절대 안 변하니 레시피를 새로 쓸 일이 없죠. 여기에 로딩 화면 뒤에서 적과 보스를 한 번씩 미리 그려, 자주 쓸 레시피를 미리 써두기까지 했습니다.

2. 캐릭터 고르다가 화면이 확 커져요

영웅 카드를 빠르게 두 번 누르면 화면이 줌인돼버렸어요. 게임이 아니라 웹페이지를 들여다보는 꼴이 되죠.

웹에는 "확대 금지" 설정이 있어요. 넣었는데도 안 먹혔습니다. 아이폰 사파리가 그 설정을 대놓고 무시하거든요.

일부러 그래요. 글자가 작아 못 읽는 사람이 확대조차 못 하게 되는 걸 막으려고, 개발자가 못 끄게 만들어둔 겁니다. 취지는 맞는데 게임에서는 곤란하죠.

그래서 막는 대신 가로채기로 갔어요. 첫 탭 뒤 320밀리초 안에 두 번째 탭이 들어오면, 브라우저가 "확대하라는 뜻"으로 읽기 전에 코드가 먼저 삼켜버립니다. 모바일 웹 만드시는 분은 언젠가 만날 문제예요.

3. 도끼가 때린 뒤에 휘둘러져요

야만인의 회전 베기 애니메이션은 2.4초인데, 실제로 몸이 도는 구간은 0.3초에서 1.0초 사이뿐이었어요. 앞의 준비 동작이 재생되는 동안 타격 판정이 먼저 나가서 "맞고 나서 휘두르는" 것처럼 보였습니다. 애니메이션을 프레임 단위로 훑어서 회전 속도가 실제로 튀는 구간을 찾고, 그 구간만 한 번의 휘두름에 맞춰 잘라 재생하게 고쳤어요.

사람이 한 일은 따로 있었어요

제일 좋았던 피드백은 이거였어요.

"피했는데도 맞으면 피하는 재미가 없잖아."

무슨 말이냐면요. 적이 공격하기 전에 바닥에 부채꼴 모양으로 공격 범위를 미리 보여줘요. 여기 맞으니까 피하라는 신호죠.

그런데 그 부채꼴이 플레이어를 계속 따라다녔어요. 옆으로 굴러서 범위 밖으로 나가면, 부채꼴도 같이 홱 돌아서 다시 나를 덮었습니다. 어디로 피하든 결국 맞는 거예요. 범위를 보여주는 의미가 없었죠. 회피 버튼을 눌러도 소용이 없으니까요.

스펙에는 "예고 0.6초 뒤에 타격"이라고만 적혀 있었어요. 그러니 틀리게 구현한 게 아니었습니다. 예고 중에 방향을 고정하라는 말이 없었을 뿐이에요.

이 한마디로 규칙이 바뀌었어요. 부채꼴이 뜨는 순간 공격 방향이 고정됩니다. 그 뒤에 범위 밖으로 나가면 적은 허공을 치고, 헛친 뒤에는 회복 동작이 끝나야 다시 조준할 수 있어요. 그제야 "보고, 판단하고, 피한다"가 성립했습니다.

적이 부채꼴 범위를 붉게 표시하며 공격을 예고하는 장면. 이 범위 밖으로 나가면 헛친다

스펙은 사람이 못 적은 걸 채우지 못해요. "이렇게 하면 재미가 없다"는 판정은 플레이해 본 사람만 할 수 있습니다.

다시 한다면 이 순서로

  1. 기획서를 먼저 씁니다. 화면·수치·파일 경로까지요. 그리고 그 안에 단계 표를 넣습니다. 이게 그대로 티켓이 돼요
  2. 규칙은 lint로 강제합니다. "여기선 이거 쓰지 마"를 문장으로만 두면 사흘 뒤에 깨져요
  3. 한 단계 = 한 PR = 검증 통과. 테스트·린트·빌드를 통과해야 커밋한다는 조건을 매번 붙입니다
  4. AI가 자기 결과를 보게 합니다. 디버그 훅 + 헤드리스 브라우저 + 스크린샷. 이게 없었으면 검이 옆으로 누운 채로 나왔을 거예요
  5. 밸런스 수치는 한 곳에 모읍니다. 고칠 파일이 하나여야 피드백 반영이 빨라져요

세 왕을 모두 쓰러뜨린 결과 화면

이틀이 걸렸지만, 제가 실제로 앉아서 한 일은 대부분 플레이하고 이상한 걸 말해주는 것이었어요. 코드는 거의 안 읽었습니다. 대신 기획서를 오래 썼어요.

한 판 해보시고 어디가 이상한지 알려주세요. 카톡 오픈채팅 ⚡ AI랑 코딩하는 사람들에서 기다릴게요.

오늘 글, 도움이 되셨나요?