One-shotting a Raccoon Heist game using Claude Fable 5

Simon Willison

Claude Fable 5로 라쿤 하이스트 게임을 원샷으로 만들기

원문은 Simon Willison님이 에 게재했습니다. 이 블로그 구독하기

2022년에 트윗한 적이 있다—GPT-3가 생성한 게임 컨셉 스크린샷과 DALL-E로 만든 컨셉 “아트”였다. 그 트윗의 4주년을 맞은 오늘, 그 트윗 내용만으로 Claude Fable 5(Claude Code for web에서 실행)가 게임 전체를 만들 수 있을지 시험해 봤다. 결과는 꽤 괜찮았다!

여기서 게임을 플레이할 수 있다. GitHub 저장소는 여기에 있으며, 짧은 시연 영상은 다음과 같다:

제작 과정

다음은 2022년 8월 5일자 트윗이다:

Simon Willison @simonw - 새로운 취미: GPT-3와 DALL-E를 조합해 60초 만에 비디오 게임을 프로토타이핑하기. 'Raccoon Heist'를 소개합니다. 두 장의 이미지 - 첫 번째는 GPT-3 플레이그라운드 스크린샷이며 프롬프트는 아래에 있습니다. 오른쪽은 명백히 DALL-E가 생성한 거의 아이소메트릭 3D 컴퓨터 게임 이미지로, 두 마리 너구리가 미래적인 건물을 지나 거리를 몰래 이동하며 뭔가를 훔치려는 듯한 모습입니다.

당시 GPT-3에 넣은 프롬프트는 다음과 같았다:

Write a detailed product description of a computer game where a team of raccoons go on heists.

당시는 텍스트 완성(text completion) 시절이라 GPT-3에 나머지 텍스트를 이어 쓰게 하는 방식이었다. GPT-3가 완성한 내용은 다음과 같다:

“Raccoon Heist”에서 플레이어는 도둑 너구리 팀과 함께 대담한 하이스트를 연이어 성공시켜야 한다. 은행 털기부터 값비싼 예술품을 훔치는 일까지, 털북숭이 팀에게는 크든 작든 못할 일이 없다. 경찰을 피하고 전리품을 챙겨 깔끔하게 도망치려면 재치와 실력이 필요하다. 흥미진진한 게임플레이와 매력적인 캐릭터들이 어우러진 “Raccoon Heist”는 가벼운 범죄 소동을 즐기고 싶은 누구에게나 안성맞춤인 게임이다.

DALL-E에 넣은 프롬프트는 짧았다:

Screenshot from a video game where a team of raccoons go on a heist

오늘의 실험은 이렇다. 그 스크린샷들을 프롬프트와 함께 Fable 5에 던져 게임 작성을 시킨 뒤, 완전히 자율에 맡기면 끝에서 작동하는 게임이 나올 수 있을까?

Claude Code for web을 GitHub Pages와 연동하기

Claude Code for web의 답답한 점 중 하나는 작업이 진행 중인 동안 결과물을 테스트하기가 어렵다는 것이다.

나는 이 제약을 우회하기 위해 GitHub Pages를 활용해 왔는데, 아주 잘 통했다.

내 작업 과정은 다음과 같다:

  1. https://github.com/new 에서 프로젝트용 새 저장소를 만든다. 공개든 비공개든 이 방법은 둘 다 똑같이 잘 작동한다.
  2. Claude iPhone 앱이나 데스크톱 앱, 또는 브라우저의 https://claude.ai/code에서 Claude Code for web 세션을 시작한다.
  3. Claude에게 무엇을 작업할지 알려주고, 가능한 한 빨리 index.html 페이지를 커밋하도록 유도한다. 그러면 claude/3d-raccoon-heist-game-50n293 같은 이름의 브랜치가 생성된다.
  4. 저장소의 Settings -> Pages 영역(내 경우에는 github.com/simonw/raccoon-heist/settings/pages)으로 이동해 “Deploy from a branch”를 선택하고, 해당 브랜치 이름을 고른 뒤 Save를 누른다.

이게 전부다! 푸시할 때마다 약 30초 안에 최신 내용이 yourname.github.io/your-repo/에서 보인다.

