ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用GPT写了个红警:AI辅助开发RTS游戏完整实战记录

用GPT写了个红警:AI辅助开发RTS游戏完整实战记录 1. 项目缘起周末突发奇想用 AI 写了个红警还能玩1.1 这个红警到底是什么先解释一个事情免得有人点进来以为是汉化复刻了当年那款经典RTS。我这个项目的全名其实是浏览器里能跑的最小红色警戒风格RTS它不是一个完整复刻而是把红警最核心的那个循环做出来了造电厂攒电力、造兵营、出动员兵和灰熊坦克、派矿车去采矿石、拉回精炼厂换钱、再爆更多兵然后去推掉电脑的老家。听起来很复杂对吧实际上用 GPT 6.1 辅助写出来的核心代码只有三千多行跑在浏览器里打开一个网页就能玩而且我已经把整个项目开源了仓库地址在这个系列的内容里我会从头讲清楚。为什么非要做红警而不是做个塔防或者贪吃蛇因为我一直觉得RTS游戏是检验游戏开发基本功的最佳试金石。它要求你在一个实时运行的循环里同时管理资源、单位状态、寻路、战斗判定、AI策略而且画面上有几十个运动中的物体每一帧的状态都必须是一致的。这些恰好都是 AI 辅助编程最容易暴露短板的地方AI 生成代码擅长单一模块但要把多个模块在同一个实时循环里协调起来不打架就需要开发者自己心里有一张完整的架构图。1.2 方案选型为什么选了 Web 技术栈做这个项目之前我认真想了一下技术栈。第一反应是用 Godot 或者 Unity毕竟它们是成熟的游戏引擎自带场景树和物理碰撞。但我最终决定用纯 Web 技术栈Vite TypeScript Canvas 2D不上任何游戏引擎。理由是这么几条一是我想验证一个假设就是 AI 辅助编程能不能在没有任何游戏引擎能力加持的前提下凭空生成一个可玩的 RTS 核心这样更能测出 GPT 的真实水平二是浏览器天然跨平台开源之后别人克隆下来npm install npm run dev就能跑门槛最低三是 Canvas 2D 虽然底层面一点但所有绘制、碰撞、性能优化都得自己控制这个过程对想学习游戏开发的读者来说更有参考价值。实际做完之后我的评价是如果只是做演示性质的小游戏纯 Canvas 完全够用甚至比上引擎更灵活。RTS 对渲染的要求其实没那么极致单位就是几十个圆形或者矩形加一层阴影地图是格子拼起来的不涉及光照、骨骼动画这些复杂管线。真正难的是逻辑层的复杂度不是渲染层的复杂度。把精力省下来去调寻路和战斗逻辑才是这个项目最值钱的部分。2. 核心技术点拆解RTS 游戏必须迈过的四道坎2.1 等距视角渲染把二维游戏伪装成 2.5D红警那种视角严格说叫等距视角但当年的实现方式并不需要真正的 3D 投影。核心就是把二维的世界坐标转换到屏幕坐标让画面看起来像俯视 45 度。公式其实很简单屏幕 x 等于 (worldX - worldY) 乘以一个系数屏幕 y 等于 (worldX worldY) 乘以另一个系数。我用的系数是 0.5这样单位移动和建筑摆放都有那种斜着走的节奏感。这里有个特别容易踩的坑图层顺序。等距视角下一个单位如果站在建筑的上方世界坐标中 y 更小它应该被建筑遮挡如果站在下方则要画在建筑前面。初学者往往会按绘制顺序想觉得先画背景再画建筑再画单位就行了但你在等距视角里让一个单位从建筑后面绕到前面时每一帧都要重新计算建筑和单位的遮挡关系。我的方案是把所有可遮挡物体建筑、树、单位统一收集到一个数组里按世界坐标的 y 值排序之后从后往前画。这个逻辑最初是让 GPT 写的但它一开始把排序维度写成了屏幕坐标 y导致单位从建筑旁边走过时出现明显的前后抖动我在调试时花了半个小时才定位。// 等距坐标转换注意半格偏移 function worldToScreen(x: number, y: number) { return { sx: (x - y) * TILE_WIDTH / 2, sy: (x y) * TILE_HEIGHT / 2 } }2.2 单位寻路与碰撞处理不要盲目上大型算法RTS 的寻路是很多人一上来就慌的问题总想着搞一套洪泛寻路或者大规模 BFS。但实际情况是这个微型红警的地图规模是 64x64 的格子同时活跃的单位上限才几十个根本不需要地狱级优化。我选择的是经典 A* 搜索然后加一个非常简单的避让规则。最初 GPT 给的 A* 实现居然没有处理障碍物碰撞的代码单位能直接从精炼厂中间穿过去看着特别出戏。后来我在每个格子上加了一个可走性标记建筑占据的格子全部标记为不可走矿石所在的格子标记为可走但会降低通行速度。矿车去采矿时优先在矿石格子周围找可走格而不是直接碾到矿石格子上面。A* 本身的实现不需要我贴完整代码网上一搜一大把重点是启发函数用曼哈顿距离其实就够了没必要用欧几里得距离因为等距网格下单位移动本身就是四方向的曼哈顿距离算出来的路径反而更自然。单位碰撞的问题更有意思。几十个单位在地图上跑如果没有避让规则它们会在狭窄路口卡成一团。我当时用了个特别土但有效的办法每个单位有一个个人空间半径移动之前检查目标位置周围有没有其他单位如果有就在切线方向上偏移一个角度。这个逻辑让我想起小时候玩红警时坦克挤在一起互相挡路的样子反而有点还原经典了。2.3 单位 AI 状态机别写成面条代码RTS 里的每个单位都像一个小机器人它需要知道自己在干什么、接下来干什么。我设计单位状态时没有用复杂的分层状态机而是最朴素的 switch-case待机、移动、攻击、被攻击反击、集结点移动。每种状态维护一个剩余时间或目标位置每帧更新时先检查当前状态再根据条件决定是否切换。这里最核心的经验是状态切换必须有一个明确的优先级顺序。比如单位被攻击时不管它当前是在移动还是待机都应该立刻切到反击状态。但如果它正在执行玩家下达的攻击指令就不应该因为被打了一下就偏离目标。我的做法是在每个状态的处理函数开头先做一次反击判断优先级高于当前任务。这部分让 GPT 写的时候它总是把反击逻辑放在移动逻辑后面导致单位挨了打还要先走到目标位置再回头反击非常蠢。我后来直接把状态切换的逻辑整体重写了一遍不再让 AI 猜这个顺序。function updateUnit(unit, dt) { if (unit.hp unit.maxHp findEnemyNearby(unit, 120)) { unit.state attacking // 反击优先级最高 } switch (unit.state) { case idle: // 待机逻辑 case moving: // 移动逻辑 case attacking: // 攻击逻辑 case collecting: // 矿车采集逻辑 } }2.4 经济循环矿车、矿石与建造队列的闭环玩过红警的人都知道资源是一切的基础。我的经济系统设计成了一条完整的链路地图上随机分布矿石簇矿车自动驾驶到最近的矿石格停在旁边以每秒 10 块钱的速度采集采满 100 块钱之后自动返回最近的精炼厂卸货之后再次出发。这个循环的触发完全由矿车的状态机驱动不需要全局管理器去调度。这条链路在 AI 辅助开发时遇到了反复出 bug 的地方。首先矿车的状态转换在去采矿和去卸货之间需要一个明确的目标切换逻辑GPT 一开始写成了两个互相独立的函数中间没有任何状态承接矿车到了矿石旁边就开始抖。其次精炼厂在建造完成之前矿车应该处于等待状态而不是径直跑向空地块。我的处理方式是给所有建筑和单位都注册了一个全局事件总线建造完成时广播一个事件矿车收到事件后才把目标精炼厂从 null 换成实际建筑。经济数值的设定也很有讲究。电厂造价 100兵营 200动员兵 20灰熊坦克 100矿石簇每簇 500 块钱地图上放了 20 簇。这个数值平衡我调了很多次一开始矿太密集玩家出兵太快电脑 AI 根本顶不住后来把矿石总量调低又变成了憋半天出不了坦克。最终的平衡原则是玩家在第五分钟时应该能稳定攒出 5 辆坦克这个时候敌方 AI 正好派第一波进攻形成第一次正面冲突。3. 用 GPT 6.1 开发的全过程实录不要让它一口气写完整个游戏3.1 第一步先跑通最小可玩闭环很多人在用 AI 写游戏时会犯一个最典型的错误一次性把需求整个甩给 AI让它生成一个完整的游戏。我负责任地说这个思路必炸。GPT 生成代码有个特点单文件超过四五百行之后内部的逻辑一致性会急剧下降函数之间的调用关系经常对不上变量名前后不一致都是小事更可怕的是它会为了看起来完整而编造一堆并不存在的 API。我用 GPT 6.1 的核心策略是拆成一个个 20 到 50 行的小函数每次只让它实现一个功能我立刻在本地跑通再进入下一个。最小可玩闭环我给 AI 下了第一个指令生成一个 64x64 网格地图用鼠标点击放置一个兵营兵营每 3 秒自动生产一个动员兵动员兵在地图上向随机方向移动。这个任务足够简单GPT 一次就通过了但这给了我一个可以持续验证的框架地图、交互、单位更新、渲染循环四个基本模块全部就位。后续所有的功能都是在这个框架里逐步叠加的。3.2 聚焦对话一次只问一个技术问题细化到具体开发过程我会把整个需求拆成一个个原子任务每个任务都聚焦到一个函数或一个类。比如这一轮任务是「给矿车加一个寻找最近矿石的逻辑」我就只给 GPT 相关的地图数据结构、矿石列表、矿车当前位置然后让它返回一个函数。下一轮任务是「给单位加一个简单的攻击判定」我就告诉它攻击方坐标、攻击范围、目标方坐标让它返回是否命中。这种方式下AI 的出错率会低很多而且即便出错错误范围往往被限制在一个函数内肉眼就能找到。还有一个很实用的技巧每轮对话末尾都把你的核心数据结构定义贴一遍。GPT 有上下文窗口限制聊了几轮之后它会忘记前面的约定比如某个单位对象的属性是hp不是health。与其让它猜不如每轮都带上这样一段interface Unit { id: number x: number y: number hp: number state: idle | moving | attacking | collecting target?: { x: number, y: number } }这段代码塞进提示词里只占几行但能省掉大量因变量名不一致导致的返工。3.3 提示词与代码审查真正值钱的是你的判断力这是我做完整个项目最大的体会AI 写代码的效率再高也不如人类的代码审查值钱。GPT 6.1 能在十几秒内给你写出一个 A* 寻路但它是写出来的不是解决出来的——它不会告诉你这个 A* 的性能边界在哪里不会告诉你当单位数量超过 200 个时会掉帧更不会主动帮你做对象池优化。这些都需要人工去追问、去测量、去决策。我总结了一套自己的提示词三板斧第一板斧是明确输入输出告诉它输入是这些输出必须是这个函数不要给我额外的东西第二板斧是带约束明确算法偏好比如不要用递归用循环实现第三板斧是让它解释让它在代码旁边写注释解释为什么这么做。第三点特别重要因为它的注释本质上是在跟我同步思路我能借此发现它哪些地方理解错了需求。代码审查方面我每让 AI 生成一段代码都会像 review 同事的 PR 一样去读一遍。这不是走形式因为我原来踩过一个大坑GPT 生成的随机移动逻辑里单位每次移动都生成一个新的随机角点结果所有单位看起来像得了癫痫一样疯狂抽搐。我审查时发现它的问题是目标点太近、更新频率太快于是加了一个最小移动距离限制单位每走完一格才尝试生成新目标点现象立刻就正常了。4. 性能和体验优化从能跑到能玩4.1 对象池与合并渲染别把每一帧都当作新生项目最早期的版本单位上限只有 40 个超过这个数量帧率就开始肉眼可见地往下掉。原因有两个一是每次创建单位都new Unit()JS 引擎频繁进行内存分配和回收表现为随机卡顿二是每个单位每一帧都单独调用 Canvas 的绘图 APIdraw call 数量太多。这两个问题叠加起来单位一多浏览器就受不了了。解决的第一个方法是我自己实现的用对象池。提前分配一个长度为 200 的单位数组每个单位有一个存活标记死亡单位不销毁而是把标记置为 false下一次需要新单位时直接复用数组里第一个空位。这几乎是所有游戏开发里最常用的性能优化手段但在 AI 生成的代码里它基本不会出现——因为 AI 是基于语料训练的大部分语料里的代码都是数据量小、性能要求低的业务代码。你需要明确地跟它说用对象池管理单位它才会给你一个像样的实现。渲染优化则是让 GPT 给了我一个离屏 Canvas 方案。静态的地形、矿石、建筑地基先一次性画到一个隐藏的 Canvas 上每一帧只把这张静态缓存图先贴到主画布再在上面叠加绘制动态的单位和建筑。这个方案让每帧的绘制次数从几百次降到了几十次。4.2 性能预算一场仗最多能同时打多少个单位我把优化做完之后做了一个简单的压测往地图上刷 300 个动员兵然后让它们全部朝同一个点移动观察帧率变化。结果是优化前 40 个单位时帧率掉到 40 FPS优化后 200 个单位还能稳定在 55 FPS 以上300 个单位才会开始有明显掉帧。对一个网页小游戏来说这个性能已经完全够用了。这里有一个特别值得推荐的调试思路你要给游戏设定一个明确的性能预算。我最初定的目标是同屏 150 个单位 30 个建筑不卡根据这个目标反推每帧留给逻辑计算的时间不能超过 8 毫秒剩下的留给渲染。有了这个预算优化就有了优先级不会瞎忙。A* 寻路是每帧最贵的逻辑运算所以我给它加了缓存同一帧内多个单位向同一个目标寻路时第一个单位算出路径后面的单位直接复用缓存结果除非地图发生变化。4.3 数据驱动与配置表把游戏数值从代码里剥出来红警之所以好玩很大一部分原因在于数值设计的丰富程度每个兵种的造价、血量、攻击力、移动速度都不一样玩家需要在这些数值之间做取舍。为了让这个项目看起来像个正经 RTS我也给动员兵、灰熊坦克、矿车、电厂、兵营、精炼厂这些对象做了配置表。配置表以 TypeScript 对象的形式放在一个单独的gameConfig.ts里代码里所有单位属性都从配置表读取而不是硬编码。这个设计在 AI 辅助开发时帮了大忙。因为当你想调整数值平衡时只需要改配置文件完全不碰逻辑代码。更重要的是如果你在同一轮对话里让 GPT 参考配置文件去写一个新功能它的输出质量会明显提高——因为它不需要在对话历史里翻找之前埋下的魔法数字。数据驱动、配置优先这条原则应该是 AI 写游戏时代最值得坚持的工程习惯。5. 开源、避坑与给后来者的建议5.1 仓库结构和运行方式项目开源之后的仓库结构非常简单一个src目录下面按功能分成几个文件夹core放地图和坐标转换units放单位状态机和 AI 逻辑buildings放建筑对象ui放建造菜单和资源条ai放敌方 AI 逻辑。入口文件是main.ts负责初始化游戏循环。运行方式就是标准的 Vite 流程npm install之后npm run dev浏览器打开 5173 端口就能看到游戏界面。顶部有资源显示右下角是建造菜单选中单位右键移动、左键点击敌方单位会尝试攻击。开源目的不是让谁来打磨这个游戏而是给想用 AI 做游戏的人一个可以完整解剖的样本。5.2 踩过的最深的坑AI 把同一个需求实现了三遍开发到中途我犯过一个很低级的项目管理错误。因为对话上下文太长我在新会话里让 GPT 重写矿车采集逻辑结果它实现了一套和之前完全不同风格的新代码。更糟糕的是新代码管自己叫collectMineralsV2而旧的函数叫mineLoop两个函数同时存在于代码里没有一个是主入口。这种同一需求被反复实现的问题其实很常见因为每开一个新会话AI 就失去了之前的上下文记忆。我的教训是每个功能模块必须第一时间抽成独立函数文件并且在仓库里维护一个AI-NOTES.md记录每个核心函数的 GPT 版本号和功能描述相当于给人脑做一个外挂记忆。另外一个小坑是事件监听的重复绑定。GPT 生成代码特别喜欢把事件监听写在初始化函数里但如果初始化函数被重复调用监听器就会叠加导致点击一次触发了两次建造。这个 bug 排查起来极其隐蔽因为没有报错只有行为异常。我的排查经验是在监听器回调里加一个 console.log 计数看一次点击会不会打两行日志。这个办法虽然土但特别高效。5.3 给也想用 AI 做游戏的人四条个人经验第一AI 写代码不是替代你思考而是把你的思考速度放大。你得先能画出完整架构图AI 才有意义如果架构不清晰AI 生成的代码只会让你更混乱。第二每让 AI 完成一个功能立刻手动测试不要攒任务到晚上一起验证。AI 报错信息非常有限它的调试能力远弱于生成能力靠它自己找 bug 基本靠运气。第三一定要学会手动拆分需求。AI 适合实现不适合设计。你可以让 AI 写一个 A* 寻路但要自己决定地图上哪些格子该设为障碍。最后也是最重要的一条保存好你的提示词它们本身就是很有价值的资产。我后期几乎建立了一个提示词库每个功能模块对应一组固定的提示词模板新开对话时直接复用效率提升不是一点半点。最后再分享一个小技巧。我在做敌方 AI 时最一开始让 GPT 写了个特别复杂的策略评估兵力、计算优势、决定进攻时机。结果跑起来电脑玩家像在睡大觉半天不出兵。后来我改成最简单粗暴的定时进攻每 90 秒派一波三辆坦克加五个动员兵直接向玩家基地冲。反而因为这个固定的进攻节奏每一局都打得有来有回。游戏 AI 不是越复杂越好明确的、可预测的挑战才是玩家觉得能玩的关键。这一点可能是这个项目里比技术更重要的收获。
返回列表