我用 GPT-Astra + Three.js,做了一款能盖房、避雨和守家的 3D 游戏
从一句"做个林间建造游戏",到一个可以真正游玩的浏览器世界,这次我没有把重点放在生成多少代码,而是让 GPT-Astra 持续参与设计、实现、测试和修正。最终做出来的《林间筑梦》(Wildwood),包含建造、家具、昼夜天气、角色寻路、怪物袭击和本地存档。

打开浏览器,眼前是一片低多边形森林。营地中央有一间木屋,右侧是河流,角色阿木站在门前。玩家可以采集木材和石料,继续扩建房屋,也可以布置床、桌椅、地毯和灯。夜晚和雷雨会改变环境,怪物则会沿路径接近家园并拆毁建筑。
它不是一段预先录好的动画,也不是用图片假装出来的 3D 页面。场景由 Three.js 实时渲染,建筑可以逐块放置、旋转、损坏和拆除,角色与怪物会在网格上真实寻路。
GPT-Astra 在这里做了什么
先说明一个容易混淆的地方:GPT-Astra 是开发过程里的协作者,不是游戏运行时的一部分。
我用它来阅读项目、拆分需求、修改代码、运行测试、打开浏览器检查交互,再根据截图和实际点击结果继续修正。玩家打开游戏后,天气、寻路、托管和战斗全部在本地运行,不需要连接大模型,也不会因为模型服务不可用而停摆。
这两层可以这样理解:
开发阶段
需求与反馈 -> GPT-Astra -> 修改代码 -> 测试与浏览器验证 -> 下一轮反馈
游戏运行阶段
玩家输入 -> React UI -> GameEngine -> Three.js 场景
-> 本地规则 / A* / 存档真正有用的不是让模型一次性吐出一个"大而全"的项目,而是让它进入工程循环。比如"建筑不要一直跟随鼠标"这句话,最后落到的是一个明确的 placementArmed 状态:点击目录物品时激活,成功放置一次后解除;没有待放置物品时,点击已建建筑则旋转它。一个看似简单的交互反馈,会同时影响状态机、射线检测、预览模型和测试。
先把游戏拆成三层
项目里最重要的三个文件分别承担不同职责:
app/game/model.ts:建筑配方、资源、耐久、放置规则、存档校验。app/game/engine.ts:Three.js 场景、游戏循环、交互、天气、敌人和存档同步。app/page.tsx:React 状态与界面,负责资源栏、工具栏、角色面板和设置。
这样拆分以后,规则不依赖画面。能不能在某个格子放墙、家具库存是否足够、旧存档是否合法,都可以直接做单元测试;Three.js 只负责把确定的状态画出来。
核心数据没有直接塞进 Mesh,而是保存成普通对象:
type Block = {
id: number;
kind: Kind;
x: number;
z: number;
rotation: number;
hp: number;
};引擎内部再维护 id -> THREE.Group 的映射。旋转一堵墙时,先更新 Block.rotation,再更新对应 Mesh;保存时只序列化数据,不尝试保存 Three.js 对象。这让存档、读档和规则测试都简单很多。
建造系统:射线、网格与单次放置
浏览器里的 3D 建造,第一步是把二维鼠标位置投射到三维世界。
引擎用 THREE.Raycaster 从相机发出射线,与地面平面求交,再除以格子尺寸并四舍五入:
private cellAtPointer() {
const point = new THREE.Vector3();
if (!this.ray.ray.intersectPlane(this.plane, point)) return null;
return {
x: Math.round(point.x / TILE),
z: Math.round(point.z / TILE),
};
}得到格子后还不能直接建造。placementError 会检查营地边界、河岸、家园核心、资源占位、人物站位、屋顶支撑和家具通道。预览模型根据结果变成绿色或红色。
鼠标语义经历了几轮调整,最后收敛成更接近建造游戏的操作:
- 点击目录里的建筑或家具,才出现跟随鼠标的预览。
- 点击地面放置一次,预览随即消失,避免连续误建。
- 没有待放置物品时,点击已经部署的物件,直接旋转 90°。
- 普通滚轮缩放镜头;按住 Shift 滚轮才旋转待建物品。
旋转只保留 0°、90°、180°、270° 四个方向。早期版本允许输入任意角度,很容易误拖到 259°。对格子建造来说,这种自由度没有带来足够收益,反而让墙体方向难以判断。后来增加了四向吸附,旧存档里的任意角度也会在读取时吸附到最近方向。
程序化低多边形场景
项目没有依赖远程模型资源,房屋、树木、石块、家具、角色和怪物主要由 Three.js 基础几何体组合而成。程序化资产的优势很现实:加载快、风格统一、参数容易改,也方便 GPT-Astra 在代码里直接调整比例和材质。
每一类物件由一个 THREE.Group 组织。墙板、立柱、窗框可以分别建模,再合并几何体减少绘制开销。材质按参数缓存,避免为相同颜色反复创建 MeshStandardMaterial。
const materials = new Map<string, THREE.MeshStandardMaterial>();
function mat(color: string, options = {}) {
const key = color + JSON.stringify(options);
if (!materials.has(key)) {
materials.set(key, new THREE.MeshStandardMaterial({
color,
roughness: 0.9,
flatShading: true,
...options,
}));
}
return materials.get(key)!;
}低多边形并不等于"随便放几个方块"。屋顶要有可读的坡面,墙体要能看出正反,角色的脸、背包、工具和雨伞需要在俯视镜头下仍然清楚。风格约束越明确,后续新增资产越不容易跑偏。
家具不是摆设,它属于同一套规则