비공개 저장소로 이렇게 해도 저장소 이름을 추측할 수 있는 사람이라면 누구나 게시된 콘텐츠를 볼 수 있다. 나는 개인적으로 크게 신경 쓰지 않는다.

Fable 5 프롬프트

Fable 5에 준 프롬프트는 다음과 같다(휴대폰 메모 앱에서 작성했다. 이 프로젝트 전체를 모바일로 진행했다). 원래 트윗의 두 이미지도 함께 첨부했다.

Build this 3D game, for the browser.

This repo is configured to serve static files so make sure there is an index.html that loads everything else.

Make sure it is mobile-friendly (touch controls, works well on small screens).

You have an OpenAI API key and access to their image generation model APIs, use that for textures to use with your 3D models. Docs here: https://developers.openai.com/api/docs/guides/image-generation - use gpt-image-2

Work independently - do not ask me to make any further design decisions. Make sure the game is fun, a little surprising, has good raccoon heist vibes, and is visually pleasing.

Commit and push as often as possible so I can preview your work - start with an index.html that presents a title screen, then build from there.

Append to a notes.md file as you work, including your changes to that as part of every commit.

기술 스택은 따로 정하지 않았다. 이전 실험들을 바탕으로 아마도 Three.js를 쓸 거라고 예상했는데, 예상대로였다.

Claude에게 OpenAI 키를 주는 것이 능력의 빈틈을 메우는 데 아주 효과적이라는 걸 알게 됐다. 이번 경우에는 텍스처로 쓸 이미지를 생성할 방법이 필요했는데, Fable은 이미지 생성기를 위한 프롬프팅을 아주 잘한다!

“독자적으로 작업하라—추가적인 디자인 결정은 내게 묻지 마라”라고 한 이유는 내 추가 입력 없이 완전하고 작동하는 게임을 만들어낼 수 있는지 보고 싶었기 때문이다.

“가능한 한 자주 커밋하고 푸시해서 내가 작업을 미리 볼 수 있게 하라”고도 했다. Claude iPhone 앱에서 Claude Code를 쓸 때는 GitHub 저장소를 지정하면 브랜치에서 작업이 진행된다. “가능한 한 자주 푸시하라”고 하면 커밋이 곧바로 해당 브랜치에 쌓이기 시작한다.

notes.md를 요청하는 걸 일종의 덤으로 좋아한다. 완성된 파일은 여기에서 볼 수 있고, 개를 추가했을 때 남긴 기록은 다음과 같다:

새로운 난관 추가: 3일차 밤부터 마당에 순찰하는 경비견이 등장한다. 낮은 폴리곤의 갈색 사냥개로, 가시 박힌 빨간 목걸이와 흔들리는 꼬리를 달고 있다. 무작위 지점 사이를 배회하며, 12 유닛 이내에서는 냄새를 맡고 추적한다(시야와는 무관하며 전적으로 후각에 의존한다. 머리 위에 👃 표시와 짖는 소리로 표현된다). 17 유닛 이상 거리를 벌리면 추적을 포기한다. 발각 메시지는 이제 원인별로 구분된다: 경비원 / 전조등 / 사냥개. 배회 → 추적 → 발각 흐름을 자동화된 테스트로 검증했다.

트랜스크립트 살펴보기

Claude Code 공유 세션에 접근할 수 있으며, 나는 claude-code-transcripts 도구로 직접 HTML 버전을 추출했는데 여기에서 볼 수 있다.

Fable은 인덱스 페이지부터 시작해 Three.js 복사본을 벤더링한 뒤, 직접 gen_textures.py 스크립트(복사본은 여기)를 작성했다.

텍스처를 생성한 뒤 제대로 보이는지 스팟 체크했다. 쓰레기통용으로 생성한 metal.jpg 파일은 이렇게 생겼는데, 정작 게임 내에서는 정확히 적용되지 않은 것 같다:

짙은 청회색 리벳 금속 패널로 이루어진 게임 텍스처 아틀라스. 왼쪽 상단에 손잡이가 달린 원형 해치가 있고, 가운데에는 골이 진 주름 패널, 왼쪽 하단에는 평평한 원형 판, 상단과 하단에는 밴드 형태의 스트립이 있다. 텍스트는 보이지 않는다.

