从工具包构建手机网页游戏
web-game 模板是一个基于 Vite + React + TypeScript 的完整游戏(Coin Dash)。你的任务是替换其规则、视图、文本和美术资源,以实现用户的游戏。优先考虑手机:竖屏 390 x 844,单手操作。桌面端也必须能正常工作。
固定规则
- 在任何代码编辑之前,先启动实时预览(构建顺序第 1 步)。然后编写代码:用至多 10 行规划,然后进行编辑。每次只修改一个文件或一个功能,然后运行
bun run typecheck。 - 绝不重建工具包已有的内容(见下方列表)。不要添加游戏引擎、UI 库或 @react-three/fiber。
- 游戏规则是
src/games/<game>/logic.ts中的纯函数: 不能使用 React、DOM、绘图、Date.now()或Math.random()。使用seeded(seed)。相同的种子必须产生相同的游戏结果:每日挑战和挑战模式依赖于此。 - 无服务器,无登录。进度保存在
localStorage中(工具包已实现)。 - 文本中的每一个声明都必须与游戏的实际行为相符。
- “已验证”意味着已看到其实际运行:
bun run check:play已通过,并且你已查看了其在手机尺寸下的截图。从实时链接返回 HTTP 200 状态码不算测试。check:play是无头运行的,不需要桌面或浏览器窗口。
工具包已经具备的功能(不要重新构建)
- 固定步长循环、键盘和触摸输入(任意位置拖拽、摇杆、方向键、转向、动作按钮、多点触控、双人模式、在电脑上鼠标拖拽)、摄像机、碰撞检测、标签页隐藏时暂停。
- 在
src/ui/中提供 2D 和 3D 通用的游戏 UI:菜单、HUD 条(分数徽章、生命值、计时器、暂停按钮)、暂停界面、结果界面、解锁功能、玩法介绍与常见问题、旋转手机提示、弹窗提示、路由、已保存的个人资料。 - 在
src/ui/theme.ts中的主题:softCandy(默认)、glassNeon、cleanMinimal。访问?theme=glassNeon可以预览其他主题。 - 最高分和解锁功能;每日挑战(基于日期种子、从发布日算起的数字、连胜记录);挑战链接
?c=<seed>&s=<score>&n=<name>,带清理后的参数以及“你赢了 / 你输了”的结果和“发送回去”的按钮;使用navigator.share或剪贴板复制进行分享;首次点击后播放 Web Audio 音效,并可记住静音状态;振动功能静默失败。 track(name, data)函数,支持事件:game_open、game_start、game_over、play_engaged(45 秒)、share、challenge_open、daily_complete。在开发中事件会输出到控制台,在发布页面上会发送到 omg 分析平台。play.tsx中的useRun会发送这些事件;不要重复发送。- 100dvh、安全区域、无滚动或缩放、48 像素的触摸目标、减少动画效果。
- 基于
src/app/about.ts构建的 SEO:标题、描述、Open Graph 分享图像、VideoGame 和 FAQPage 结构化数据。安装清单来自 omg Vite 插件,使用public/icons/中的文件。
选择 2D 或 3D
默认为 2D(脚本压缩后约 100 KB)。仅在需要深度感时才使用 3D;它会添加 three.js 库(压缩后约 137 KB),在菜单显示期间加载,且绝不会出现在 2D 构建中。通过在 src/app/view.ts 中修改一行代码进行切换,然后删除未使用的视图文件。
选择控制方式
在 src/app/about.ts 中设置一次,配置 GAME.controls 对象:kind(控制类型)、touchHint(手机端菜单提示文本)和 keysHint(电脑端菜单提示文本)。play.tsx 会将 GAME.controls.kind 传递给 <TouchControls> 组件。这两个提示文本以及 howToPlay(玩法介绍)必须描述同一种控制方式。对于使用按钮的游戏,绝不要使用“拖拽”一词;对于拖拽游戏,绝不要使用“点击箭头”一词。如果提示文本中引用了其他控制方式,check:ui 和 check:play 检查将会失败。
| 游戏类型 | kind 值 | 玩家操作 | 规则读取方式 |
|---|---|---|---|
| 接挡、捕捉、跑酷、赛道躲避、弹弓 | drag | 在场地上任意位置拖拽 | axisX/axisY;使用 mode="absolute" 时,通过 dragTarget 让挡板位于手指下方 |
| 俯视角游戏、自由移动、双摇杆射击 | stick | 操作左下角的摇杆 | axisX/axisY |
| 网格移动:迷宫、贪吃蛇、推箱子 | dpad | 点击四个方向箭头 | axisX/axisY 或 wasTapped |
| 赛车、双键变道 | steer | 按住左或右 | axisX |
| 单次点击:flappy bird、跳跃、时机游戏 | buttons | 点击动作按钮 | wasTapped(input, "p1", "a") |
可以使用 buttons={[...]} 在任何控制类型上添加动作按钮。axes="x" 将拖拽限制为左右方向。键盘始终有效:方向键或 WASD,以及空格键。
构建顺序
- 首先预览,在编辑任何文件之前: 如果缺少
node_modules文件夹,在项目目录中运行bun install。然后在后台启动bun run dev(端口 5174)。调用omg_expose_port,端口设为 5174,不要使用expoGo。告诉用户:“预览已准备就绪,位于下方卡片中。它会在您构建时自动更新。” Vite 会在后续每次保存时重新加载预览。如果首次运行步骤已完成此操作,或者端口 5174 已被此项目的开发服务器占用,则直接进入步骤 2。 - 规划: 用一句话描述核心循环,选择控制类型(见上表)、胜利条件、失败条件、得分方式以及回合如何结束(计时、生命值或掉落物)。将规划写入
README.md。 - 规则: 将
coin-dash/logic.ts复制到src/games/<game>/logic.ts。保留createGame(players, seed)、stepGame(game, input, dt)、finalScore、events(用于音效)、UNLOCKS、shareLine这些函数和对象。使用axisX/axisY/wasTapped读取输入。 - 视图: 复制
view-2d.ts(画布:drawWorld、drawShadow、loadImage)或view-3d.ts(three.js:createStage、chaseCamera、billboard、checkerTexture;共享几何体、Lambert 材质、无阴影映射的 blob 阴影)。当设置了美术资源图像时绘制它,否则绘制占位形状。 - 连接: 在
src/app/play.tsx中修改导入、HUD 数值、FEEDBACK(每个事件对应的音效)、按钮以及run.finish({...})这一行。保留<TouchControls kind={GAME.controls.kind}>、<HudScore icon={ART.scoreIcon}>、run.step和run.finish。 - 文案: 填写
src/app/about.ts(游戏名称、包含搜索词的标题、描述、标语、控制方式、上线日期、游戏方向、3 到 5 步的玩法介绍、技巧提示、4 到 6 个常见问题)。 - 美术资源(必须): 工具包自带 Coin Dash 示例美术资源,这些资源绝不能出现在用户的游戏中。在
theme.ts中选择预设主题,或者自行扩展一个主题并设置客户的颜色。使用omg_generate_image生成图片,保持统一风格(一张精灵图,切割成透明 PNG,游戏总大小控制在 3 MB 以下),并替换所有以下项目:src/app/art.ts中的ART.hero(菜单标题下的美术图)和ART.scoreIcon(HUD 分数图标)。删除public/art/hero.svg和public/art/score.svg。ART.items(每种解锁一个)和ART.sprites。public/icons/(pwa-192x192.png、pwa-512x512.png、pwa-512x512-maskable.png、尺寸为 180 的apple-touch-icon.png)和public/og.png(1200 x 630)。 然后查看菜单、游戏和结果页面的截图:确保没有红色跑者、金币、金币徽章出现,除非游戏主题就是关于金币的。
- 检查: 依次运行
bun run typecheck、bun test、bun run check:ui,然后运行bun run check:play。check:play会在 390 x 844 的尺寸下通过触摸操作进行一个回合的测试:菜单、开始游戏、你的控制方式、暂停、恢复、结果界面、控制台错误、示例美术资源。打开.omg/ui-check/和.omg/play-check/中的每一个 PNG 截图。修复每一个 FAIL 和 WARN 错误,然后再次运行这些检查。在check:play通过之前,不要发布。 - 发布: 执行
omg_deploy。首次发布需要public/omg-store/1.png到3.png(菜单、游戏、结果界面在 390 x 844 尺寸下的截图)。等待一分钟,然后运行bun run check:play -- --url <上线后的链接>,并查看其截图。仅报告在此次检查中通过的项目。
只有在工具包的 Bug 阻碍你时才编辑 src/ui/、src/game/ 和 src/render/ 目录。将辅助函数添加到你自己的游戏文件夹中。
手机端规则
- 保留视口标签、
styles.css、page.ts。将 HUD 元素放在<Hud>组件内。重要的控制按钮保持在屏幕下半部分。 - 游戏中不要使用
setInterval、setTimeout或Date.now()来计算时间:使用dt增量时间。 - 不要使用
alert、confirm、prompt、外部字体或外部脚本。 - 3D 限制:最多几百个网格,重用几何体和材质,无后期处理,无大纹理。
- 在手机上,omg 安装栏位于游戏下方,游戏变为其上方的卡片。不要隐藏或移动它。
最终报告
- 上线后的链接。2. 用一句话描述游戏。3. 测试了哪些内容以及测试的尺寸,包括在上线链接上执行
check:play的结果。4. 菜单、游戏和结果界面的截图。5. 已知问题或后续想法(最多 5 条)。说明是否在真实手机上测试过 3D 游戏。
已学到的经验教训
- 一行中末尾的按钮如果因横向滚动而移出屏幕,会导致玩家无法操作。
check:ui会检测此类按钮的失败;请确保关键按钮在 360 像素宽的屏幕上可见。 - “已验证”的结论在部署后立刻得出,而此时线上网站仍在提供旧版本。请稍等片刻后重新检查。
- 游戏的副本会与源版本产生差异。请从单一文件夹构建;绝不修补副本。
- Fruit Catcher(10 月 22 日)在游戏有一对方向箭头按钮的情况下,提示文本却说“向左或向右拖拽移动篮子”,导致拖拽无效。正确设置
GAME.controls,并让check:play来驱动测试。 - 同一款游戏发布时带有工具包的红色跑者、金币、金币分数徽章和金币应用图标。步骤 7 会替换所有这些内容;
check:play会对任何残留的示例文件报错。 - 用户等待了 5 分钟未看到预览,因为端口从未暴露。步骤 1 必须在任何编辑之前完成。
- 代理写道“桌面浏览器未运行”,并仅通过 HTTP 检查了上线链接。
check:play是无头运行的;请直接在上线链接上运行它。 - 为新的自定义事件(
play_engaged)优化的广告被弃用了。请从标准目标开始投放广告,待该事件每周触发约 50 次后再切换。