家具沿用了建筑的对象模型、旋转、耐久和存档机制,但增加了制作库存和室内放置规则:必须先制作成品,摆放时消耗成品;床、桌椅会阻挡寻路,地毯可以和其他家具叠放;门内唯一通道不能被实体家具堵死。
进入家具分类时,镜头会切到更高的室内角度,并隐藏附近屋顶。这里必须区分"视觉隐藏"和"规则删除":屋顶虽然看不见,仍然提供遮雨,也仍然会被怪物破坏。
床和椅子还会影响舒适度。角色在室内空闲时,靠近床每秒额外恢复 2 点,靠近椅子恢复 1 点。家具因此不只是装饰,而是和生存状态发生了联系。
角色寻路与本地托管

角色使用 javascript-astar 做四方向寻路。网格会把墙、资源、河流、核心和实体家具标记为不可通行;劳动目标本身通常不可站立,所以角色需要找到目标旁边的可达格子。
自动托管不是大模型在线控制,而是一组可预测的本地规划规则。玩家可以选择发展家园、持续采集或维修巡查。托管遇到小雨会尝试回屋,雷雨和怪物来袭时优先避险或守卫,夜间则进入屋内休息。
安全屋也不是简单判断"附近有没有屋顶"。系统会检查墙和门是否闭合、角色所在格上方是否有屋顶、入口是否存在可走通路线。墙体出现缺口、屋顶损毁或门内被家具堵住,庇护都会失效。
这个部分最适合让 GPT-Astra 帮忙补边界:它可以沿着状态和调用链检查"路线中断后是否重算""避雨任务会不会提前扣材料""手动移动能否立即接管"等问题,再把这些边界变成测试。
昼夜、天气和一次真实的闪烁问题