이어서 게임의 첫 기본 버전을 만들고, Playwright를 이용해 “사전 설치된 Chromium에서 스모크 테스트”하기로 결정했다. 이를 통해 자신의 작업물을 직접 스크린샷으로 찍어 눈으로 확인할 수 있었다. 데스크톱과 모바일 너비 모두에서 테스트한 뒤, 모바일 너비에서는 너구리가 보이지 않는다는 것을 발견하고 이를 수정했다:

이제 모바일에서도 너구리, 쓰레기통 은신처, 그리고 두 동료 너구리가 모두 완벽하게 보인다. 이 중요한 수정 사항을 커밋한다.

이어서 타이틀 화면을 생성하기로 했고, gen_title.py 스크립트를 이용해 만들었다. 이때 사용한 gpt-image-2 프롬프트는 다음과 같다:

Video game key art, low-poly 3D render style, moody nighttime scene: a cute low-poly raccoon wearing a tiny black burglar mask sneaking on its hind legs carrying a glowing gold coin, next to a tipped-over metal trash can, suburban house with warm glowing windows in the background, deep blue night, full moon, fireflies, cinematic rim lighting, charming heist caper mood. No text, no words, no logos.

그리고 결과물 이미지(Claude는 “gorgeous”라고 평했다)—다만 데스크톱에서는 이미지가 위쪽 3분의 1만 잘려 보여 너구리가 보이지 않는다는 점을 덧붙인다!

뒤집힌 쓰레기통 옆에서 금화를 들고 있는 폴리곤 스타일 너구리, 배경에는 집과 달이 보인다.

그리고 내가 가장 좋아하는 변경점, 개를 추가한 부분이다:

export function makeDog() {
  const g = new THREE.Group();
  const BROWN = 0x8a6440, DARK = 0x5e4128;
  const body = new THREE.Mesh(new THREE.SphereGeometry(0.42, 10, 8), M(BROWN));
  body.scale.set(0.9, 0.8, 1.5);
  body.position.y = 0.55;
  body.castShadow = true;
  g.add(body);
  const head = new THREE.Mesh(new THREE.SphereGeometry(0.3, 10, 8), M(BROWN));
  head.position.set(0, 0.85, 0.62);
  g.add(head);
  const snout = new THREE.Mesh(new THREE.SphereGeometry(0.16, 8, 6), M(DARK));
  snout.scale.set(0.9, 0.7, 1.3);
  snout.position.set(0, 0.76, 0.9);
  g.add(snout);
  const nose = new THREE.Mesh(new THREE.SphereGeometry(0.06, 6, 6), M(BLACK));
  nose.position.set(0, 0.78, 1.08);
  g.add(nose);
  for (const s of [-1, 1]) {
    const ear = new THREE.Mesh(new THREE.SphereGeometry(0.12, 6, 6), M(DARK));
    ear.scale.set(0.7, 1.3, 0.5);
    ear.position.set(0.2 * s, 1.08, 0.55);
    g.add(ear);
    const eye = new THREE.Mesh(new THREE.SphereGeometry(0.05, 6, 6), M(0x1a1a1a, { emissive: 0x331111 }));
    eye.position.set(0.13 * s, 0.92, 0.86);
    g.add(eye);
  }
  const tail = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.09, 0.5, 6), M(DARK));
  tail.position.set(0, 0.8, -0.62);
  tail.rotation.x = 0.8;
  g.add(tail);
  // spiked collar
  const collar = new THREE.Mesh(new THREE.TorusGeometry(0.22, 0.05, 6, 12), M(0xc0392b));
  collar.position.set(0, 0.78, 0.5);
  collar.rotation.x = Math.PI / 2.4;
  g.add(collar);
  const legGeo = new THREE.CylinderGeometry(0.07, 0.09, 0.34, 6);
  const legs = [];
  for (const [x, z] of [[-0.22, 0.35], [0.22, 0.35], [-0.22, -0.35], [0.22, -0.35]]) {
    const leg = new THREE.Mesh(legGeo, M(DARK));
    leg.position.set(x, 0.17, z);
    g.add(leg);
    legs.push(leg);
  }
  let phase = Math.random() * 10;
  return {
    group: g,
    animate(dt, speed) {
      phase += dt * (3 + speed * 10);
      const amp = Math.min(0.6, 0.1 + speed * 0.6);
      legs[0].rotation.x = Math.sin(phase) * amp;
      legs[3].rotation.x = Math.sin(phase) * amp;
      legs[1].rotation.x = -Math.sin(phase) * amp;
      legs[2].rotation.x = -Math.sin(phase) * amp;
      tail.rotation.z = Math.sin(phase * 1.5) * 0.4;
      body.position.y = 0.55 + Math.abs(Math.sin(phase)) * 0.04 * (0.3 + speed);
    },
  };
}

