用AI做网页小游戏:结构化需求与调试闭环
作者:很酷cat2026.11.14 09:22浏览量:1简介:本文讲清如何用 ERNIE 5.5 Flash Preview 生成单文件网页小游戏。先把玩法拆成规则、界面、交互、数值、胜负五项需求,再通过运行、贴报错、分批修改的循环打磨可玩性,并附可直接复制的提示词、修正后的核心代码与发布检查清单。
一句话能出游戏,前提是需求说得清
很多人用一句话让模型做《羊了个羊》,拿到的版本却和脑子里的不一样。原因通常有两个。第一,需求缺细节,模型只能自由发挥。只说“做个羊了个羊”,它不知道卡牌有几层、槽位有几格、输了能不能重开。第二,没有调试闭环。第一版几乎一定有问题,比如点击穿透、卡牌重叠、计分错乱。如果只是重新生成一遍,问题大概率还会出现。
更稳的流程是:结构化需求,生成单文件代码,本地运行,描述现象并迭代修改,最后发布分享。本文按这个顺序展开,并给出可复制的提示词和修正后的关键代码。
传统做法与 AI 辅助的差别
| 环节 | 传统自研 | AI 辅助(ERNIE 5.5 Flash Preview) |
|---|---|---|
| 玩法设计 | 写策划案、画原型 | 用自然语言描述规则,模型补全数值与边界 |
| 界面搭建 | 手写 HTML/CSS、切图 | 直接产出单文件页面,可加入响应式布局 |
| 核心逻辑 | 自己实现状态机、碰撞、计分 | 生成可运行逻辑,并解释关键函数 |
| 调试 | 断点、日志、反复尝试 | 贴报错与现象,定位到具体函数修改 |
| 素材 | 找美术、做音效 | 用 CSS 或 Canvas 绘制,用 Web Audio 合成音效,或接入 CC0 素材 |
| 发布 | 配服务器、打包 | 静态文件上传到对象存储或 GitHub Pages |
AI 主要缩短的是“从 0 到能跑”的时间。数值平衡和手感调优仍然要自己玩上几十局。
为什么选 ERNIE 5.5 Flash Preview
ERNIE 5.5 Flash Preview 是文心系列基础模型 ERNIE 5.5 Flash 的预览版本。官方介绍中,它重点强化了编程开发、智能体、知识工作与深度研究能力,并支持长上下文输入,可结合代码编写、工具调用与 Agent 工作流完成多步骤任务。对做游戏来说,有三个直接价值。
第一,长上下文能装下整个文件。做到第三轮迭代时,可以把完整的 index.html 连同修改要求一起提交,模型更不容易忘记前面定义过的变量和函数名。具体上下文窗口长度以千帆模型详情页为准。
第二,单文件输出能力强。游戏类需求很适合一个 HTML 文件完成,双击即可运行,发给朋友也能直接打开,不需要构建工具。
第三,擅长按指令约束输出。你可以要求“只用原生 JS,不引入第三方库”或“必须支持手机触屏”,模型会按要求执行。这对后续维护很关键。
模型的详细信息、版本与接入方式可以在 百度智能云千帆模型详情页 查看。想先体验效果,可以打开 ERNIE 5.5 Flash Preview 在线体验 输入游戏需求试一轮。
注意:模型本身是文本模型,产出的是代码和设计。图片、音效、3D 模型等二进制素材需要用外部工具生成,或从素材站获取,再由代码引用。游戏实际运行在浏览器中。
准备工作
准备工作很少,几分钟就能完成。
- 现代浏览器:Chrome、Edge 或 Safari 的最新版本
- 文本编辑器:VS Code 最顺手,记事本也能用
- 新建一个 game.html 文件,所有代码都写在这里
- 可选:Node.js,仅当你想用 npx serve 启动本地服务时需要。serve 默认监听 3000 端口,实际地址以终端输出为准
不需要安装 Unity,不需要游戏引擎,也不需要注册任何 SDK。
五步实操流程
第 1 步:把玩法写成结构化需求
只写“做个炸金花”,模型会自己补全大部分细节。应补齐五项:玩法规则、界面元素、交互方式、数值设定、胜负条件。缺哪一项,模型就会自行补充,补出来的内容往往不合你的意。
第 2 步:要求输出单文件代码
明确写出“单个 HTML 文件,原生 JS,内联 CSS,不引用外部资源”。这样生成的文件可以直接双击运行。
第 3 步:本地运行,收集第一手问题
打开页面,按 F12 进入控制台(Console)。把报错原文完整复制下来,连同你观察到的现象一起回贴给模型。这一步比重新生成更有效。
第 4 步:迭代修改,一次只改一类问题
如果同时提五个修改点,模型容易顾此失彼。建议按“致命 Bug、交互手感、数值平衡、视觉细节”的顺序推进,每轮只聚焦一类。
第 5 步:发布与分享
- 最快的方式:把 HTML 文件直接发给朋友,通过微信或 QQ 传输后用浏览器打开。
- 需要链接的场景:使用 GitHub Pages、Cloudflare Pages,或百度智能云对象存储 BOS 的静态网站托管,上传后即可获得公网地址。
- 手机游玩:静态托管后用手机浏览器打开,再添加到主屏幕,体验接近原生 App。
发布前请确认文件中没有密钥、个人信息或未授权的素材。静态文件任何人都能打开,不要把敏感内容放进去。
可直接复制的 Prompt(游戏生成主模板)
把方括号内的内容换成你的需求即可:
# 任务目标生成一个可直接运行的单文件网页小游戏:[羊了个羊 / 炸金花 / 狙击射击]# 输入信息(玩法规则)- 游戏类型:[三消卡牌 / 扑克牌型比大小 / 定点射击]- 核心规则:[例如:卡牌共 3 层,点击卡牌进入底部 7 格槽位,槽内集齐 3 张相同即消除;槽位满 7 格且无可消除则失败]- 操作方式:[鼠标点击 / 手机触屏点击 / 鼠标移动瞄准加点击射击]- 数值设定:[关卡卡牌总数 90 张、槽位 7 格、倒计时 120 秒、弹匣 8 发]- 胜负条件:[清空全部卡牌为胜;槽位爆满为败]- 特殊规则(如有):[例如:同牌型比点数,点数相同判平局;A-2-3 为最小顺子,Q-K-A 为最大顺子]# 执行要求1. 输出单个 HTML 文件,CSS 与 JS 全部内联,禁止引用任何外部文件、CDN 或图片链接。2. 仅使用原生 HTML、CSS 和 JavaScript,不使用任何框架或第三方库。3. 视觉素材用 CSS 渐变、Unicode 符号或 Canvas 绘制实现;音效用 Web Audio API 程序化合成。4. 必须响应式适配:桌面端与手机竖屏均可正常游玩,触屏事件需与鼠标事件同时生效。5. 手机端需包含 viewport meta 标签,并用 env(safe-area-inset-*) 处理刘海屏安全区。6. 代码需分区注释:状态管理 / 渲染 / 交互 / 计分 / 主循环。7. 关键逻辑(如牌型判定、消除检测、命中判定)请写成独立纯函数,便于后续单点修改。8. 包含开始界面、游戏中 HUD(分数、剩余、倒计时)、胜利与失败弹窗、重新开始按钮。9. 输出前自检语法,确保没有未定义的变量、函数名拼写错误或明显的类型错误。# 预期输出1. 一个完整可运行的 HTML 文件代码块。2. 代码后附一段简要说明:核心数据结构是什么、主循环在哪里、如需调整难度应修改哪几个常量。
三个案例的关键实现
案例一:羊了个羊(消除类)
这类游戏的难点在卡牌的层叠遮挡与可点击判定,消除本身并不复杂。下面的数据结构以卡牌左上角坐标 x、y 记录位置。
// 每张牌记录层级与左上角坐标,层级高的覆盖层级低的const CARD_W = 60, CARD_H = 80;const state = {cards: [/* { id, type, layer, x, y, alive, el } */],slots: [], // 底部槽位,最多 7 个SLOT_MAX: 7,remain: 90};// 计算两张牌的重叠面积占单张牌面积的比例function overlapRatio(a, b) {const ox = Math.max(0, Math.min(a.x + CARD_W, b.x + CARD_W) - Math.max(a.x, b.x));const oy = Math.max(0, Math.min(a.y + CARD_H, b.y + CARD_H) - Math.max(a.y, b.y));return (ox * oy) / (CARD_W * CARD_H);}// 上层牌覆盖面积超过 60% 时,下层牌不可点击function isCovered(card) {return state.cards.some(c =>c.layer > card.layer && c.alive && overlapRatio(c, card) > 0.6);}// 入槽后检查是否有三张同类,有则消除function tryMatch(type) {const same = state.slots.filter(s => s.type === type);if (same.length === 3) {same.forEach(s => removeCard(s));return true;}return false;}
判定基于面积重叠,因此注释与代码一致。removeCard 需要同时把牌标记为不存活,并从 DOM 中移除。否则会出现牌已经消失却仍能点击的情况。
迭代时最常提的修改有两类:卡牌间距太小导致误触,以及槽位满 7 格后应立即判定失败并弹窗。
案例二:炸金花(规则判定类)
这类游戏的核心是牌型识别与比大小。逻辑密集,但规则边界清楚,很适合交给模型。下面的代码把牌面 r 取值为 1 到 13,其中 1 表示 A,花色 s 单独记录。
// 判定三张牌的牌型,rank 越大越强:豹子 6 > 顺金 5 > 金花 4 > 顺子 3 > 对子 2 > 单牌 1function evaluate(cards) {const rs = cards.map(c => c.r).sort((a, b) => a - b);const suited = cards.every(c => c.s === cards[0].s);const isTrip = rs[0] === rs[1] && rs[1] === rs[2];const isPair = !isTrip && (rs[0] === rs[1] || rs[1] === rs[2]);const normalRun = rs[1] - rs[0] === 1 && rs[2] - rs[1] === 1; // 含 A-2-3const highRun = rs[0] === 1 && rs[1] === 12 && rs[2] === 13; // Q-K-Aconst isRun = normalRun || highRun;if (isTrip) return { rank: 6, name: '豹子' };if (isRun && suited) return { rank: 5, name: '顺金' };if (suited) return { rank: 4, name: '金花' };if (isRun) return { rank: 3, name: '顺子' };if (isPair) return { rank: 2, name: '对子' };return { rank: 1, name: '单牌' };}// 同牌型时的比较键,按位逐一比较,数值越大越强function tieKey(cards) {const { rank } = evaluate(cards);const rs = cards.map(c => c.r).sort((a, b) => a - b);// 顺子与顺金只比最大牌:A-2-3 最小(按 3 计),Q-K-A 最大(按 14 计)if (rank === 3 || rank === 5) {if (rs[0] === 1 && rs[1] === 2 && rs[2] === 3) return [3];if (rs[0] === 1 && rs[1] === 12 && rs[2] === 13) return [14];return [rs[2]];}// 其余牌型:先按出现次数降序(对子、豹子的牌面优先),再按点数降序const vals = cards.map(c => (c.r === 1 ? 14 : c.r));const count = {};vals.forEach(v => { count[v] = (count[v] || 0) + 1; });return vals.sort((a, b) => count[b] - count[a] || b - a);}// 返回正数表示 a 大于 b,0 表示平局function compareHands(a, b) {const ra = evaluate(a).rank, rb = evaluate(b).rank;if (ra !== rb) return ra - rb;const ka = tieKey(a), kb = tieKey(b);for (let i = 0; i < ka.length; i++) {if (ka[i] !== kb[i]) return ka[i] - kb[i];}return 0;}
这段代码有三处容易出错,需要特别留意。
第一,数组要用下标取值。rs 是三张牌点数组成的数组,比较首张牌时要写 rs[0],不能直接写 rs。cards[0].s 同理。
第二,顺子判断必须在排序之后进行。排序后的 [1, 2, 3] 同时满足普通顺子条件,所以 A-2-3 无需单独写一条分支。[1, 2, 13] 不构成顺子,因此 K-A-2 不成顺。
第三,同牌型比大小的规则要写进 Prompt。本文约定先比牌型,牌型相同再比牌面,牌面完全相同则平局,花色不参与比较。你可以改成自己的规则,但要把规则写死,否则模型每次生成的版本可能不一样。
下面是一个验证例子。Q-K-A 应当大于 A-2-3:
const qka = [{ r: 12, s: '♠' }, { r: 13, s: '♥' }, { r: 1, s: '♦' }];const a23 = [{ r: 1, s: '♣' }, { r: 2, s: '♥' }, { r: 3, s: '♦' }];console.log(compareHands(qka, a23) > 0); // true
案例三:狙击游戏(实时交互类)
实时类游戏应当使用 requestAnimationFrame 作为主循环,而不是 setInterval。后者的帧率不稳定,命中判定也容易漂移。
let last = performance.now();function loop(now) {const dt = Math.min((now - last) / 1000, 0.05); // 限制单帧步长,防止切到后台再回来时大幅跳帧last = now;update(dt); // 更新靶子位置、倒计时等状态render(); // 绘制画面requestAnimationFrame(loop);}requestAnimationFrame(loop);// 命中判定:用圆形靶心做距离检测,比矩形碰撞更贴合视觉function onShoot(mx, my) {if (state.ammo <= 0) return;state.ammo--;const hit = state.targets.find(t =>Math.hypot(t.x - mx, t.y - my) <= t.r);if (hit) {state.score += hit.score;state.targets = state.targets.filter(t => t !== hit);}}
上面的代码只包含主循环和命中判定。update 与 render 需要在 Prompt 中要求模型补全,或自行实现。复制代码前请确认这两个函数已经存在,否则页面会报错。
常见问题排查表
| 现象 | 大概率原因 | 回贴给模型的描述 |
|---|---|---|
页面空白,控制台报 xxx is not defined |
函数定义晚于调用,或变量作用域错误 | 贴报错原文,并说明是第几步操作触发 |
| 手机点击没有反应 | 只绑定了 click,没有处理 touchstart | “请同时支持触屏,并阻止 300ms 延迟与双击缩放” |
| 卡牌点击穿透到下层 | 遮挡判定阈值不合理 | “上层牌与当前牌的重叠面积超过 60% 时,下层牌不可点击” |
| 游戏越快越卡 | 每帧都在创建和删除 DOM 节点 | “改为 Canvas 渲染,或复用节点池,避免逐帧增删 DOM” |
| 手机端布局错乱 | 缺少 viewport 设置,或没有处理安全区 | “补充 viewport meta 标签,并用 env(safe-area-inset-*) 适配刘海屏” |
| 音效不响 | 浏览器要求用户操作后才能播放音频 | “首次点击时初始化 AudioContext 并调用 resume” |
| 切到后台回来直接失败 | 倒计时用真实时间差计算 | “倒计时按累计 dt 递减,页面隐藏时暂停” |
检查清单:发布前逐项确认
- 桌面端与手机端都能完整通关一次
- 失败后可以一键重开,分数、倒计时、剩余牌数全部重置
- 控制台没有报错
- 没有外部资源依赖,断网也能玩
- 难度、时长、弹药等关键数值集中定义为常量,方便自行调整
- 页面标题与图标已改成你的游戏名
- 文件中没有密钥、个人信息或未授权的素材
agent-">进阶:从生成代码到 Agent 工作流
批量制作游戏,或者要做包含关卡数据、排行榜、素材管理的完整项目时,可以借助 ERNIE 5.5 Flash Preview 的 Agent 与工具调用能力,把流程拆成多个步骤:需求拆解、生成关卡 JSON 配置、生成游戏代码、调用本地命令做语法检查、读取报错并自动修正、输出构建产物。这类多步骤任务正是模型重点优化的方向。
其中的“执行命令”“生成图片素材”“渲染视频”等步骤,必须在具备相应工具和运行环境的 Agent 框架中完成。模型负责规划、生成代码和判断结果。
小结
- 把玩法写成结构化需求,规则、界面、交互、数值、胜负条件五项齐全。
- 要求单文件原生实现,双击即可运行,方便分享。
- 建立调试闭环,贴报错原文并描述现象,每轮只改一类问题。
- 发布走静态托管,拿到链接就能分享。
- 手感需要自己调。AI 给出的是可运行的版本,好不好玩取决于你玩了多少局。
现在就选一个你小时候玩过的小游戏,把上面的 Prompt 填好,在 ERNIE 5.5 Flash Preview 在线体验 中跑出第一版。想接入自己的应用或做批量生成,可以到 百度智能云千帆模型详情页 了解模型能力与接入方式。先做出一个能玩的版本,剩下的都是迭代问题。

登录后可评论,请前往 登录 或 注册