# 用 Claude Fable 5 一次性生成完整《Raccoon Heist》游戏

- 来源：Simon Willison 博客
- 作者：Simon Willison
- 发布时间：2026-08-06 03:42
- AIHOT 分数：70
- AIHOT 标记：精选
- AIHOT 链接：https://aihot.virxact.com/items/cmsgiawmi06n8ro5qttsxdlib
- 原文链接：https://simonwillison.net/2026/Aug/5/raccoon-heist

## 精选理由

给出从旧推文到可运行游戏的完整闭环：用 GitHub Pages 实时预览、Playwright 自动抓屏修复，使 agent 能在无人类干预下迭代出可用产物，适合探索 agent 编码游戏的开发者直接复用该流程。

## AI 摘要

Simon Willison 将 2022 年 GPT-3 和 DALL-E 生成的游戏概念与截图输入 Claude Fable 5（运行于 Claude Code for web），成功构建出可玩的 3D 浏览器游戏。

## 正文

早在2024年，我曾在推特上发布过由GPT-3生成的一个游戏概念截图，以及用DALL-E创作的所谓概念“艺术”。今天，正值那条推文发布四周年之际，我决定看看Claude Fable 5（在Claude Code网页版中运行）能否仅凭那条推文的内容就构建出整个游戏。它干得相当不错！

你可以在这里试玩这个游戏。下面是一段简短的视频演示：

视频 · 前往原文观看

我是如何构建的

这是2022年8月5日的那条推文：

我当时给GPT-3的提示词是：

为一款电脑游戏撰写一份详细的产品描述，游戏内容是一队浣熊实施抢劫。

那还是在文本补全时代，你需要让GPT-3补全剩余的文本。它补全的内容是：

在《浣熊大劫案》中，你和你的浣熊窃贼团队的任务是完成一系列大胆的抢劫。从抢银行到偷窃无价艺术品，对于你的毛茸茸团队来说，没有哪项工作是太大或太小的。你需要运用你的智慧和技能来躲避警察，并带着赃物干净利落地逃脱。凭借激动人心的玩法和一群迷人的角色，《浣熊大劫案》是任何寻求轻松诙谐冒险的人们的完美游戏。

我给DALL-E的提示词很简短：

一款电子游戏的截图，画面中一队浣熊正在进行抢劫

今天的实验是：我能否将这些截图连同一条要求其编写游戏的提示词一起丢给Fable 5，然后放手让它自行发挥，最终得到一个可运行的游戏？

将Claude Code网页版设置为使用GitHub Pages

Claude Code网页版一个令人沮丧的地方在于，当它还在工作时，你很难测试它正在构建的内容。

我一直在使用GitHub Pages来绕过这个限制，并且发现效果非常好。

我的流程如下：

在 https://github.com/new 为项目创建一个新仓库——这可以是公开的也可以是私有的，这个技巧对两者同样有效。

启动一个Claude Code网页版会话，可以在Claude的iPhone或桌面应用中，也可以在浏览器中访问 https://claude.ai/code

告诉Claude要做什么，并鼓励它尽快提交一个index.html页面。这将创建一个名称类似claude/3d-raccoon-heist-game-50n293的分支。

进入仓库的 Settings -> Pages 区域（我的情况是 github.com/simonw/raccoon-heist/settings/pages），选择“Deploy from a branch”，选中分支名称，然后点击 Save。

就这么简单！每次推送后大约 30 秒内，最新内容就会显示在 yourname.github.io/your-repo/ 上。

如果你对私有仓库执行此操作，任何能猜到仓库名称的人都能查看已发布的内容。我个人对此不太担心。

Fable 5 的提示词

以下是我给 Fable 5 的提示词（写在手机上的备忘录应用里——整个项目都是在移动端完成的）。我附上了原始推文中的两张图片。

构建这个 3D 游戏，面向浏览器运行。

此仓库已配置为提供静态文件服务，所以请确保有一个 index.html 来加载所有其他内容。

确保它对移动端友好（支持触控操作，在小屏幕上运行良好）。

你有一个 OpenAI API 密钥，可以访问他们的图像生成模型 API，用它来为你的 3D 模型生成纹理。文档在这里：https://developers.openai.com/api/docs/guides/image-generation - 使用 gpt-image-2

