构建游戏
构建一个可玩、正确的浏览器游戏——而不是静态截图。游戏就是一个 React 路由,带有一个 <canvas>(或 R3F 的 <Canvas>)加上 DOM 叠加 UI。使用 design-ui 技能设计叠加层(开始屏幕、HUD、菜单);本技能掌控游戏循环和世界。
控制 / 反向 A/D: 在编写 WASD、转向或飞行输入之前,请打开 .grok/skills/controls/SKILL.md。如果你只读了本文件或一类的游戏手册,载具/飞行演示的 A/D 常常是反的。
范围说明——单人、机器人或小型 P2P 合作:
- 默认发布单人或单人 + AI/机器人。
- 2-8 人合作 / 休闲实时(共享光标、派对游戏、朋友间的休闲动作)受支持——使用
multiplayer-p2p技能(WebRTC 网状网络,通过/api/rtc信令)。先阅读其信任模型。 - P2P 是当前唯一支持的多玩家方式。不要半吊子地构建无法连接的 Socket。
参考资料(按需加载):
controls技能(../controls/)——必需用于移动/转向/飞行:玩家可见的 A/D、反向转向反模式、飞行副翼、强制性自检 +window.__controlsTest。对于载具/飞机来说不可选。references/threejs-foundational.md——深入的 3D/循环/性能参考。对于 3D 务必阅读。references/3d-libs.md——three.js + @react-three/fiber + drei + rapier 的使用。threejs技能(../threejs/)——官方的完整 Three.js + TSL API 转储(llms-full.txt)。用于高级材质/着色器/WebGPU/加载器;不用于简单的 2D Canvas 游戏。references/babylon.md——Babylon.js,功能齐全的 3D 引擎替代方案。references/phaser.md——Phaser 3,2D 游戏的默认引擎。references/ecs-architecture.md——大型游戏的实体-组件-系统结构。references/genres/*.md——按类型游戏手册(FPS、2D 平台游戏、竞速卡丁车、消除匹配三消/俄罗斯方块、Voxel/Minecraft、无尽跑酷、俯视双摇杆、塔防、棋盘卡牌象棋)。类型文件不能替代controls。references/game-feel-juice.md、input.md、audio.md、collision-physics.md、save-persistence.md、procedural-generation.md、ai-pathfinding.md。game-asset-core(+game-animation-frames/game-tilesets/game-character-consistency/game-ui-icons)——生成精灵、图集、瓦片或 UI 时的引擎就绪 2D 美术默认值与验证(参见 §6)。
为构建选择具体的类型/专题参考;本文件是循环/世界的通用核心。输入符号 → controls。 2D 游戏美术 → game-asset-core。
1. 游戏循环与计时(#1 正确性问题)
- 使用引擎的 RAF 循环驱动循环(
renderer.setAnimationLoop、R3F 的useFrame或 2D Canvas 的requestAnimationFrame)。绝对不要使用setInterval/setTimeout/Date.now()进行游戏计时。 - 将所有移动/动画按 delta 时间缩放(秒),使速度与帧率无关(60Hz vs 144Hz)。每帧计算一次 delta 并重复使用。
- three.js:使用
THREE.Timer(而不是Clock——在同一帧第二次调用Clock.getDelta()时返回 ~0,这是一个经典的冻结错误)。
- three.js:使用
- 限制 delta(
min(delta, 0.1)),防止后台标签页导致物体瞬移。 - 物理/游戏逻辑的固定时间步长: 累积 delta,以固定速率(例如 1/60)步进模拟,同时以显示速率渲染——防止穿透和非确定性。
2. 控制(委托给 controls 技能)
在实现任何 WASD / 转向 / 飞行代码之前,请打开 .grok/skills/controls/SKILL.md。 该技能是以下内容的权威来源:
- 玩家可见的 A = 左 / D = 右(追尾摄像机,同时向前移动)
- 为什么
KeyA → steer−+yaw += steer * +rate是反向的(最常见的错误) - 载具 vs FPS(扫射 ≠ 转向)、固定翼副翼、直升机/无人机注意事项
- 强制性自检 +
window.__controlsTest探测
不要将 genres/racing-kart.md 视为转向符号的唯一来源——飞机、水上摩托和机甲永远不会打开它。
简短提醒(完整细节见 controls):
// 载具偏航主体(追尾摄像机):在此基向量下,A 必须增加偏航角
forward = (-sin(yaw), 0, -cos(yaw))
// KeyA → steer = +1; yaw += steer * turnRate * speedFactor * dt
// 错误(反向):KeyA → steer = -1; yaw += steer * turnRate * dt
- 指针锁定: 仅鼠标视角——自行实现 WASD;点击即玩的叠加层;锁定后消失。
- 使用按住状态 + dt 追踪按键;通过
references/input.md统一输入设备。 - 完成: 运行
controls技能检查清单。仅截图对于载具/飞行是不够的。
3. 3D 朝向与世界对象(“侧向/向后”错误)
- three.js 是右手坐标系,+Y 向上:+X 向右,+Y 向上,+Z 朝向观察者。网格面向 +Z;摄像机看向 −Z(经典的“摄像机朝向反”陷阱)。
- 像
Cone/Cylinder这样的基本几何体默认指向 +Y → 需要旋转以使尖端朝向正面(geo.rotateX(Math.PI/2))。 - 将网格朝向
forward(网格面向 +Z):最简单正确的方式是mesh.lookAt(mesh.position.clone().add(forward))。要手动构建基向量,将 +Z 列设为forward,并选择能保持正确右手旋转的 x 轴(det = +1):xAxis = normalize(cross(up, forward)),然后makeBasis(xAxis, up, forward)。 注意这是cross(up, forward)——而不是 §2 中的移动right = cross(forward, up)。以 +Z 为目标(而不是摄像机的 −Z)会翻转 x 轴的符号,使得xAxis × up = forward;坐标系保持右手系(无镜像)。不要对网格使用makeBasis(xAxis, up, -forward)——这会把forward设为反向(摄像机的约定),导致网格面向后方。 - 将摄像机朝向
forward(摄像机看向 −Z):将 +Z 列设为-forward——xAxis = normalize(cross(forward, up)),然后makeBasis(xAxis, up, -forward)(或者直接用camera.lookAt(target))。 - 保持一致的世界
up,使物体保持竖直;仅旋转平坦的基本几何体使其站起来。 - 验证 glTF 导入朝向;使用
AxesHelper/ArrowHelper调试。竖直自检:角色站在地面上,不是躺着或沉入地面。
4. 摄像机必须与移动一致
- 为移动保留专用的
moveForward/moveRight,一次计算,永不因摄像机代码而改变(别名化共享临时向量会导致摄像机和移动不一致——这是一个真实的再现错误)。 - 第三人称跟随:
desired = playerPos + up*height + moveForward*(-followDist);使用指数平滑(按 delta 缩放)将摄像机插值到该位置,lookAt(player)。 - 分层调试顺序:(1)按键注册 → (2)移动符号正确 → (3)摄像机一致。按此顺序修复。
5. 性能
- 最小化绘制调用(
renderer.info.render.calls,目标 <100):共享材质,对重复对象使用InstancedMesh/BatchedMesh,图集。 - 自己处理 GPU 资源释放(在关卡切换时调用
geometry/material/texture.dispose())——three.js 不会自动 GC。对象池化子弹/敌人/粒子。 - 避免每帧分配(重用临时向量)。压缩纹理;远距离使用 LOD。
6. 资源(避免生成照片陷阱)
- 交互式 3D 元素(武器模型、角色、道具、投射物)→ 使用 3D 几何体 / glTF 构建,而不是生成图像。用一张持枪的照片级真实 JPG 作为 FPS 模型看起来不对,无法动画,而且(JPG 没有 Alpha)会渲染成一个黑框。将一个真实的 3D 模型作为覆盖层渲染层挂接到摄像机。
- 仅将图像生成保留给平面 2D 资源(纹理、天空/菜单背景、2D 精灵、UI 美术)。绝对不要使用生成的照片作为 3D 网格、模型或角色替代品——用 3D 几何体 / glTF 构建。在绘制到 Canvas/纹理的图像上设置
crossOrigin="anonymous"。 请参阅imagine-grok-build技能(仅限 2D——图像工具无法生成真正的 3D)。 - 引擎就绪游戏美术规范 → 打开
game-asset-core(../game-asset-core/)获取默认值 + 盲验 + 重试纪律,然后是对应的专家:game-animation-frames(循环/运动规则)、game-tilesets、game-character-consistency、game-ui-icons。这些是质量控制/规范,不是导出流水线。当需要真实美术时,不要使用火柴人占位符。 - 2D 游戏精灵 / 动画图 → 运行
generate2dsprite(.grok/skills/generate2dsprite/SKILL.md):实色#FF00FF品红imagine_text_to_image图 + 色度后处理脚本(处理器需要品红)。将透明 PNG/GIF 接入 Canvas/Phaser。仍然应用game-asset-core(+ 相关时的动画/角色专家)。 - 2D 地图 / 关卡 / 道具包 → 打开
generate2dmap(.grok/skills/generate2dmap/SKILL.md)。优先使用仅基础底层 + 单独的道具/碰撞来构建可玩地图。浏览器默认:raw_canvas/ Phaser。可平铺地面/墙壁 → 同时需要game-tilesets进行 2×2 接缝检查。 - 可选更密集的移动动画 → 运行
video2dsprite(Grok 的imagine_image_to_video+ 沙箱脚本;品红基础)。对于清晰的生产级主角,优先使用generate2dsprite。使用game-animation-frames进行循环/翻转测试规则;在此沙箱中,优先使用video2dsprite而不是临时的仅限 ffmpeg 的采集。
7. 音频、存档、手感
- 音频:在首次用户手势(点击开始)上解锁
AudioContext,否则 iOS 静音;在visibilitychange时重新恢复。(references/audio.md) - 存档:使用
localStorage/IndexedDB,包含version字段 + 迁移逻辑。 - 手感:屏幕震动、击中停顿、缓动补间、粒子——成本低,感知质量提升巨大。保持表现层与模拟层分离。
8. 移动端
- 区分 Canvas 缓冲区大小与 CSS 大小;尊重
devicePixelRatio。 touch-action: none,按基础分辨率信箱适配,处理屏幕方向。- 触屏控制(虚拟摇杆 + 动作按钮),目标区域 ≥44px。
技术栈 / 引擎选择
- 3D → three.js,理想情况下通过 @react-three/fiber + drei(适合 React 应用;drei 提供指针锁定/控制/加载器)+ @react-three/rapier 用于物理/角色控制器。参见
references/3d-libs.md。 - 2D → 对于贪吃蛇、俄罗斯方块、Flappy Bird、平台游戏来说,原生 Canvas 2D 就足够了;只在类型需要时使用 Phaser。
- 这些游戏依赖未预先安装——需要
npm install它们(并确保它们出现在package.json中,以便 Vercel 构建能用到)。
完成标准(在声明“完成”之前)
- 无控制台错误加载;可见的游戏画面(不是空白 Canvas)。
controls技能自检通过(从追尾摄像机视角向前移动时 A = 左 / D = 右;如果反向则翻转一个符号)。不限于截图。- 3D 竖直与摄像机一致自检通过(§3, §4)。
- 在移动端视口上运行,带有触屏控制。
- 生产构建(
npm run build)能渲染构建后的输出,而不仅仅是开发环境。 - 分享 / X 卡片: 打开
og技能——自定义public/og.jpg以及src/lib/og/site.json中的"type": "x:game"。X 使用og:type="x:game"将链接预览展示为游戏卡片;不要使用twitter:card或为此发明x:type。当 Canvas 应用省略site.json字段时,browser-smoke/brand-check会发出警告。