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 做一个小游戏”这条路走不走得通。走通之后,你可以选择继续优化,也可以把它当作一个里程碑,然后开始下一个更有挑战的项目。
如果你也想做类似的项目,我建议从更小的范围开始,例如“一个角色、一张地图、一种怪物、一次掉落”,跑通后再逐步扩展。不要给自己设太高的上线标准,毕竟做出来的本身就是对当年那段时光的一种纪念。