游戏时间连续推进,每个昼夜随机为 4 到 6 分钟。天气在晴天、小雨和雷雨之间切换,每段持续 45 到 120 秒。太阳强度、环境光、天空、雾和火光会根据时间与天气平滑变化。
早期雷雨版本有一个明显问题:闪电逻辑会周期性把太阳强度直接设置为 7,而正常光照更新又会把它改回基础值,两个写入源互相打架,整屏不断跳亮。
修复方式不是调低闪电频率,而是让每一帧只有一个光照写入者,并通过指数平滑逼近目标值:
const blend = immediate ? 1 : 1 - Math.exp(-Math.max(0, dt) * 3);
this.sun.intensity = THREE.MathUtils.lerp(
this.sun.intensity,
targetSunIntensity,
blend,
);雨滴同样会读取屋顶格子。落到有屋顶的位置时,雨线在屋顶上方重置,不会穿进室内。这个细节很小,却直接决定"屋子真的能避雨"还是只在面板上显示一个安全标签。
怪物、防御与统一游戏循环
游戏循环由 requestAnimationFrame 驱动,并把单帧时间限制在 0.05 秒以内,减少切回标签页后的一次性跳变。
private animate = () => {
this.frame = requestAnimationFrame(this.animate);
const dt = Math.min(this.clock.getDelta(), 0.05);
this.controls.update();
this.updateAgent(dt);
this.updateEnemies(dt);
this.updateResident(dt);
this.updateShelterVisuals(dt);
this.updateLighting(dt);
this.renderer.render(this.scene, this.camera);
};怪物使用 A* 接近建筑,会寻找目标、移动、攻击并重新规划路线。守卫塔攻击一定范围内的怪物;角色在屋内守卫时,也可以攻击近处敌人,并降低附近建筑承受的伤害。
这里有一个重要原则:鼠标操作、键盘劳动和自动托管最终调用同一套采集、维修与建造结算函数。否则三条入口很容易出现资源扣除不一致,或者自动托管能绕过玩家限制的问题。
本地存档:先验证,再恢复
游戏每 15 秒自动保存,并在页面离开时补存一次。存档包含资源、天气、建筑、家具库存、旋转方向、角色位置和已采集资源。
读取时不会直接相信 localStorage 里的 JSON。validSave 会检查版本、数值范围、建筑类型、坐标、耐久和家具库存。损坏存档会进入兜底流程;旧版缺少新字段时,则从默认状态补齐。
这个设计对持续迭代很重要。AI 辅助开发很容易快速增加字段,如果没有兼容策略,功能更新一次,玩家的家园就可能再也打不开。
移动端不是把桌面缩小

