免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

AI辅助编程实战:我用提示词工程从零复刻《宝可梦红》

AI辅助编程实战:我用提示词工程从零复刻《宝可梦红》 《宝可梦 红》这款 26 年前的游戏我从小玩到大一直想知道把它“拆开”会看到什么。直到 AI 编程工具成熟之后这个好奇心终于有了落地的可能。过去五个月我利用下班和周末时间用 AI 辅助编程的方式从零复刻了《宝可梦 红》的核心玩法、地图探索、战斗系统、宝可梦收集与交换等模块现在项目已经能在浏览器里完整跑通一周目流程。整个过程有惊喜也有翻车踩了不少 AI 编程特有的坑也沉淀出一套跟 AI 协作开发复杂项目的打法。这篇文章会把整个过程按“进展、弯路、收获”三条线完整复盘重点聊聊我怎么拆解需求、怎么写提示词、怎么让 AI 一步步把游戏做出来以及最后项目能跑起来的关键经验。1. 项目整体设计与思路拆解1.1 为什么选《宝可梦红》作为复刻对象选择《宝可梦 红》不是随手一拍脑袋而是有明确的工程考量。这个游戏虽然看起来是简单的像素风 RPG实际内部系统极为复杂地图数据用二维数组存储每个区块有碰撞属性、遇敌概率、NPC 对话事件战斗系统涉及属性克制、种族值、个体值、努力值、经验公式、进化条件还有背包道具、宝可梦交换、PC 存储、商店购买、道馆徽章等一整套状态管理。这种复杂度对 AI 编程来说是最理想的训练场。太简单的项目AI 一遍生成完就没有优化空间学不到协作方法太复杂的项目上下文一长 AI 就懵容易产生幻觉代码。宝可梦的复杂度刚好卡在“能分出十几个独立子系统每个子系统又能单独验证”的粒度上非常适合测试 AI 辅助开发的上限。另一个考虑是历史资料充足。粉丝社群对《宝可梦红》的数据挖掘已经非常完整属性表、种族值、经验曲线、地图布局、事件触发条件都有文档记录。这意味着我可以直接把精确数据喂给 AI而不需要靠它“回忆”游戏细节减少了一类重要的出错源。1.2 技术选型从 Python 原型到 Web 技术栈项目启动时我犹豫过技术路线最终定了 JavaScript Canvas 渲染 本地存储存档的纯前端方案用 Vite 做构建工具。这个选择有几个具体原因复刻对象是 Game Boy 游戏整个画面只有 160×144 像素Canvas 2D 完全够用不需要引入 Phaser 这类重量级框架减少 AI 生成时的知识负担浏览器环境天然适合迭代调试改完代码刷新就能看效果不用处理模拟器环境问题。AI 生成的代码也更容易跑起来存档用 localStorage 就能解决不需要后端。后续做交换功能时可以靠 BroadcastChannel API 实现同浏览器双窗口通信我最初其实先做了个 Python Pyxel 版本因为 Pyxel 的 API 简洁AI 生成代码的准确率会高一些。但后来发现 Pyxel 不方便分享给朋友试玩也没有成熟的方式做移动端适配所以第二个月推倒重来用 TypeScript 重写了一版。这个决定当时看起来很亏实际上节省了后续几个月的返工时间——Web 形态的传播成本几乎是零朋友点开链接就能玩。1.3 模块拆解把大型游戏工程切成 AI 能吃下的小块第一次让 AI 写整个游戏时我给出的提示词是“用 JavaScript 写一个宝可梦红”结果生成的代码基本不能运行。原因不是 AI 笨而是这个请求的上下文空间太大它没法同时把握地图系统、战斗系统、对话系统、道具系统之间的交互关系。后来我把整个项目拆成了 16 个独立模块每个模块控制在 300 到 600 行代码以内分别让 AI 实现地图加载与渲染瓦片数据解析、相机跟随、分层绘制玩家控制四方向移动、碰撞检测、动画状态机NPC 系统对话脚本、移动模式、事件触发战斗系统回合制流程、招式计算、状态变化宝可梦数据种族值、个体值、经验公式、进化链背包与道具分类管理、使用效果、商店交易存档系统序列化、压缩、自动保存标题画面与菜单 UI音效与音乐播放野生宝可梦遭遇草丛遇敌概率、等级分布每个模块独立开发、独立测试通过预定义的接口协议对接。比如地图模块只需要对外暴露getTileAt(x, y)和isWalkable(x, y)两个方法战斗模块根本不需要知道地图怎么渲染只需要在合适的时机调用场景切换函数。这种拆法有两个好处AI 在一个小上下文里生成的代码质量显著提升幻觉率从 40% 降低到 10% 左右出问题时能快速定位是哪个模块的锅避免“全项目搜索 bug”的噩梦。接口协议文档我写在一个INTERFACE.md里每次让 AI 写新模块时都先把这份文档贴到 prompt 里确保各模块之间的契约一致。2. 核心功能实现与关键技术细节2.1 游戏主循环与状态机管理《宝可梦 红》的游戏逻辑本质是一个嵌套状态机全局有标题画面、过场动画、主地图探索、战斗、对话、菜单、存档等状态战斗内部又有战斗开始、玩家指令选择、招式动画、伤害计算、经验结算、升级进化等子状态。如果不用状态机管理代码会迅速变成一锅粥AI 后续维护也会越改越乱。我用一个类管理状态切换核心逻辑只有三四十行class GameStateMachine { private states: Mapstring, GameState; private current: GameState; constructor() { this.states new Map(); } registerState(name: string, state: GameState) { this.states.set(name, state); } async switchState(name: string, params?: any) { if (this.current) { await this.current.onExit(); } this.current this.states.get(name); await this.current.onEnter(params); } update(deltaTime: number) { if (this.current) { this.current.update(deltaTime); } } }每个游戏状态实现onEnter、update、render、onExit四个方法。这个设计的核心优势是 AI 在生成新状态时不需要理解其他状态的内部实现只需要按照接口协议写逻辑。比如“背包状态”只需要在玩家选择某个道具后调用gameStateMachine.switchState(battle, { itemUsed: potion })至于战斗状态怎么处理道具效果背包状态完全不关心。2.2 地图数据存储与渲染机制《宝可梦 红》的地图本质是一个 2D 瓦片数组每块地图由多个图层组成。我设计的存储结构如下interface MapData { width: number; height: number; tiles: Uint16Array; // 地形层索引指向 tileset collision: Uint8Array; // 碰撞层0 可通过1 阻挡 events: MapEvent[]; // 事件点包括 NPC 坐标、触发脚本 encounters: EncounterTable; // 野生宝可梦遭遇表 warpPoints: WarpPoint[]; // 地图出口 }渲染时只绘制相机视野范围内的瓦片而不是整张地图。真机分辨率是 160×144我做了 3 倍整数缩放呈现 480×432 的显示区域每帧最多只需要绘制 30×27 个瓦片性能完全没问题。最容易被忽略的是碰撞检测的实现方式。我踩过一个坑AI 最初生成的碰撞检测用玩家的中心点去检测前方瓦片导致玩家在贴墙时看起来像是嵌进了墙里。正确做法是玩家实体要有一个略小于精灵图的碰撞盒检测碰撞时用碰撞盒的四条边分别探测前方瓦片。我把这个经验写进了 prompt 里“Player entity has a collision box that is 10x14 pixels, smaller than the 16x16 sprite. When detecting collision, use the edge of the collision box to sample the tile immediately ahead.”2.3 战斗系统与数值计算的实现战斗系统是整个项目里最容易出错的部分因为宝可梦的伤害公式不是简单乘除法伤害 ((((2 × 攻击方等级 / 5 2) × 威力 × 攻击力 / 防御力) / 50) 2) × 属性修正 × 随机数(0.85~1.0) × 会心一击如果 AI 生成的公式顺序不对或者括号嵌套错误战斗结果会完全不符合原作。这个部分我放弃了让 AI 从零推理公式而是直接把公式以 TypeScript 函数模板的形式贴给 AI要求它严格实现类型定义和计算流程只允许调整变量名和做类型标注。function calculateDamage(options: DamageCalculationOptions): number { const attack options.isPhysical ? options.attackerStats.attack : options.attackerStats.specialAttack; const defense options.isPhysical ? options.defenderStats.defense : options.defenderStats.specialDefense; let damage Math.floor(Math.floor( (2 * options.attackerLevel / 5 2) * options.movePower * attack / defense ) / 50) 2; const modifier options.sameTypeBonus * // 本系加成1.5 或 1 options.typeEffectiveness * // 属性克制0.25 ~ 4 options.criticalMultiplier * // 会心一击1 或 2 (0.85 Math.random() * 0.15); // 随机浮动 return Math.max(1, Math.floor(damage * modifier)); }战斗脚本则用基于协程的状态机实现。每回合流程是玩家选招 → 计算先手顺序 → 播放攻击动画 → 计算伤害 → 检查对方 HP → 对方反击 → 检查我方 HP → 回合结束。这中间还有麻痹、睡眠、混乱等状态检查代码很容易被 if 嵌套搞乱。我要求 AI 把每个检查逻辑拆成独立的小函数用yield实现动画等待可读性大幅提升。3. AI 辅助编程的实操过程与核心环节3.1 提示词工程从“帮我写”到“按规范实现”这是整个项目里提升最大的环节。最开始我用的是聊天式 prompt效果不稳定。后面总结出一套对 AI 更友好的 prompt 模板分四层第一层是角色与目标“你是一名有十年经验的前端游戏开发工程师熟悉《宝可梦》系列的底层机制。请实现一个 [模块名]用于 [项目描述]。代码将在浏览器中运行使用 TypeScript不依赖任何第三方库。”第二层是技术约束“请使用 Canvas 2D API 完成渲染不要使用 DOM 元素绘制游戏画面所有全局状态必须放在window.__GAME__命名空间下代码中不要包含任何浏览器兼容性检测逻辑动画帧统一使用requestAnimationFrame驱动。”第三层是数据结构契约“地图数据必须符合以下接口附上三四十行接口定义玩家对象必须包含x、y、direction、isMoving四个属性。新代码只允许依赖MapData、Player、SpriteManager三个现有模块接口定义见下方。”第四层是验收标准“代码完成后请自查以下几点1. 碰撞检测是否使用玩家碰撞盒四条边分别采样前方瓦片2. 地图越界时是否强制回退到边界内3. 相机是否始终以玩家为中心4. 当你打开游戏时会看到一个 20×15 瓦片的视野范围。不要生成示例代码直接生成完整模块。”这套模板把模糊需求变成了精确需求。AI 生成符合要求的代码成功率从 30% 提升到 70% 左右剩余 30% 的失败主要集中在算法细节上比如寻路逻辑、经验升级曲线等这些我会单独拆出来喂给它。3.2 现有代码的再利用与性能调优AI 生成的第一版地图渲染代码每帧都会重新绘制整个视野内的所有瓦片。对于 480×432 的分辨率这其实也够用帧率能保持 60FPS。但如果要支持后续的地图动画比如水面的流动效果就需要更高效的方案。我让 AI 做了一层优化把静态瓦片预先渲染到离屏 Canvas 上每帧只做一次drawImage把整个离屏画布拷到主画布只有动态元素玩家、NPC、特效才逐帧绘制。这个优化让渲染开销从每帧 900 次 drawImage 调用降低到 200 次左右为后续功能留下了性能余量。另一个容易被忽略的问题是内存管理。AI 生成的地图数据如果用 JavaScript 对象存储每个瓦片的属性一张 50×40 的地图就要创建 2000 个对象。我改成 Uint16Array 存储地形索引、Uint8Array 存储碰撞数据内存占用从数百 KB 降到几十 KB。这个优化对现代浏览器不算什么但让我养成了“数据布局先行”的编程习惯。3.3 测试驱动开发AI 代码进安全区的关键跟 AI 写代码最容易翻车的就是改一处坏另一处。最开始我没有自动化测试每次改完都手动玩游戏从头走到尾五个月下来效率极低。后来我引入了 Vitest 做单元测试给核心逻辑伤害计算、经验计算、进化判断、背包增删都写上了测试用例。比如伤害计算我会把《宝可梦红》原作数据作为用例一只 50 级的喷火龙使用喷射火焰攻击 50 级的妙蛙花属性克制 2 倍、本系加成 1.5 倍随机数取 1.0 时的理论伤害范围是 108~127。测试就断言计算结果落在 108 到 127 之间。这样每次 AI 改完代码我只需要跑一遍npm test就能立刻抓住回归 bug。这个习惯在中后期特别有用因为模块多了之后AI 经常会在改 A 模块时不小心影响 B 模块。有了测试作为保护网我敢放手让 AI 做修改不用担心改崩了找不到原因。4. 五个月里踩过的坑与排查经验4.1 最大的坑让 AI “回忆”游戏数据项目早期我犯过一个特别蠢的错误直接问 AI “耿鬼的种族值是多少”它给的答案是 60/65/60/130/75/110——这是对的。但当我问“大针蜂的种族值”时它毫不犹豫给出了一个完全编造的数据后来我查证原作发现错得离谱。AI 的语言模型本质是预测下一个词不是查询数据库。当数据在训练集中出现频率高时它可能“记住”了正确答案但冷门宝可梦或者细节数据它就会自信地编造。这个问题的解决方案只有一个所有数据都必须从可信数据源人工校对后以 JSON 文件形式提供给代码绝不让 AI 直接生成数据表。我把《宝可梦红》的全部 151 只宝可梦的种族值、属性、经验曲线、进化等级整理成一个pokemon-data.json一只一只从资料站核对过。这个过程花费了大约两周的零碎时间但为项目消除了一个巨大的不确定源。后面所有模块都统一从这份 JSON 读取数据AI 只需要写取数逻辑不需要生成数据本身。4.2 上下文窗口限制与信息丢失用 AI 开发大型项目时最大障碍是它的上下文窗口有限。一开始的项目对话聊到第三周时AI 已经完全忘记了第一周定下的接口协议导致生成的代码频繁出现“函数不存在”“属性名对不上”的编译错误。我后面摸索出一套应对方法不再依赖长对话维护项目记忆而是建一个PROJECT_SPEC.md文件记录项目的接口协议、模块清单、技术决策和当前进度每次新对话开始时把这份文件完整贴给 AI。内容大概包含项目技术栈与目录结构所有模块的接口签名比如export class BattleSystem { constructor(config: BattleConfig); startWildBattle(pokemonId: number): PromiseBattleResult; }数据文件的位置和格式说明当前已完成的功能和已知 bug 列表这个方法让每个对话都从“全新开始”变为“无缝续接”AI 不需要回忆之前聊过什么只需要根据 specs 干活。建议任何用 AI 做复杂项目的人都试试这个文档本质上就是 AI 版本的“项目文档”。4.3 调试与回滚的工程实践AI 生成的代码出 bug 时我一开始的应对方式是“描述 bug 让 AI 修”后来发现效率极低。AI 对 bug 的理解往往流于表面它的修复有时会引入新的问题。比如玩家走进草丛没触发战斗AI 会试着把触发概率调高但真正原因是事件坐标写错了一格。后来我改成“先定位再修复”的策略自己写日志、加断点定位到具体函数和行号后再把这个精确定位信息喂给 AI让它只修这一个点。比如“玩家在 (10, 5) 坐标进入草丛时EncounterSystem.check返回 false请检查 event trigger 条件判断中的坐标偏移”。这样的修复准确率大幅提升也更少引发连锁 bug。另外每次让 AI 做较大修改前我都会用 git 打一个 tag。改崩了就git checkout恢复而不是陷在 bug 里和 AI 来回拉扯。这个习惯救了我无数次特别是项目后期“改一处动了全盘”的情况经常发生。5. 常见问题速查表与避坑技巧5.1 高频问题与解决对照表问题现象根本原因有效解法AI 生成代码编译就报错接口协议对不上把 PROJECT_SPEC.md 贴进 prompt明确依赖签名游戏运行时表现不符合预期AI 凭经验“自由发挥”设计提示词中增加行为约束描述期望的具体行为而非抽象目标一改 A 功能 B 就崩模块间耦合严重拆分模块通过接口通信依赖单元测试保护遇敌概率忽高忽低随机数种子管理混乱统一用一个 Random 类管理所有随机数种子可配置用于测试存档丢失localStorage 存储空间或序列化失败用 JSON.stringify 前先深拷贝加 try/catch存两份备份动画卡顿每帧重复绘制静态元素分离静态背景层和动态层背景层预渲染到离屏 Canvas扒下来的像素素材有色偏索引色转 RGBA 时调色板顺序错误统一用调色板数组映射测试已知颜色的输出值5.2 不写单元测试就别让 AI 改代码单元测试是我这次项目坚持下来的最重要工程实践也是我从这次复刻项目中收获的硬核经验。用 AI 写代码代码量增长极快但 bug 也随之上来了。没有测试时我根本不知道 AI 改完某个模块后是否悄悄破坏了其他的逻辑。比如经验计算函数我手动跑一轮战斗验证要花三十秒但写完十组测试跑一次只要五十毫秒。这中间节省的时间五个月累计下来至少有几十个小时。给 AI 写测试用例也有技巧。我通常先让 AI 生成待测模块的实现再让 AI 根据同一份接口描述生成测试用例。这样测试和实现是同一份 AI“画风”写的逻辑风格一致问题更容易暴露。测试用例优先覆盖边界值0 级、满血、属性免疫、所剩 PP 为 0、状态组合中毒 睡眠 低于 1 HP、以及原作数值对拍用已知战斗结果作为断言标准。5.3 “保留几个版本之前的代码”是硬道理用 AI 重构代码时非常容易产生“不可逆破坏”尤其是涉及状态机切换和事件系统时。有一次我想优化战斗动画的播放逻辑AI 连改了好几版运行效果却一代不如一代。如果没有 git 回滚我可能会继续陷入无休止的调试循环。所以我现在的固定流程是重构之前git commit一次让 AI 生成重构方案我先审看方案再执行如果执行结果不满意git revert回到之前的提交点重新换个思路再来。这个流程看起来很保守实际上最省时间——砍掉无谓的调试循环直接重新开始。6. 收获与复盘的实践建议6.1 个人能力边界的变化做这个项目的收获远不止于一个能玩的复刻版游戏更关键的是我理解了自己作为开发者的能力边界是如何被 AI 重新定义的。以前写游戏最大的成本在写代码本身现在最大的成本变成了“明确需求”和“定义验收标准”。这两种能力恰好是大部分程序员在工作中最稀缺的。我花了很多时间整理INTERFACE.md和技术 specs表面上看是在“给 AI 写需求”实际上是把以前脑子里模糊的设计逐步变成清晰的规则。这个过程让我对模块化设计、接口契约、数据布局这些基础概念的理解更加深入。AI 没有让我变懒反而逼我把项目设计做得更严谨。6.2 写给也想用 AI 复刻老游戏的朋友如果你也想做类似的事我总结了几条重要建议。第一条选项目时一定要挑自己真正了解的游戏。《宝可梦红》我玩了无数遍每个系统什么逻辑、每种情况应该在界面上显示什么我心里都有预期AI 给的代码对不对一眼就能看出来。如果你不熟悉原作AI 变成什么样你都不知道该不该接受。第二条先做最小可玩原型。不用一开始就把所有宝可梦、所有地图都做完先做到“主角能在一张地图上走、能进草丛遇敌、能打一场简易战斗、能升级”这个闭环跑通之后后面的功能都是往这个框架里加内容。第三条数据库是支撑不是 AI 的“回忆录”。所有数值数据都应该是从可信来源整理出来的 JSON而不是让 AI 生成数据表。你整理数据的过程也是重新认识游戏设计的过程。6.3 下一步打算与项目扩展方向基础版本已经能玩我准备给它加两个新东西。第一是随机化模式让每只宝可梦的出现位置、进化方式、招式学习表都打乱这样即便玩过原作的人也能有新鲜感。第二是简易对战交换功能利用 WebRTC 做点对点连接两个人可以直接在浏览器里联机交换宝可梦。我自己最享受的还是“看着代码从无到有游戏从抽象概念变成可玩实体”的过程。AI 不是魔法它需要你用工程方法去引导、约束和验证。把 AI 当作一个特别勤奋但有时会胡说八道的初级同事你提供清晰的 specs 和及时的反馈它就能帮你把项目推向一个超过个人独立完成能力的高度。
返回列表