早在2022年,我发推文展示了由GPT-3生成的一个游戏概念截图,以及一些用DALL-E创作的概念“艺术”。今天,在那条推文四周年之际,我决定看看Claude Fable 5(运行在Claude Code for web中)能否根据那条推文的内容构建整个游戏。它做得相当不错!
你可以在这里玩这个游戏。这是GitHub仓库,还有一个简短的视频演示:
▶ https://static.simonwillison.net/static/2026/raccoon-heist-720p.mp4
我是如何构建这个的
这是2022年8月5日的推文:

我当时的GPT-3提示是:
Write a detailed product description of a computer game where a team of raccoons go on heists.
那是在文本补全时代,你让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来绕过这个限制,并发现它效果非常好。
这是我的流程:
- 在https://github.com/new为项目创建一个新仓库——这可以是公开的或私有的,这个技巧对两者同样有效。
- 在Claude iPhone或桌面应用中,或在浏览器中访问https://claude.ai/code,启动一个Claude Code for web会话。
- 告诉Claude要做什么,并鼓励它尽快提交一个
index.html页面。这将创建一个名称类似于claude/3d-raccoon-heist-game-50n293的分支。 - 导航到仓库的设置 -> Pages区域(在我的情况下是
github.com/simonw/raccoon-heist/settings/pages),选择“从分支部署”,选择分支名称,然后点击保存。
就这么简单!每次推送后大约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认为它“华丽”)——不过我注意到,当它在桌面端显示时,会被裁剪到只剩顶部三分之一,浣熊不见了!

然后是我最喜欢的改动:它添加了狗:
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' });

它又做了几处调整,然后宣布自己完成了:
Raccoon Heist 已构建、测试并推送——在
claude/3d-raccoon-heist-game-50n293上有7次提交,全部通过桌面、竖屏手机和横屏手机视口上的自动化Playwright试玩验证。游戏内容:你是一只戴着面具的浣熊,从你的团队垃圾箱藏身处溜出来,进入月光下的死胡同。收集金币、宝石、戒指、甜甜圈和鱼(它们会明显堆叠在你背上并让你减速),然后摇摇摆摆地回家,在天亮前把它们存入。每一夜都会提高目标和热度:第1夜有一名手电筒扫射的守卫,第2夜有两名,第3夜增加一只完全无视视线的追踪猎犬。一辆警车用车头灯扫过街道,房主会随机从窗户探出头,一只海鸥会定期俯冲轰炸你未收集的战利品,披萨片会触发6秒的速度狂热,还有一台稀有的金色电视,如果你能把它拖回家就值120分。胜利评级:TRASH PANDA ⭐ → CAT BURGLAR ⭐⭐ → MASTER OF DISGUISE ⭐⭐⭐,你的最佳成绩会保存到localStorage。
构建方式:内置Three.js(无CDN)、程序化低多边形角色,以及程序化WebAudio配乐(鬼祟的行走贝斯爵士乐加上所有音效——零音频文件)。全部七种地面/墙壁纹理和标题屏幕主视觉图都是用gpt-image-2生成的,并作为静态资源提交,因此部署的游戏不会进行任何API调用。移动端获得一个动态触摸摇杆(在你触摸的任何位置出现)加上一个DASH按钮;桌面端使用WASD/方向键和空格键。
测试发现了两个值得提及的真实bug:画布在任何真实手机上以2倍尺寸渲染(一个
cssText赋值抹掉了Three.js的内联尺寸设置——桌面DPR-1测试完全掩盖了它),以及胜利屏幕的星级评分div继承了标题屏幕的全屏.starsCSS,并静默吞掉了对“下一夜”按钮的每一次点击。两者都已修复并进行了回归测试,同时测试了拾取、存入、追逐/被抓、黎明、夜晚推进以及失败/重试流程。
notes.md有完整的构建日志。游戏已在分支上上线——只需在仓库根目录提供服务并打开index.html。
这个游戏好玩吗?
这是一个令人印象深刻的起点,但它不是一个好游戏。
你操控浣熊在院子里四处移动收集物品——甜甜圈、鱼、金币、珠宝——同时避开拿着手电筒的守卫,在后面的关卡中还要避开一只狗。你的携带容量有限,一旦装满,就需要把东西送到垃圾箱。如果你捡到一片披萨,你会获得暂时的速度提升。
完全没有团队机制——垃圾箱旁边还有两只静止的浣熊,但它们纯粹是装饰。
随着关卡推进,它会稍微变得更有挑战性——第3关引入的狗是最有趣的新机制——但它非常、非常容易通关。它也相当无聊——每一夜都有固定时长,你可以收集所有物品,然后在等待黎明时无事可做。
我对实现印象深刻。它是完全3D的,有垃圾桶,手电筒照明锥很有趣,而且它有相当连贯的视觉风格。它在移动端也能运行。音乐(根据Claude的说法是“程序化WebAudio配乐(鬼祟的行走贝斯爵士乐加上所有音效——零音频文件)”)很简单但感觉差不多合适。
作为一个完成的游戏项目,它很平庸。作为一个来自单一提示的起点,我认为它非常令人印象深刻。
我现在已经凭感觉编码了不少游戏。从玩法角度来看,它们都令人深感失望——事实证明,设计有趣的游戏仍然是人类独有的特质,而且需要比Claude或我能投入的更多的技能和经验。
尽管如此,我强烈推荐把游戏开发项目作为探索智能体能力的一种方式。这是一种有趣、低风险的方式来尝试新事物。如果你坚持足够久,你甚至可能做出值得一玩的东西!
2026年8月7日更新:我把同样的提示交给了运行GPT-5.6 Sol Ultra的OpenAI Codex Desktop,得到了明显更好的结果——GPT-5.6 Sol注意到了浣熊小队进行抢劫的重要性,并构建了一个游戏,你必须在博物馆中救出你的两名队友,然后堆叠在他们上面偷走金沙丁鱼。
