先说结论:标题里的“GPT 6.1”,说白了不是我手上真有个官方发布的 6.1 版本,而是我给当时正在用的那套大模型辅助编程工作流起的内部代号。我用它从零写出了一个能在浏览器里直接运行、致敬经典《红色警戒》玩法的即时战略游戏,并把完整源码开源到了 GitHub。目前跑起来已经很稳:单位能寻路、采矿车会回基地倒矿、敌方 AI 会主动派兵来抄家、框选和编队也都正常。说实话,第一次看到自己“写”出来的游戏能玩,那种感觉真的很微妙。
这篇文我会把整个项目的来龙去脉、技术设计、提示词方法、踩坑记录、开源发布流程都摊开讲。适合三类人看:想用 AI 辅助做游戏但又不知道从哪下手的开发者、纯想了解 RTS 游戏核心系统怎么实现的玩家、以及准备发布自己第一个开源项目但卡在 License 和文档上的新手。内容不算短,但每一步都能直接照抄。
1. 项目缘起:为什么非要用 AI 做一个“红警”
1.1 一切的起点:从一句玩笑到立项
事情源于一个很普通的晚上。朋友发消息说,现在 AI 写代码这么厉害,能不能让它写个红警出来。我第一反应是“不太可能”,因为 RTS 游戏的复杂度和俄罗斯方块完全是两个物种。RTS 里至少有四套核心系统在同时运转:地图与视野、单位状态与命令、资源经济循环、敌方 AI 决策。每套系统单独拿出来都是能写几千行的东西,更别说凑在一起还要保证帧率。
但那天我恰好刚换了一套新的 AI 辅助开发环境,就想着测一测它到底行不行。于是随口定了个目标:一个周末内,能用网页打开,能框选单位,能采矿,能造坦克,能派兵去把对面基地推掉。当时觉得大概率做不成,但真做起来之后发现,事情比想象中靠谱得多。项目最终花了两天半完成,比我手动写过的最快纪录快了一个数量级。
1.2 为什么偏偏是“红警”
选择复刻红警而不是其他品类,其实有几个技术上的理由。第一,RTS 的玩法循环非常清晰:采矿获得资源,资源用于建造单位,单位用于扩张和进攻。这个循环对 AI 理解和拆分任务都很友好,不像开放世界 RPG 那样充满状态分支。第二,我对这个品类足够熟,知道哪些功能可以砍、哪些功能不能砍。这非常重要,因为 AI 生成代码时最怕目标模糊。
很多人低估了这一点。当我跟 AI 说“做一个即时战略游戏”时,它可能生成一个逻辑离谱的玩具。但我如果告诉它“请实现以下回合制之外的特性列表:可框选单位、右键下达移动指令、采矿车自动回基地倾倒、敌方 AI 以时间波次出兵”,它就能一个模块一个模块地完成。目标越具体,模型输出的代码相关性越高。这个项目能成,一半功劳在任务拆解。
1.3 技术选型:为什么是 Web 而不是 Unity
刚开始我也认真考虑过 Unity 或 Godot,毕竟做游戏引擎更专业。但最后选择了 Web 技术栈:TypeScript + HTML5 Canvas + 极简自建渲染循环,不使用任何框架。原因很简单:AI 对这种无框架的项目生成质量最高,因为它在训练数据里见过无数次类似的 Canvas 游戏实现;没有外部依赖,也方便开源分享,别人 clone 下来 npx 就能跑;再加上浏览器本身就是跨平台分发渠道。
我列过一张对比表,可以直观看到当时的思考:
| 方向 | 上手难度 | AI 生成质量 | 开源分享成本 | 可达性 |
|---|---|---|---|---|
| Unity + C# | 中高 | 一般,代码与 Editor 环境强耦合 | 需要导入工程、装引擎 | 低,对手游玩家的浏览器不友好 |
| Godot + GDScript | 中 | 一般,版本更新快容易歧义 | 需要装引擎 | 低 |
| Web + TS/Canvas | 低 | 高,训练语料足够多 | git clone 后 npm install 即可 | 高,打开网页就能玩 |
| 原生桌面(C++/Rust) | 高 | 低,AI 容易生成编译不过的代码 | 跨平台成本高 | 低 |
最后选了 Web 这一列。这里有一个很关键的经验是:不是先想“我想用哪个引擎”,而是先想“AI 在哪个路线最能稳定发挥”。工具链越标准、依赖越少,模型生成的代码越不容易跑偏。这个原则放到今天依然有效。
2. 整体设计与架构拆解
2.1 核心模块:地图、单位、AI、UI 四大块
RTS 看起来复杂,但往细了拆,无非是四个模块在互相配合。我在设计阶段就明确告诉 AI:“程序采用纯前端模式,没有后端服务器,所有状态保存在内存对象里。”
- 地图模块:负责网格、地形、矿点分布,以及像素坐标与网格坐标的互相换算。早期版本直接用二维数组,一行代码就是一块格子,比如
0表示空地,1表示障碍物,2表示矿点。 - 单位模块:负责每个单位的类型、属性、状态机、命令队列。坦克、步兵、采矿车全都统一到一个接口,只是在属性数值上做区分。
- AI 模块:负责敌方阵营的资源积累、出兵决策、目标选择。我不需要它像真正的神经网络那样“学会”玩 RTS,只要能按照规则自动生产单位并攻击玩家基地就够。
- UI 模块:负责地图渲染、血条绘制、框选框、制造菜单、小地图。这是最容易被 AI 忽略的部分,因为它不是“玩法逻辑”,而是“视觉反馈”。
每个模块之间尽量减少直接调用。地图只提供查询接口,单位只上报坐标和状态,AI 通过一个简单的事件总线订阅单位生成和死亡事件。这样的设计要求 AI 写代码时很好理解:所有通信都走一个叫做EventBus的全局单例,谁要发消息就emit,谁要听消息就on。模型对这种发布-订阅模式太熟悉了,几乎不大会生成偏差代码。
2.2 核心数据结构设计
数据结构是整个项目的定海神针。数据结构一旦确定,后续所有功能都是填空题。游戏里的核心类型我用 TypeScript 定义,比如:
type UnitType = 'soldier' | 'tank' | 'miner' | 'base'; interface Unit { id: number; type: UnitType; owner: 0 | 1; // 0 玩家,1 敌方 x: number; y: number; hp: number; maxHp: number; speed: number; state: 'idle' | 'moving' | 'attacking' | 'mining' | 'returning'; targetId?: number; commandQueue: Command[]; } interface Command { kind: 'move' | 'attack' | 'mine' | 'build'; tx: number; ty: number; targetId?: number; }这里有个很重要的设计决策:单位不能只有“当前状态”,还必须有一个commandQueue命令队列。因为 RTS 里玩家经常右键一连串点好几个地方,单位应该按照顺序依次执行,而不是每次只执行最后一个命令。AI 一开始没有生成队列,只有单个 target 坐标,导致单位总会忘记中间路线。后来我在提示词里明确加了“维护一个 FIFO 命令队列”这个约束,代码立刻就正常了。
数据结构还有一个隐蔽的好处:方便 AI 在处理大量重复逻辑时保持一致。例如移动、攻击、采矿都只需要读取unit.x和unit.y,然后根据state决定下一帧动作。模型见过的模式越多,写出来的 bug 就越少。
2.3 为什么这套架构适合 AI 辅助开发
很多人以为 AI 辅助开发等于“一句话让 AI 写出整个软件”,但我的实际经验是,最适合 AI 发挥的代码,往往是接口稳定、模块边界清晰、单个函数功能单一的代码。这也是为什么我在项目一开始就花了一个小时做着架构设计,看起来有点浪费时间,实际上这往往是整个项目里最省钱的一步。
举个例子:在写寻路逻辑之前,我先定义了findPath(startX, startY, endX, endY): Point[]这样一个函数签名。AI 只需要填充 A* 算法即可,不需要去了解地图是怎么渲染的、单位是怎么移动的。反过来也一样,移动模块只需要调用findPath,不用关心寻路内部用什么数据结构。这种接口隔离让 AI 生成代码时上下文窗口不会被无关逻辑塞满,生成结果的质量稳定很多。
如果一上来就让 AI “直接生成一个完整可玩的游戏”,它往往会把所有逻辑揉在一个Game.ts文件里,画布渲染、单位更新、碰撞检测、AI 决策全挤在一起,光是在 2000 行代码里找 bug 就能让人崩溃。模块化并不是为了代码美学,而是为了让 AI 能真正“分得清自己在干什么”。
3. 用 GPT 6.1 写代码的实操过程
3.1 提示词该怎么写才有谱
这是我整个项目里最想分享的部分。很多人用 AI 写游戏失败,不是因为 AI 能力差,而是提示词写得模糊。我的方法是按照“背景、目标、输入、输出、约束、示例”六要素来描述需求。用一个我实际用过的提示词来举例:
我正在做一款网页 RTS 游戏。请实现 A* 寻路函数。
输入:二维网格 grid,其中 0 表示可通行,1 表示障碍;起点 sx, sy;终点 ex, ey。 输出:返回一个数组,包含从起点到终点的路径点(包括起点和终点,不包括终点前一格也可),如果找不到路径就返回空数组。 约束:请使用 JavaScript 实现,不要引入第三方库;使用四方向移动即可,不需要对角线;为了性能,请在函数旁边附上启发函数用到了曼哈顿距离的注释。 示例:grid 是 3x3 全 0,起点(0,0),终点(2,2),应当返回至少包含三个点的路径数组。
这条提示词里,最关键的两个部分分别是“输出”和“约束”。“输出”定义让 AI 知道函数签名;“约束”定义摆平了后续优化需求。公告语气上也要明确,不要写“帮我写个寻路”,而要写“请实现满足以下签名的函数”。模型对代码任务的理解深度,很大程度上取决于任务描述的工程完整度。
如果任务比较大,就拆成多个提示词来写。我一般会让 AI 先写地图类和单位类,通过编译再写移动逻辑,最后才写 AI 行为。整个过程大概拆成了 40 多次会话,每次会话只关注一个小功能。
3.2 从零到可玩:四个阶段
整个开发过程我把它切成了四个阶段,每个阶段都设置了一个可验证的里程碑:
第一阶段是“地图能画出来”。让 AI 生成一个 Canvas 渲染循环,把二维数组转成格子,格子上有颜色区分地面和矿点。能跑通这一步,说明渲染链路正常。第二阶段是“单位能动起来”。点击造一辆坦克,右键点击让坦克走到目标坐标,并且能走出直线或绕开简单障碍。这个阶段最容易暴露的是寻路坐标和渲染坐标的换算问题。第三阶段是“经济循环转起来”。采矿车自动寻找最近的矿点、采完自动回基地、倒入资源后再次出发。这阶段主要调状态机。第四阶段是“敌方会打过来”。AI 阵营定时累积资源、自动出兵、选择攻击目标。
每个阶段结束后的验证方式很粗暴:在浏览器里手动点一遍。不通过就继续丢给 AI 修。因为每个阶段的功能足够小,AI 改进时无需重写大量代码,这保证了整体进度没有失控。
这里要补充一个重要的实操心得:每一轮让 AI 改代码后,我都会把改动后的文件单独存一份快照,确认这版能跑了再继续往下走。这个习惯很多人没有,结果 AI 某一次给你的代码出现回归,你想回退都找不到节点。版本控制不只是开源社区的习惯,也是 AI 辅助开发的保命工具。
3.3 AI 写错代码怎么办
说句公道话,AI 在这个项目里也犯了不少错。最常见的三种错误,我都碰到过:
第一种是“想当然的实现”。我在让 AI 实现采矿车自动寻找矿点时,它直接让采矿车走到矿点后执行state = 'mining',但我没说明采矿需要耗时等待。结果画面里,矿车一接触矿点就瞬间多了一大笔钱,完全没有采掘动画和延迟。解决办法是补充需求,明确采矿是一个持续 3 秒的过程,期间矿车保持原地且资源每秒加 1。
第二种是“两个模块之间的数据格式对不上”。地图模块用网格坐标表示位置,单位模块用像素坐标表示位置,AI 生成出错的代码里经常把两者混用。我最后直接在提示词里规定“游戏世界使用网格坐标作为唯一标准,渲染时才进行像素转换”,这一问题才彻底消失。
第三种是“性能灾难”。AI 默认会在每帧更新里遍历所有单位、每个单位里遍历整个寻路数组,数量一多就掉帧。这种问题只能靠代码评审和性能调优去解决,不能被动的指望 AI 自己优化。
我的建议是:把 AI 当成一个可以快速试错的高级工程师,而不是神。写错了就让它在反馈里重新读代码,或者直接指出错误类型“单位重叠了,请检查 move 函数中碰撞检测顺序”,效果往往不错。不要动不动就重新生成整个文件,那会让模型丢失上下文。
4. 关键功能实现与效果
4.1 把游戏玩法跑起来的细节
游戏可玩的核心是由“采矿-建造-进攻”三个环节构成的闭环。我来说几个具体实现过程,这些细节直接决定了“能不能真玩”。
采矿逻辑比较简单。玩家在开局拥有一座基地车和一个采矿车。当我右键点击一个矿点时,系统先判断单位类型,如果是矿车,就生成mine命令。寻路成功后,矿车移动到矿点格,然后进入mining状态,持续 3 秒,每隔 1 秒增加 1 点资源。采矿结束后,矿车会自动生成一条返回基地的路径,在到基地之前,它不需要玩家再点。状态机一旦设计好,这个循环基本不会出错。
建造逻辑是通过建筑面板完成的。玩家点击“兵营”按钮,系统会检查资源是否足够,足够则在基地附近找一个空地生成建筑。建筑生成后可以生产坦克和步兵。这里有一个很容易被忽略的点:生产单位必须设置出生点,并且出生点不能和其他单位重叠。我用一个简单的“出生点候选列表”解决:在建筑周围以螺旋方式搜索最近的空地,找到后生成单位。AI 最开始生成的是固定坐标出生点,结果可生产单位一多,出生点重叠会叠出一堆残影。
敌方 AI 我采用的是基于事件和定时器的决策系统:每 20 秒尝试生产一个单位,每 40 秒发起一次小规模攻击波次,攻击波次的目标是玩家建筑列表中坐标最靠前的建筑。AI 本身不考虑逃跑、侦察等复杂行为,但它能保证每次进攻都有来有回。如果玩家被推平,界面会显示失败;如果推平敌方基地,界面显示胜利。胜利条件足够了,再多的花活都是后期加功能的事。
4.2 性能优化:让它在老笔记本上跑起来
完整项目跑起来之后,我在自己的老笔记本(8G 内存、集显)上测试,开场 10 分钟后就掉到 20 帧,问题主要集中在三处:影子算法太笨重、单位渲染全部实时重绘、AI 决策每帧都全量扫描地图。
优化第一招是对象池。单位死亡后不再删除对象,而是把状态重置到“待复用”列表,新单位直接复用旧实例。这样避免了频繁的垃圾回收和对象分配,单位数量从 200 提升到 600 帧率不崩。第二招是分帧处理寻路计算。寻路是重计算,我让每帧最多只做 3 次寻路计算,多出来的放入队列下一帧处理。玩家基本感觉不到延迟,但 CPU 峰值降了一半以上。第三招是脏矩形重绘:把画布拆成若干个区块,只有内容变化的区块才重新绘制,静态地图省掉了大量 drawRect 开销。
这三个优化做完,同一台老笔记本上同场景帧率从 20 帧提升到 58 帧左右,基本达到了“能玩”的标准。性能优化是个无底洞,但对一个开源 demo 来说,能维持稳定帧率就是胜利,剩下的精力要留给功能和文档。
4.3 开源发布:License、README 与社区反馈
代码写完了,不发出来就失去了这个项目一半的意义。开源发布这件事,我认为最需要认真对待的其实是 License 的选择,而不是注册账号和 git push 这种操作。我在 Gitee 和 GitHub 上都发布过项目,这里说一下实际体验:GitHub 国际用户多更适合让项目被看到,Gitee 对国内网络环境中下载代码方便,两个平台都推可以。
License 选型我列过一张速查表,这次也放出来给大家参考:
| License | 能商用吗 | 修改后是否必须开源 | 适合场景 |
|---|---|---|---|
| MIT | 可以 | 不强制 | 希望代码被最大化复用的个人项目 |
| Apache 2.0 | 可以 | 不强制 | 希望带专利保护和贡献者规范的项目 |
| GPL 3.0 | 可以 | 必须开源 | 希望代码永远保持开源的社区项目 |
| BSD | 可以 | 不强制 | 和 MIT 类似,但文案稍严谨 |
我这个项目用的是 MIT,因为我的目标很明确:让喜欢 RTS 的开发者拿代码随便改,也可以作为 AI 辅助开发的教学样例。如果你在意别人拿你的项目做闭源商用,那应该考虑 GPL;如果你特别在意代码署名,那 Apache 2.0 会更明确。开源不是为了给自己找麻烦,而是想清楚“你把代码交出去之后,希望它怎么被使用”。
仓库结构也是一个能影响别人是否愿意试玩的因素。我最终的目录是这样组织的:
root ├── src/ │ ├── core/ // 事件总线、地图数据、单位接口 │ ├── systems/ // 移动系统、寻路系统、AI系统、经济系统 │ ├── ui/ // 画布渲染、小地图、建造菜单 │ └── main.ts // 入口文件 ├── index.html ├── package.json ├── README.md └── LICENSEREADME 里除了标题,我还放了一张运行截图、一行启动命令、以及一段“玩法说明”和“已知问题”。实测下来,放截图会让试玩率提升非常明显,因为玩家看到截图才知道点哪里能造兵。开源不只是一份代码,更是一份让别人能快速上手的“说明文档”。
项目开源一周后,GitHub 收到了几个 issue,有人反馈在手机浏览器上布局错乱,有人问能不能加双人局域网对战。这些都是很正常的反馈,我处理方式是先把已知问题写在 README 里,再把最紧急的布局 bug 修掉。这比回复每一个问题更高效。
5. 常见问题与排障实录
5.1 单位卡墙和寻路不收敛
这个项目里最让我头疼的问题就是单位走着走着卡在墙角不动。排查后发现原因有好几种:一种是障碍物被标记得太密,寻路四方向在狭窄通道里没有足够空间让单位转身折返;另一种是终点格子本身就是障碍物,终点格上有个建筑,路径计算直接返回空。
解决方案有两层:第一层在寻路算法上,把启发函数权重视为1.0,不鼓励走斜线,也不刻意绕远路;第二层在游戏逻辑上,若目标格被占用,则寻找周围 8 格里最近的可通行格子作为最终目标。这样处理之后,卡墙现象基本绝迹。
还有一个“单位走着走着突然消失”的 bug,排查到最后其实是 AI 在移动结束后把单位坐标设置到了(0,0),导致单位瞬间瞬移到地图左上角。原因是单位reachTarget触发后执行了一段多余逻辑。这类 bug 因为藏在状态机分支里,单靠肉眼很难发现,用 console.log 一支支个单位看状态变化才挖出来。所以我强烈建议,AI 辅助写的游戏一定要在前端加一个调试面板,显示每个单位的 state、x、y、targetId,排障效率直接翻三倍。
5.2 框选和镜头偏移
第二个高频问题来自框选。玩家点击画布框选时,明明框住了三个单位,实际选中的可能只有一个。问题根源在于我用 Cavas 绘制时设置了相机偏移,而框选矩形用的是屏幕坐标,没有转换到世界坐标。当时 AI 生成的代码里没有做worldX = screenX + cameraX这种坐标变换,导致框选区域在滚动画布后完全错位。
修复方式就是在鼠标按下和鼠标移动的坐标里都先做世界坐标转换,再和单位坐标比较。这个 bug 修完,框选功能就从“偶尔灵”变成了“一直灵”。这件事也再次验证了之前的原则:坐标系统一定要在架构阶段就统一。我建议准备开发类似游戏的朋友,第一天就先定一个坐标换算函数,之后所有鼠标交互都走它,不吃亏。
5.3 帧率与内存问题速查
最后整理一个排障速查表,是我在开发过程中总结出来的,应该能帮你省下几小时的疑惑时间:
| 症状 | 可能原因 | 快速排查与解决 |
|---|---|---|
| 单位越多帧率越低 | 每帧全量遍历/对象频繁创建 | 改用对象池,寻路分帧队列 |
| 内存持续上涨 | 事件监听器重复绑定 | 检查 EventBus 的 off 方法是否在单位销毁时调用 |
| 框选偏移 | 相机坐标未转换 | 统一 world/screen 坐标换算函数 |
| 单位卡墙 | 目标格障碍或寻路失败 | 终点格精确到空地,失败时相邻格兜底 |
| 单位重叠 | 缺少出生点/碰撞检测 | 生成时搜一个相邻空地 |
| 游戏卡顿一下后恢复 | 某帧做大量寻路 | 寻路计算分帧,单帧上限 3 次 |
这里有一条很值得说的经验:如果你用 AI 辅助开发,不要等到出问题才去看代码。每次 AI 写入新功能后,顺手把该文件从头到尾读一遍,理解它动了哪些状态。虽然 AI 写代码能把人从重复劳动里解放出来,但“理解自己项目的代码”这件事,它永远替代不了你。调试其中有一次让我抓狂的“每帧都会创建 500 个临时对象”问题,就是因为我在单元测试里没用 eventBus 的清理函数,导致内存只涨不降。这个 bug 几乎影响不了玩法,但一旦玩家玩久了,整个页面最终会卡死。处理起来也不难,销毁单位时调用eventBus.off(unitId)即可。但这类问题如果不看代码,你是根本猜不到罪魁祸首在事件订阅里的。
最后再分享一点个人体会
这个项目前后只花了一个周末,但它带给我的收获比我之前半年手动写的个人项目都要大。它让我真正理解了“任务拆解”对 AI 辅助开发的极端重要性:清晰的数据结构接口决定了 AI 的上限,精细的分阶段验证决定了项目的下限。如果你想复刻这个项目,我实际建议的顺序是:先照着这篇文里的数据结构写一张纸的设计图,再拿给 AI 逐模块实现。不要跳着让 AI 一次写太多,不要略过手动验证,更不要忘了给每个阶段做一个可跑的小 demo。
现在这个项目还躺在我的 GitHub 上,PR 和 issue 我都会看。后续我打算扩展的方向包括:局域网对战、战役模式、单位技能系统,以及把存档系统做成纯本地的 localStorage 版本。如果你也在用 AI 做游戏,欢迎把你的踩坑记录发出来,这些真实的失败经验比任何教程都值钱。
祝你的第一个 AI 辅助游戏也早日跑起来。如果中途遇到卡墙、掉帧、框选失灵这一类问题,别慌,往架构和坐标换算的方向查,大概率能解决。