그리고 Playwright를 이용해 한 차례 테스트를 진행했고, 테스트에는 또 다른 스크린샷도 포함됐다.

  // walk near the dog
  await page.evaluate(() => { const d = window.__rh.dog; window.__rh.teleport(d.x + 6, d.z); });
  await page.waitForTimeout(2000);
  info = await page.evaluate(() => JSON.stringify({ dog: window.__rh.dog, state: window.__rh.state, player: window.__rh.debug().player }));
  console.log('after approach:', info);
  await page.waitForTimeout(3000);
  info = await page.evaluate(() => JSON.stringify({ dog: window.__rh.dog, state: window.__rh.state }));
  console.log('after chase:', info);
  await page.screenshot({ path: __dirname + '/shot-dog.png' });

게임 스크린샷. 3일차 밤, 너구리가 한가운데에서 개와 겹쳐 있다. 'Busted! The Guard shooed you off!'라는 메시지가 표시되고, 화면 가장자리에는 붉은 기운이 감돌고 있다.

몇 가지 조정을 더 한 뒤, 스스로 완료를 선언했다:

Raccoon Heist는 제작, 테스트, 푸시를 마쳤다claude/3d-raccoon-heist-game-50n293 브랜치에 7번의 커밋, 모두 데스크톱, 세로형 휴대폰, 가로형 휴대폰 뷰포트에서 자동화된 Playwright 플레이스루로 검증됐다.

게임: 플레이어는 마스크를 쓴 너구리가 되어 동료들의 쓰레기통 은신처에서 달빛이 비치는 막다른 골목으로 몰래 빠져나온다. 동전, 보석, 반지, 도넛, 생선 등을 주우면 등에 visibly 쌓이고 이동 속도가 느려지며, 해가 뜨기 전에 뒤뚱뒤뚱 걸어 돌아와 은행에 맡겨야 한다. 밤이 지날수록 목표와 압박이 커진다. 1일차 밤에는 손전등을 휘두르는 경비원이 한 명, 2일차 밤에는 두 명, 3일차 밤에는 시야와 전혀 무관하게 냄새로 추적하는 사냥개가 추가된다. 경찰차가 전조등을 비추며 거리를 순찰하고, 집주인들이 무작위로 창밖을 내다보며, 갈매기가 주인이 없는 전리품을 주기적으로 낚아채고, 피자 조각을 먹으면 6초간 속도 FRENZY가 발동하며, 희귀한 황금 TV는 무게를 감수하고 집까지 끌고 오면 120점을 준다. 승리 등급: TRASH PANDA ⭐ → CAT BURGLAR ⭐⭐ → MASTER OF DISGUISE ⭐⭐⭐, 최고 기록은 localStorage에 저장된다.

구성: Three.js 벤더링(CDN 사용 안 함), 프로시저럴 로우폴리 캐릭터, 그리고 프로시저럴 WebAudio 사운드트랙(몰래 걷는 듯한 워킹 베이스 재즈와 모든 효과음 — 오디오 파일은 하나도 없음). 7개의 지면/벽 텍스처와 타이틀 화면 히어로 아트는 모두 gpt-image-2로 생성되어 정적 에셋으로 커밋됐으므로, 배포된 게임은 API 호출을 전혀 하지 않는다. 모바일에서는 터치하는 위치마다 나타나는 동적 터치 조이스틱과 DASH 버튼을 제공하고, 데스크톱에서는 WASD/방향키와 Space를 사용한다.