独立工作——不要让我做任何进一步的设计决策。确保游戏有趣、有点出人意料、有良好的浣熊抢劫氛围，并且视觉上令人愉悦。

尽可能频繁地提交并推送，这样我就能预览你的工作——先从一个展示标题画面的 index.html 开始，然后在此基础上继续构建。

在工作过程中将内容追加到 notes.md 文件中，并将对该文件的更改包含在每次提交中。

我没有做任何技术选型。我（正确地）猜测，根据之前的实验，它很可能会使用 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 提示词：

游戏主视觉图，低多边形 3D 渲染风格，氛围感十足的夜景：一只可爱的低多边形浣熊戴着小小的黑色小偷面罩，用后腿直立偷偷潜行，怀里抱着一枚闪闪发光的金币，旁边是一个被掀翻的金属垃圾桶，背景是郊区住宅，窗户透出温暖的灯光，深蓝色夜空，满月高悬，萤火虫飞舞，电影感轮廓光，充满俏皮的盗窃冒险氛围。无文字、无字样、无标志。

而最终生成的图片（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 自动化试玩验证，覆盖桌面端、竖屏手机端和横屏手机端三种视口。

游戏内容：你是一只戴着面具的浣熊，从同伴的垃圾箱藏身点溜出来，潜入月光下的死胡同。收集金币、宝石、戒指、甜甜圈和鱼（它们会肉眼可见地堆在你背上，拖慢你的速度），然后摇摇晃晃走回家，在黎明前把它们存进银行。每晚目标都会提高，风险也随之升级：第一晚有一个拿手电筒扫射的守卫，第二晚有两个，第三晚增加一只靠嗅觉追踪的猎犬，完全无视视线遮挡。一辆警车用大灯扫过街道，住户会随机从窗户探出头来，一只海鸥会时不时俯冲轰炸你还没收走的战利品，披萨片能触发 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 继承了标题画面的全屏 .stars CSS，悄悄吞掉了“下一夜”按钮上的每一次点击。两个问题均已修复并做了回归测试，拾取、存钱、追逐/被抓、黎明、夜晚推进以及失败/重试流程也都一并覆盖。

notes.md 里有完整的构建日志。游戏已上线在该分支上——只需托管仓库根目录并打开 index.html 即可。

这游戏好玩吗？

这是一个令人印象深刻的起点，但它算不上一个好游戏。

你操控一只浣熊在院子里收集物品——甜甜圈、鱼、金币、珠宝——同时躲避持手电筒的保安，后期关卡还要躲一条狗。你有有限的携带容量，一旦装满就需要把东西丢到垃圾箱里。如果捡到一块披萨，你会获得临时加速。

完全没有团队机制——垃圾箱旁边有两只静态的浣熊，但它们纯粹是装饰。

随着关卡推进，难度会略有提升——第 3 关引入的狗是最有意思的新机制——但通关非常非常容易。而且也挺无聊的——每个夜晚有固定时长，你可以把所有物品都收集完，然后只能干等着天亮。

我对这个实现印象深刻。它是完全 3D 的，有垃圾桶，手电筒的光锥效果很有趣，而且视觉风格相当连贯统一。它在手机上也能运行。音乐（据 Claude 说，是“程序化生成的 WebAudio 配乐（带点偷偷摸摸感觉的行走贝斯爵士，外加全部音效——零音频文件）”）虽然简单，但感觉恰到好处。

作为一个完成的游戏项目，它只能算平庸。但作为从一个单一提示词出发的起点，我认为它非常令人印象深刻。

我现在已经用 vibe coding 方式做了不少游戏。从游戏性的角度来看，它们全都令人大失所望——事实证明，设计出好玩的游戏仍然是人类独有的特质，而且这需要的技能和经验远超 Claude 或我所能带来的。

话虽如此，我还是强烈推荐把捣鼓游戏开发项目作为探索智能体能力的一种方式。这是一种有趣、低风险的尝试新事物的途径。如果你能坚持足够久，说不定还真能做出点值得一玩的东西！

发布于 2026 年 8 月 5 日晚上 7：42 · 在 Mastodon、Bluesky、Twitter 上关注我，或订阅我的通讯
