用Vibe Coding 13天开发怀旧挂机游戏:AI辅助编程实践 📅 发布时间:2026/8/30 4:14:13 👁 浏览次数: vibe coding 最近在开发圈里讨论很多简单说就是用自然语言描述需求让 AI 代码助手帮你搭骨架、补模块你再按反馈调试调整。我这次用 13 天做了一个怀旧网游主题的放置挂机小项目名字叫“QQ华夏挂机版”用来纪念 18 年前泡在网吧打网游的日子。整个过程基本没离开三条原则先把场景想清楚再让 AI 动手最后自己验收。先说结论vibe coding 很适合做这种“个人怀旧项目”。它不需要一开始就把架构设计完整也不需要专业美术资源只要你有明确的核心玩法AI 就能帮你把大部分重复代码生成出来。但真正决定项目能不能完成的不是 AI 的生成速度而是你自己的判断力——哪些功能值得做哪些功能做到一半就该停哪些问题必须手动修。这篇文章就按我的实际开发顺序拆一遍。不涉及任何真实游戏账号、真实服务器或官方资源纯属自娱自乐的独立小游戏用 HTML5 Canvas 做一个本地运行的单机放置玩法。我会把它当成一个可以复现的开发案例来讲包括开发前准备、13 天的节奏、核心逻辑落地、AI 代码验收和下一步扩展。1. 先明确 vibe coding 适合解决什么1.1 我理解的 vibe coding 是“可运行的草稿”vibe coding 这个词火起来之后很多人把它理解成“随便说说AI 全自动写代码”。我的实际感受是它更像一种高频反馈的开发方式你把一个很小、很具体的目标丢给 AI比如“写一个二维网格地图地图上有树、石头和水”AI 返回代码你运行看效果发现问题再继续描述。关键不是句子有多自然而是你描述的需求足够小。我一开始也试过让 AI 直接生成“一个完整的放置挂机游戏”结果它给了我一个塞满模块但完全跑不起来的项目。后来我改成一次只描述一个功能代码质量立刻提升很多。所以我会把 vibe coding 定义成“能跑的草稿”不是“一步到位的完整产品”。1.2 它适合个人开发者的地方个人开发者做小项目最大的成本往往是时间和决策而不是写代码本身。vibe coding 最大的价值是把“从零到能跑”的时间压缩得很短尤其适合下面几类项目快速验证玩法想知道自动战斗、掉落、升级这一套循环有没有意思。怀旧向个人项目不用在乎商业价值关键是还原记忆里的感觉。学习性质的项目通过 AI 生成的代码反推实现思路边跑边理解。小工具和小游戏结构简单、依赖少、不需要复杂服务器。在这些场景里AI 写的代码不需要完美也不需要考虑团队协作、性能优化和长期维护。优先考虑的是能不能跑通能不能在这个基础上快速改。1.3 不适合直接交给 AI 的地方同时也要认清边界。如果项目涉及复杂实时同步、支付、账号体系、高并发渲染或者对性能和安全性有严格要求的业务系统我不建议用 vibe coding 作为主力方式。AI 能生成一段看起来合理的代码但它可能缺少异常处理、边界校验和性能考虑。例如我如果真要做一款面向大量玩家的多人在线游戏需要处理角色位置同步、背包一致性和防作弊这个阶段靠 vibe coding 撑不住。个人怀旧项目则没有这些问题因为使用者只有我数据也只在本地。1.4 这个怀旧项目的目标定义在动手前我给这个项目定了一个很明确的目标做一个“能玩一下午”的放置小游戏让我找回 18 年前那种打怪、捡装备、升级、做任务的感觉但所有内容都是自己写的不涉及任何真实的游戏资源。“能玩一下午”是有判断标准的能进入地图、角色会自动寻找怪物并战斗、有掉落、有背包、有基本任务、死亡后可以复活或重开存档不会丢。这五条做到了项目就算达到预期。2. 开发前准备场景、素材、边界、验收2.1 先把游戏循环写成一段话我建议你在让 AI 写代码之前先用一段自然语言把游戏循环写清楚。这一步看着简单实际上决定了整个项目的骨架。我当时写的是“玩家从新手村出发地图上有若干怪物。角色会自动靠近最近的怪物并攻击。怪物死亡后会掉落金币、材料和装备。玩家拾取后放入背包可以打开背包查看装备并穿戴。角色获得经验后升级属性提升。每隔一段时间NPC 发布一个简单任务完成任务获得额外经验。玩家可以存档重新打开项目后继续玩。”这段话就是后续所有开发需求的出发点。AI 每次生成代码时我都会把这一段放在需求描述里防止它理解成“要做一个完整的 3D 大型网游”。2.2 素材不靠画的套路这是很多新手最担心的问题我不会画图怎么做游戏我的做法很简单就是用色块、文字和简单形状拼出视觉效果。地图用二维数组表示1 表示墙0 表示可走路面2 表示树。角色用一个圆形或矩形方块表示用颜色区分玩家和怪物。怪物不同颜色、不同大小的方块区分等级。装备不需要图片背包里用名字加颜色字符串显示。这样做的原因是vibe coding 阶段的核心是验证玩法。只要地图、角色、怪物都能被清楚识别画面的精细程度可以放到后面再补。如果有人觉得方块太粗糙可以用像素风格素材或免费图标替换但那是第二个阶段的事。2.3 边界定清楚才不会被 AI 带偏给 AI 描述需求时边界比功能更重要。我需要明确告诉 AI“不做”什么避免它越加越多不接入任何真实游戏服务器不做账号登录不做在线聊天不做商城和充值不做真实游戏品牌的美术还原不做 3D 效果不做复杂 AI 行为怪物只要会刷新、会被攻击、会掉落就行边界定清楚之后AI 生成的内容会收敛很多。它不会动不动就给你加一个“登录注册页面”或者“支付回调接口”。这些功能在真实项目里很有用但在一个怀旧小项目里只会拖慢进度。2.4 工具和运行环境我的项目选择是 HTML5 JavaScript用 Canvas 渲染画面所有逻辑放在本地浏览器里运行。这样不需要安装复杂环境也不需要买服务器打开一个 HTML 文件就能测试。类别我的选择原因运行方式本地浏览器环境简单调试方便客户端语言JavaScript / HTML5 Canvas适合快速搭建小游戏游戏类型单机放置 RPG不需要同步和服务器存档方式localStorage 或下载存档文件本地保存免部署AI 工具任意 AI 代码助手关键是迭代不是具体品牌如果你希望以后打包成桌面应用可以在原型完成后用 Tauri 或 Electron 包一层壳。但我的建议是第一版不要打包先把网页版跑稳定了再说。打包会引入新的安装依赖和跨平台问题和游戏本身的玩法没有关系。2.5 验收标准验收标准要可执行不能只说“好玩”。我给自己定的是地图能加载角色能移动角色会自动寻怪、攻击、获得经验背包能打开能查看物品数量装备能穿戴攻击力发生变化任务能接受、能完成、能领取奖励刷新页面后进度不丢失任何一条没通过都不算完成。有了这套验收标准我每天结束前只需要问一个问题“今天做出来的版本哪几条通过了”如果只做了一堆代码但没有通过任何验收项那就说明走了弯路。3. 13 天开发节奏拆解每天只盯一件事3.1 第 1 到 2 天搭建够用的骨架前两天的目标不是做玩法而是让项目跑起来。我让 AI 生成一个最基本的 Canvas 游戏循环有画布、有背景色、有一个可移动方块有每秒刷新 60 次的 update/render 逻辑。这个阶段最容易出现的错误是“脑子里想得太大手上一行代码还没写”。我建议把它压到最小一个页面一个游戏循环一个方块。检验标准只有一个浏览器打开方块能随着键盘移动。第 2 天我补了两个基础模块地图数据和碰撞检测。地图用二维数组表示角色移动时判断目标格子是不是墙。这个模块后面所有玩法都依赖它值得先做。3.2 第 3 到 5 天地图、移动和碰撞第 3 天我开始让 AI 生成一张 20x15 的小地图。地图四周是墙中间有若干障碍物和空地角色从左上角出生。这里不需要复杂寻路只需要“按方向键移动”。第 4 天处理碰撞。最简单的做法是每次移动前计算目标位置如果目标位置的格子是墙就停在原地。这个逻辑即使通过 AI 生成也要自己理解否则后续怪物碰撞会出问题。第 5 天我把地图可视化了。用不同颜色区分草地、道路、墙、树这样看起来才像一个“小世界”。到这里核心框架已经成立剩下的事情可以放心交给后续迭代。3.3 第 6 到 9 天战斗、怪物和掉落第 6 天到第 9 天是最核心的玩法阶段。我把功能拆成四个小批次第一批场景里生成 5 个怪物角色靠近后按空格攻击。第二批怪物被攻击后血量下降血量为 0 时消失角色获得经验。第三批怪物死亡后掉落随机物品物品掉在地图上可以被拾取。第四批角色属性面板显示生命、攻击、等级、经验条。每次只让 AI 做一个批次。需要特别注意的是不要让怪物同时在多个逻辑里被处理。如果刷怪、攻击、掉落都堆在一个函数里后面会很难排查。我通常会让 AI 拆成独立的函数并在函数名上体现职责例如spawnMonster、attackTarget、generateDrop。3.4 第 10 到 12 天挂机循环、背包和任务第 10 天把“手动攻击”改成“自动攻击”。角色只要在怪物附近就会根据自己的攻击速度自动攻击。这就是题目里说的“挂机版”的核心玩法角色能自己打怪、自己升级玩家只需要偶尔打开背包整理装备。第 11 天做背包。背包数据是一个数组每个物品有名称、类型、数量、属性。界面放在画布右侧按 B 键打开。这个部分代码量不大但比较繁琐AI 很适合处理这种表单类逻辑。第 12 天做任务系统。我只能提醒不要一开始就设计十个任务。做两个通用任务就够了比如“击败 5 只野狼”和“拾取 3 个材料”。任务系统其实是状态机未接受、进行中、可提交、已完成。只要把这个状态流转做好后面加任务只是加数据。3.5 第 13 天体验问题清理和打包最后一天我没有加新功能只做三件事修复已知的诡异 bug比如怪物重叠、攻击距离过大、物品拾取不到。清理控制台报错保证浏览器打开只有 0 个错误。把保存功能调到“刷新页面后自动恢复”的状态。这里有个经验新功能永远做不完体验问题和关键 bug 才决定项目能不能真正玩起来。第 13 天看起来没有“新产出”但它决定了这个项目是一个能玩的游戏还是一个半成品代码库。4. 核心逻辑怎么落地地图、战斗和自动挂机4.1 地图先用二维数组再做可见地图我用二维数组做地图每一格代表一个地块类型。示例const map [ [1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 2, 0, 0, 1], [1, 0, 1, 1, 0, 0, 1], [1, 0, 0, 0, 0, 2, 1], [1, 1, 1, 1, 1, 1, 1] ];这里1是墙0是可行走区域2是装饰物。角色移动时只要目标位置不是1就可以走过去。这个方案的好处是调试非常直观。你想知道某个地方能不能走直接看数组就行。等玩法稳定后如果想换成图片风格再做一个“地图块到图片”的映射即可。4.2 角色属性成长感靠数值曲线角色属性通常包括生命值、攻击力、防御力、移动速度、攻击速度、经验值、等级。数值不用复杂但曲线要有感觉。我用的公式类似下一级所需经验 当前等级 * 80 上一级经验。这样前期升级快后期升级慢。攻击力和生命值则用“基础值 等级 * 成长值”计算。如果你不确定数值怎么调可以先给 AI 一个粗略参数再自己跑一遍感觉。实测时最容易发现的问题是前期太容易死或者后期打一个怪要打一分钟。遇到这种问题优先调攻击速度和攻击力不要调地图和怪物数量。4.3 自动战斗循环核心循环怎么写自动战斗是挂机玩法的关键。它不需要很复杂核心就是找最近的敌人移动到攻击距离内然后攻击。示例代码只表达思路function updateAutoBattle(game, dt) { if (!game.player.alive) return; const target game.findNearestEnemy(game.player.x, game.player.y, 200); if (!target) return; if (game.distance(game.player, target) target.attackRange) { game.moveToward(game.player, target, game.player.moveSpeed * dt); } else { game.player.attackTimer - dt; if (game.player.attackTimer 0) { target.hp - game.player.attackPower; game.player.attackTimer game.player.attackSpeed; if (target.hp 0) { game.generateDrop(target); game.removeEntity(target); game.player.exp target.exp; game.checkLevelUp(game.player); } } } }这段代码不是完整版本但它体现了挂机循环的关键点角色不需要玩家操作系统每帧判断一次状态并执行“寻找目标、移动、攻击、结算”四个步骤。实现时容易忽略的是攻击计时器。每个角色得有独立的attackTimer不然多个角色会共享同一个攻击冷却导致攻击频率完全错乱。4.4 掉落、背包和装备怪物死亡后我让系统生成一个掉落物对象包含物品 ID、名称、类型、数量、地图坐标和拾取范围。玩家走过去碰到掉落物掉落物从地图上移除并加入背包。背包数据建议用数组不要用对象。因为玩家可能会捡到多个相同物品用数组配合数量字段比较直观。每件物品的信息都来自一个物品配置表示例{ id: sword_02, name: 铁剑, type: weapon, attack: 12, desc: 比木剑结实一点 }装备系统可以做得更简单玩家打开背包选择武器或防具点击穿戴角色攻击或防御属性即时更新。这里不需要做复杂的装备槽位计算一个“武器”和“防具”槽位就够了。4.5 任务、NPC 和存档任务系统要独立于战斗逻辑。状态机是任务最稳定的实现方式任务状态分为idle、active、can_complete、completed。每次击杀怪物或拾取物品时通知任务系统更新进度。存档方面最简单的方式是把关键数据转成 JSON 存入 localStorage。存档内容包括角色位置、等级、经验、生命、背包列表、任务进度和地图上的掉落物。刷新页面时读取存档并恢复。但要注意localStorage 是本地存储换浏览器、清缓存都会丢。如果你要长期使用或分享给别人最好再做一个“导出存档文本”的功能。5. AI 代码的验收与排错不能只看“能跑”5.1 先把问题分成四类AI 生成的代码第一次往往能跑但跑起来不一定正确。我把问题分成四类每类处理方式不一样问题类型表现处理方式语法错误报错、页面白屏直接让 AI 修复通常很快逻辑错误不报错但行为不对看具体场景给 AI 复述“预期行为”边界问题怪物死亡后掉落消失、攻击距离异常自己补全条件判断设计问题数值崩坏、流程太绕需要自己决定怎么改不能全靠 AI排错时最忌讳的是直接说“你写的代码有 bug”。AI 知道代码有 bug但它不知道你的预期是什么。正确做法是描述场景和预期当怪物血量小于等于 0 时应该生成掉落物并移除怪物但当前怪物消失了没有生成物品。这样 AI 才有修的方向。5.2 我踩过的五个典型坑第一个坑地图坐标和画布坐标混淆。地图是二维数组角色移动走的是行列坐标但画布渲染需要像素坐标。AI 经常在这两种坐标间来回混用结果角色走到墙里或飞出地图。解决办法是统一约定逻辑坐标用地图格子坐标渲染时乘以格子大小。第二个坑怪物重叠。刷怪时没有检查位置多个怪物生成在同一个点。攻击目标计算时角色会一直追到最近的怪物看起来像在打空气。解决办法是刷怪时检测目标位置是否已有怪物如果有就换一格。第三个坑攻击距离过远。AI 默认生成的角色攻击距离可能覆盖半个屏幕角色还在地图另一头就能打到怪。后来我统一给每个怪物设了攻击范围角色也要走进这个范围才能攻击。第四个坑存档恢复后位置错误。存档存的是地图坐标恢复时没有判断该位置是否是墙结果角色复活到墙里面。解决办法是读取存档后先做一次碰撞检查如果目标位置是墙就移动到最近的空地。第五个坑AI 不断往代码里加功能。我让它优化背包显示它顺手加了一个“商店系统”还加了几个没用的接口。后来我严格限制每次需求范围并明确说“不要增加需求外的新功能”。5.3 固定排查顺序遇到问题时我建议按这个顺序排查不要一开始就改代码。先看现象是白屏、报错、卡住、没反应还是行为怪。再看控制台有没有红色报错报错信息指向哪个文件和哪一行。再看输入状态地图数据、角色位置、怪物列表是否正常。再看参数攻击距离、攻击速度、移动速度是否有明显异常值。再改代码尽量一次只改一个变量或一个条件然后重新运行。很多看起来像 AI 笨的问题最后发现是我自己没有把输入描述清楚。例如我没说“角色的出生点不能是墙”AI 生成随机坐标时就完全有可能生成到墙里。这类问题不是 AI 能力不足而是需求边界缺失。5.4 防止需求漂移vibe coding 很容易让人“先做做看”结果做着做着就偏离原目标。我在项目中期就有一次想加“宠物跟随”系统后来一想这跟怀旧初体验没有任何关系果断砍掉。防止需求漂移的办法是每次增加功能前问自己三个问题。这个功能是不是核心游戏循环的一部分不加它玩家会不会觉得这个游戏不完整如果加它需要多久会不会拖慢已有功能如果三问里有两个是“否”就不加。这样我才能把 13 天的时间真正花在核心体验上。6. 从 13 天原型继续深化性能、玩法和发布6.1 性能检查别凭感觉14 天之后如果还想继续做我建议先做一次性能检查不要凭“感觉还行”来判断。重点看三类指标帧率浏览器里能不能稳定在 50 到 60 帧。实体数量地图里同时存在多少个怪物和掉落物时开始卡顿。逻辑耗时自动战斗循环里的距离计算和对象遍历是否每帧都在做。实测方法很简单把怪物数量从 10 调到 50再调到 100观察帧率变化。如果明显掉帧优先优化的是“每帧遍历全体怪物”的逻辑。可以改用空间分块或者减少掉落物消失时间。对于个人项目能保证 30 个到 50 个怪物同时在场不掉帧基本就够用了。6.2 可玩性改进方向如果只追求可玩性我建议优先改进三个方面而不是加系统。成长反馈每升一级弹出属性变化让玩家有明确“变强了”的感觉。选择空间多设置几种武器或技能让不同玩家有不同路线。任务引导前期任务要告诉玩家“下一步做什么”避免挂机后没有目标。注意这些改进要基于现有数据做。你可以先记录每局游戏的平均时间、角色平均等级、掉落物拾取率再决定改哪里。6.3 从本地原型到对外展示如果你想把这个项目分享出去除了贴代码最好做一个可以访问的静态页面。构建和部署这一步不算复杂但要注意路径要写相对路径不要用本地绝对路径。资源文件要压缩地图数据可以换成 JSON 文件。存档用 localStorage 时换浏览器会丢建议导出存档。如果要用桌面应用形式发布再考虑用 Tauri 或 Electron 打包。但对一个 13 天完成的怀旧小游戏来说跑在浏览器里已经足够。过度封装反而会消耗很多时间。6.4 是否要继续投入的判断项目做完后我冷静想了一下它值不值得继续做判断标准不是“代码好不好”而是“我还想不想玩”。如果我自己玩了两天还有兴趣说明它具备继续打磨的基础。如果我自己都不想打开就没有必要继续堆功能。怀旧项目最重要的是表达情感和验证想法它不需要变成一个复杂产品。所以我最后给出的建议是13 天版本的目的是让你确认“用 vibe coding 做一个小游戏”这条路走不走得通。走通之后你可以选择继续优化也可以把它当作一个里程碑然后开始下一个更有挑战的项目。如果你也想做类似的项目我建议从更小的范围开始例如“一个角色、一张地图、一种怪物、一次掉落”跑通后再逐步扩展。不要给自己设太高的上线标准毕竟做出来的本身就是对当年那段时光的一种纪念。