테스트에서 잡아낸 언급할 만한 실제 버그 두 가지: 캔버스가 실제 휴대폰에서 2배 크기로 렌더링되던 문제(cssText 할당이 Three.js의 인라인 사이징을 덮어쓰고 있었다 — 데스크톱 DPR-1 테스트에서는 전혀 드러나지 않았다), 그리고 승리 화면의 별점 div가 타이틀 화면의 전체 화면 .stars CSS를 상속받아 “다음 밤” 버튼에 대한 모든 탭을 조용히 가로채던 문제. 두 버그 모두 수정되어 픽업, 뱅킹, 추격/발각, 해 뜸, 밤 진행, 패배/재시도 흐름과 함께 회귀 테스트를 마쳤다.

notes.md에 전체 빌드 로그가 있다. 게임은 브랜치에서 라이브 상태다 — 저장소 루트를 서빙하고 index.html을 열면 된다.

게임은 재미있을까?

출발점으로는 인상적이지만, 좋은 게임이라고 하기는 어렵다.

플레이어는 마당을 돌아다니며 도넛, 생선, 금화, 보석 같은 아이템을 수집하면서 손전등을 든 경비원과 이후 레벨에서는 개를 피해야 한다. 들고 다닐 수 있는 양에는 제한이 있어 가득 차면 쓰레기통에 가져다 놓아야 한다. 피자 조각을 주우면 일시적으로 속도가 빨라진다.

팀 메커니즘은 전혀 없다. 쓰레기통 옆에 두 마리 너구리가 서 있긴 하지만 순전히 장식용이다.

레벨이 진행될수록 난이도가 조금 올라간다. 3레벨에 등장하는 개가 가장 흥미로운 새로운 요소지만, 전체적으로 클리어하기가 매우 쉽다. 게다가 꽤 지루하기도 하다. 각 밤마다 고정된 지속 시간이 있는데, 모든 아이템을 다 모은 뒤에는 해가 뜰 때까지 할 일이 없어 기다려야 한다.

구현 자체는 인상적이었다. 완전한 3D이며, 쓰레기통도 있고 손전등 빛 원뿔 연출도 재미있고, 시각적 스타일도 어느 정도 일관성이 있다. 모바일에서도 작동한다. 음악(Claude의 표현에 따르면 “프로시저럴 WebAudio 사운드트랙(몰래 걷는 듯한 워킹 베이스 재즈와 모든 효과음 — 오디오 파일은 하나도 없음)”)은 단순하지만 제법 어울린다.

완성된 게임 프로젝트로서는 평범하다. 하지만 단일 프롬프트에서 출발한 결과물로서는 매우 인상적이라고 생각한다.

지금까지 바이브 코딩으로 꽤 많은 게임을 만들어 봤다. 게임플레이 측면에서는 모두 크게 실망스러웠다. 재미있는 게임을 설계하는 일은 여전히 인간 고유의 영역이며, Claude나 나 모두 갖추지 못한 상당한 기술과 경험을 요구한다는 사실이 드러난 셈이다.

그렇다고 해도 에이전트의 역량을 탐색하는 방법으로 게임 개발 프로젝트를 만져보는 것을 적극 추천한다. 새롭고 다양한 시도를 해보기에 재미있고 위험 부담도 적다. 충분히 오래 붙잡고 있으면 정말 플레이할 만한 결과물을 만들어낼 수도 있다!

업데이트 2026년 8월 7일: 동일한 프롬프트를 GPT-5.6 Sol Ultra를 실행하는 OpenAI Codex Desktop에 던져봤더니 훨씬 더 나은 결과가 나왔다—GPT-5.6 Sol은 너구리 분대가 하이스트를 벌인다는 점의 중요성을 제대로 짚어냈고, 박물관에서 두 동료를 구출한 뒤 서로 탑을 쌓아 황금 정어리(Golden Sardine)를 훔쳐야 하는 게임을 만들어냈다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글