Three.js 画布本身适应屏幕并不难,难的是 HUD。桌面端同时显示角色、任务、天气、资源和建造栏,手机上如果原样压缩,场景会被面板盖住。
移动端做了这些取舍:任务面板收起,角色信息缩短,资源栏改为横向紧凑布局,底部目录保持可点击尺寸,天气和暂停状态仍然可见。触屏使用单指平移、双指缩放旋转,关键动作都有屏幕按钮,不能只依赖键盘快捷键。
怎么验证一个"看起来能玩"的 3D 游戏
这类项目不能只看 TypeScript 是否通过。当前版本的验证分为四层:
- 规则单元测试:配方事务、放置边界、存档校验、寻路、庇护、天气和旋转。
- 类型检查与生产构建:避免开发服务器掩盖静态导出问题。
- 浏览器行为测试:实际点击建造、采集、旋转、制作、拆除、存档和刷新恢复。
- 视觉与像素检查:桌面、390×844、360×740 三种尺寸检查画布非空、控件边界、横向溢出和动画变化。
项目目前有 18 项规则单元测试。图形回归脚本还会检查家具是否真实出现在画布中、雷雨亮度是否稳定、河水是否在运动、移动端按钮是否越界。
GPT-Astra 在这一环节的价值很直接:它不只修改代码,还能运行验证、读取失败结果、回到具体模块继续修正。没有这一步,所谓 AI 编程很容易停在"页面能打开"。
我实际采用的提示词思路
提示词不需要写得像需求招标书,但要把目标、边界和验收方式说清楚。下面这些模板可以直接作为 GPT-Astra + Three.js 项目的起点。
1. 从零搭建可玩闭环
请用 React、TypeScript 和 Three.js 实现一款浏览器单人 3D 建造与防守游戏。
第一阶段只做可玩闭环:
- 一个可平移、旋转、缩放的低多边形 3D 场景;
- 木材、石料和家园生命值;
- 建造、采集、维修、拆除;
- 怪物接近并攻击建筑,家园生命值归零后结束;
- localStorage 本地存档。
业务规则与 Three.js 渲染分开。不要用静态图片或假交互代替 3D 场景。
完成后运行单元测试、TypeScript 检查和生产构建,并在桌面与手机尺寸下实际操作验证。这段提示词最重要的是限定"第一阶段"。如果一开始同时要求联网、多人、任务系统和几十种建筑,模型很容易生成大量代码,却没有稳定的核心循环。
2. 建立程序化低多边形场景
在现有 Three.js 游戏中建立统一的低多边形视觉系统。
要求:
- 使用 BoxGeometry、ConeGeometry、CylinderGeometry 等基础几何体组合资产;
- 树木、岩石、房屋和家具保持一致的比例、色彩与 flatShading 风格;
- 相同材质复用,适合时合并静态几何体;
- 保留投影、雾、昼夜光照和可辨认的物体轮廓;
- 不引入远程模型依赖。
先阅读现有资产生成函数,再沿用项目已有的辅助函数和命名方式。修改后检查画布非空、阴影正常,并截图验证桌面和移动端构图。这里不要只说"做得更精美"。比例、材质、性能和验证方式越具体,输出越稳定。
3. 实现鼠标建造
为现有 Three.js 场景实现网格建造交互。
交互规则:
- 点击目录中的建筑或家具后,才激活待放置状态;
- 用 Raycaster 将鼠标投射到地面,吸附到整数网格;
- 绿色预览表示可放置,红色表示冲突;
- 成功放置一次后立即退出待放置状态;
- 未选择待放置物品时,点击已部署物件将它旋转 90 度;
- 点击空地不执行操作;
- 普通滚轮缩放镜头,Shift + 滚轮旋转待建物品。
放置前必须检查边界、资源、人物占位、重复建筑、屋顶支撑和家具通道。不要让 UI 状态成为规则的唯一来源,规则应放在可测试的模型层。这是本项目迭代次数最多的一类提示词。把"什么时候跟随鼠标"和"点击已建物件做什么"写清楚,可以避免建造、选择和镜头操作互相抢事件。
4. 增加角色和 A* 寻路
在现有网格建造游戏中加入一个可见的 3D 居民角色,并使用成熟的 A* 库寻路。
要求:
- 点击地面后,角色走到对应可达格子;
- 墙、河流、资源、核心和实体家具不可穿越;
- 采集、维修和建造目标可能不可站立,角色要走到目标相邻格;
- 路径变化或被阻挡时重新规划,不能穿墙或瞬移;
- 行走、挥锤、挥斧、受天气影响的动作由角色状态驱动;
- 手动输入应立即中止自动任务并接管角色。
优先复用现有 Block 数据和网格判定,不要维护第二套互相冲突的碰撞世界。为绕墙、目标邻接、不可达目标和家具阻挡增加测试。5. 增加本地自动托管
为居民增加完全本地运行的规则托管,不调用在线大模型。
提供三个目标:发展家园、持续采集、维修巡查。
托管必须真实寻路和执行劳动,完成动作后再扣除或增加资源。
优先级:
- 雷雨或怪物来袭时寻找安全屋;
- 夜间进入屋内休息;
- 有受损建筑时按目标决定是否维修;
- 缺少材料时采集;
- 条件满足后再执行预定建造计划。
安全屋必须由闭合墙体、门、屋顶和可达入口共同判定。路线被阻断时显示原因并等待重算,不要传送。玩家移动或点击场景时立即退出托管。明确"本地规则"很有必要。否则文章里说的 AI 开发协作,很容易和游戏内 AI 行为混为一谈。
6. 定位雷雨闪烁问题
当前 Three.js 游戏在雷雨天气出现整屏明暗闪烁。请先复现并定位所有会写入太阳光、环境光、背景色、雾和曝光的代码路径,不要直接猜测 CSS 问题。
修复目标:
- 每个视觉属性每帧只有一个明确的写入来源;
- 天气和昼夜只计算目标值,再由统一光照更新函数平滑插值;
- 暂停、切换天气和读取存档时不发生亮度跳变;
- 保留雨滴和强风效果,不需要强烈整屏闪电。
修复后固定时间步运行至少 720 帧,记录太阳光强度的最小值和最大值,并分别检查桌面与手机画布。这类排错提示词要先要求"列出所有写入源"。如果只让模型"修一下闪烁",它很可能在错误位置增加延时或降低透明度。
7. 检查存档兼容性
请审查本地存档的读取、验证和升级流程。
要求:
- 不直接信任 localStorage 中解析出的对象;
- 验证版本、资源、建筑类型、坐标、耐久、旋转和家具库存;
- 缺失的新字段从默认状态补齐;
- 非法值、Infinity、NaN、负库存和未知类型必须拒绝;
- 损坏存档进入新游戏兜底,不能让页面崩溃;
- 保持已有存档版本可读取,不清除用户数据。
请为合法旧存档、缺字段存档和多种损坏数据增加单元测试,再运行生产构建。8. 做一次完整浏览器回归
请对当前游戏做端到端浏览器回归,不要只检查页面能否打开。
至少验证:
- 选择建筑后出现预览,放置一次后预览消失;
- 点击已建建筑旋转 90 度,资源、耐久和数量不变;
- 采集、维修、拆除和家具制作的资源结算正确;
- 保存后刷新,建筑、库存、角色位置和方向恢复;
- 小雨、雷雨、夜间和怪物来袭状态能正常运行;
- 桌面 1440×960、手机 390×844、小屏手机 360×740 无横向溢出和控件重叠;
- Three.js 画布非空,关键物件有可见像素变化,控制台无错误。
发现问题后直接修复并重新执行受影响的验证,最后列出已通过项目和剩余边界。9. 用截图反馈做小步迭代
日常调整不必重复整份需求。更常用的是这种短提示词:
查看我在浏览器中标记的区域。当前建筑被选中后一直跟随鼠标,容易误操作。
请先阅读现有建造状态和指针事件,再改成:点击目录物品后只放置一次;成功放置后取消跟随;没有待放置物品时,点击已建建筑旋转该建筑。保留现有镜头操作、存档兼容和移动端行为。
完成后运行相关测试、类型检查和生产构建,并在当前浏览器实际点击验证。不要修改无关界面。这类提示词的结构很固定:指出观察到的问题,描述期望行为,列出不能破坏的边界,最后要求验证。它比"优化一下交互"有效得多。
GPT-Astra 与 Three.js 结合后的结论
做完《林间筑梦》后,对这套组合的判断很明确:Three.js 负责提供一个可计算、可交互的实时世界,GPT-Astra 则负责在这个世界逐渐变复杂时,帮助开发者持续理解、修改和验证它。
Three.js 很适合 AI 辅助开发。场景、相机、光源、材质、几何体和动画都有清晰的代码表达,视觉结果也能通过浏览器立即观察。GPT-Astra 修改一个材质参数、碰撞规则或指针事件后,可以直接运行项目并查看结果。代码变化与画面反馈之间的距离很短,迭代速度自然会快。
但这套组合真正有价值的地方,并不是"模型会写 Three.js"。单独生成一棵树、一间房或一段旋转动画并不难,难的是项目扩大以后,建造、寻路、天气、家具、存档和界面开始互相影响。GPT-Astra 的优势主要体现在下面几个方面。
1. 能沿着完整调用链理解问题
"建筑一直跟随鼠标"表面上是一个画面问题,实际牵涉 React 中的工具选择、引擎里的建造状态、Raycaster 指针事件、预览 Mesh 的显隐以及成功放置后的状态清理。
GPT-Astra 可以先阅读这些调用关系,再决定状态应该放在哪里,而不是只在界面上临时隐藏模型。对 Three.js 项目来说,这一点很重要,因为许多视觉异常的根因并不在渲染层。
2. 擅长处理跨模块的一致性
一次旋转规则调整,会影响新建建筑、已部署建筑、家具、快捷键、鼠标滚轮、存档恢复和界面角度显示。GPT-Astra 可以同时检查这些入口,让它们最终调用同一套规则。
这种跨模块一致性比单段代码生成更有价值。游戏最容易出现的问题,往往是同一件事在不同入口下表现不同,例如鼠标建造会扣材料,而自动托管忘了扣;新物件只能四向旋转,旧存档却恢复成任意角度。
3. 可以把自然语言反馈落成状态机
用户通常不会描述内部实现,只会说"不要一直跟随鼠标""点击已经部署的建筑就旋转""雷雨时画面太闪"。GPT-Astra 能把这类反馈翻译成明确状态:是否处于待放置状态、点击目标是什么、哪一个模块拥有光照写入权。
这让非技术反馈可以更快进入代码,但前提是需求必须落到可验证的行为。比如"优化旋转"过于模糊,"未选择物品时点击已建建筑右转 90°,资源和耐久不变"就可以直接实现和测试。
4. 能完成从修改到验证的闭环
GPT-Astra 的明显优势是可以继续往后走:修改代码后运行单元测试、类型检查和生产构建,再打开浏览器实际点击,检查桌面与手机布局。如果验证失败,它还能根据结果回到对应模块继续修正。
Three.js 项目尤其需要这个闭环。类型正确只能说明代码可以编译,不能说明相机里真的看得到物体,也不能说明雨滴没有穿过屋顶、家具没有挡住入口、按钮没有遮住画布。把浏览器结果重新交给模型判断,比停在"构建成功"可靠得多。
5. 适合维护快速增长的上下文
游戏功能增加后,开发者需要同时记住资源规则、建筑占位、人物碰撞、天气状态和旧存档兼容。GPT-Astra 能从项目文件、状态文档、测试和当前浏览器画面中恢复上下文,减少每次修改前重新梳理整个项目的成本。
这不意味着上下文可以无限膨胀。项目仍然需要清楚的模块边界、可信的状态文档和自动化测试。代码越混乱,模型越容易在局部修复时破坏其他行为。
这套组合的边界
GPT-Astra 不应该接管游戏运行时的每个决定。《林间筑梦》的寻路、天气和托管都使用本地规则,因为它们需要低延迟、可预测、可测试,也不能依赖网络状态。GPT-Astra 更适合出现在开发环节,帮助设计和维护这些规则。
它也不能代替产品判断。任意角度旋转在技术上完全可行,但是否适合格子建造,仍然要看实际操作。模型可以实现多个方案并提供验证证据,最终取舍仍来自玩家反馈和对游戏节奏的判断。
所以,对 GPT-Astra + Three.js 的最终结论不是"一句话就能生成 3D 游戏",而是:它把个人开发者能够管理的项目复杂度向前推了一大步。Three.js 给出实时、透明、可编程的 3D 底座,GPT-Astra 负责理解不断增长的工程上下文,把自然语言反馈落实到多个模块,并坚持完成测试和浏览器回归。
当需求边界清楚、规则层与渲染层分离、验证手段完整时,这套组合可以把一个模糊的游戏想法持续推进到可玩、可存档、可维护的版本。它最强的地方不是第一次生成,而是第十次修改之后,项目仍然能继续工作。
项目技术栈:React、TypeScript、Three.js、OrbitControls、javascript-astar、本地存档 运行方式:浏览器实时渲染,无远程图片和模型依赖 开发协作:GPT-Astra 参与需求拆解、代码修改、测试与浏览器回归