12 KiB
name, description, metadata, user-invocable
| name | description | metadata | user-invocable | ||
|---|---|---|---|---|---|
| building-games | Build browser games and interactive/canvas/3D experiences in this TanStack Start + React app. Use for any game, simulation, or WebGL/Canvas experience — 2D or 3D, single-player. Covers the game loop & timing, 3D orientation/camera conventions, collision, performance, assets, audio, save, game feel, and per-genre playbooks. For WASD / vehicle / flight **input signs and inverted A/D**, open the **`controls`** skill — do not rely on this file or racing-kart alone. Triggers on "game", "minecraft", "fps", "platformer", "racing", "tetris", "snake", "shooter", "3d", "three.js", "canvas", "voxel", "physics". |
|
false |
Building Games
Build a playable, correct browser game — not a static screenshot. A game is
just a React route with a <canvas> (or <Canvas> for R3F) plus DOM overlay UI.
Style the overlay (start screen, HUD, menus) with the design-ui skill; this
skill owns the gameplay loop and world.
Controls / inverted A/D: open .grok/skills/controls/SKILL.md before
writing WASD, steering, or flight input. Vehicle/flight demos often ship with
A/D flipped if you only read this file or a single genre playbook.
Scope note — single-player, bots, or small P2P co-op:
- Ship single-player or single-player + AI/bots by default.
- 2–8 player co-op / casual realtime (shared cursors, party games, casual
action among friends) is supported — use the
multiplayer-p2pskill (WebRTC mesh, signaled at/api/rtc). Read its trust model first. - P2P is the only supported multiplayer right now. Do not half-build sockets that can’t connect.
References (load on demand):
controlsskill (../controls/) — required for movement/steer/flight: player-visible A/D, inverted-steer anti-pattern, flight ailerons, mandatory self-test +window.__controlsTest. Not optional for vehicles/planes.references/threejs-foundational.md— the deep 3D/loop/perf reference. Read for 3D.references/3d-libs.md— three.js + @react-three/fiber + drei + rapier usage.threejsskill (../threejs/) — official full Three.js + TSL API dump (llms-full.txt). Load for advanced materials/shaders/WebGPU/loaders; not for simple 2D canvas games.references/babylon.md— Babylon.js, the batteries-included 3D engine alternative.references/phaser.md— Phaser 3, the default engine for 2D games.references/ecs-architecture.md— entity-component-system structure for larger games.references/genres/*.md— per-genre playbooks (fps, platformer-2d, racing-kart, puzzle-match3-tetris, voxel-minecraft, endless-runner, topdown-twin-stick, tower-defense, board-card-chess). Genre files do not replacecontrols.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) — engine-ready 2D art defaults and verification when generating sprites, sheets, tiles, or UI (see §6).
Pick the specific genre/topic reference for the build; this file is the universal
core for loop/world. Input signs → controls. 2D game art → game-asset-core.
1. Game loop & timing (the #1 correctness issue)
- Drive the loop with the engine's RAF loop (
renderer.setAnimationLoop, R3FuseFrame, orrequestAnimationFramefor 2D canvas). NeversetInterval/setTimeout/Date.now()for game timing. - Scale ALL movement/animation by delta time (seconds) so speed is frame-rate
independent (60Hz vs 144Hz). Compute delta once per frame and reuse it.
- three.js: use
THREE.Timer(notClock—Clock.getDelta()returns ~0 on a second call in the same frame, a classic freeze bug).
- three.js: use
- Cap delta (
min(delta, 0.1)) so a backgrounded tab doesn't teleport things. - Fixed timestep for physics/gameplay: accumulate delta and step simulation at a fixed rate (e.g. 1/60) while rendering at display rate — prevents tunneling and non-determinism.
2. Controls (delegate to the controls skill)
Open .grok/skills/controls/SKILL.md before implementing any WASD / steer /
flight code. That skill is the source of truth for:
- Player-visible A = left / D = right (chase cam, while moving forward)
- Why
KeyA → steer−+yaw += steer * +rateinverts (most common bug) - Vehicle vs FPS (strafe ≠ steer), fixed-wing ailerons, heli/drone notes
- Mandatory self-test +
window.__controlsTestprobe
Do not treat genres/racing-kart.md as the only place steer signs live —
planes, jetskis, and mechs never open it.
Short reminder (full detail in controls):
// Vehicle yaw body (chase cam): A must increase yaw with this basis
forward = (-sin(yaw), 0, -cos(yaw))
// KeyA → steer = +1; yaw += steer * turnRate * speedFactor * dt
// WRONG (ships inverted): KeyA → steer = -1; yaw += steer * turnRate * dt
- Pointer lock: mouse-look only — implement WASD yourself; click-to-play overlay; dismiss on lock.
- Track keys with held state + dt; unify devices via
references/input.md. - Finish: run the
controlsskill checklist. Screenshot-only is insufficient for vehicles/flight.
3. 3D orientation & world objects (the "sideways/backwards" bugs)
- three.js is right-handed, +Y up: +X right, +Y up, +Z toward viewer. Meshes face +Z; cameras look −Z (the classic "camera backwards" gotcha).
- Primitives like
Cone/Cylinderpoint +Y by default → rotate to align a tip with forward (geo.rotateX(Math.PI/2)). - Orienting a mesh to face
forward(meshes face +Z): simplest correct way ismesh.lookAt(mesh.position.clone().add(forward)). To build the basis by hand, set the +Z column toforwardand choose the x-axis that keeps it a proper right-handed rotation (det = +1):xAxis = normalize(cross(up, forward)), thenmakeBasis(xAxis, up, forward). Note this iscross(up, forward)— not the movementright = cross(forward, up)from §2. Targeting +Z (instead of a camera's −Z) flips the x-axis sign so thatxAxis × up = forward; the frame stays right-handed (no mirroring). Do not usemakeBasis(xAxis, up, -forward)for a mesh — that targets −forward (the camera convention) so the mesh faces backwards. - Orienting a camera to look along
forward(cameras look −Z): set the +Z column to-forward—xAxis = normalize(cross(forward, up)), thenmakeBasis(xAxis, up, -forward)(or justcamera.lookAt(target)). - Keep a consistent world
upso objects stay upright; only rotate flat primitives to stand up. - Verify glTF import orientation; debug with
AxesHelper/ArrowHelper. Upright self-test: characters stand on the ground plane, not lying/sunk.
4. Camera must agree with movement
- Keep a dedicated
moveForward/moveRightfor movement, computed once and never mutated by camera code (aliasing a shared temp vector makes camera and movement disagree — a real repro bug). - Third-person follow:
desired = playerPos + up*height + moveForward*(-followDist); lerp the camera toward it (use exp-based smoothing, delta-scaled),lookAt(player). - Isolate-the-layer debug order: (1) keys register → (2) movement signs correct → (3) camera agrees. Fix in that order.
5. Performance
- Minimize draw calls (
renderer.info.render.calls, target <100): share materials,InstancedMesh/BatchedMeshfor repeated objects, atlases. - Dispose GPU resources yourself (
geometry/material/texture.dispose()) on level change — three.js does not GC them. Pool bullets/enemies/particles. - No per-frame allocations (reuse temp vectors). Compress textures; LOD for distance.
6. Assets (avoid the generated-photo trap)
- Interactive 3D elements (weapon viewmodels, characters, props, projectiles) → build from 3D geometry / glTF, not a generated image. A flat photorealistic JPG of a gun-in-hands used as an FPS viewmodel looks wrong, can't animate, and (JPG has no alpha) renders as a black box. Parent a real 3D viewmodel to the camera as an overlay render layer.
- Reserve image generation for flat 2D assets only (textures, sky/menu
backgrounds, 2D sprites, UI art). Never use a generated photo as a 3D mesh,
viewmodel, or character substitute — build those in 3D geometry / glTF.
Set
crossOrigin="anonymous"on images drawn to canvas/textures. See theimagineskill (2D only — image tools cannot produce real 3D). - Engine-ready game art doctrine → open
game-asset-core(../game-asset-core/) for defaults + blind verify + retry discipline, then the matching specialist:game-animation-frames(loop / motion laws),game-tilesets,game-character-consistency,game-ui-icons. These are QC/doctrine, not the export pipeline. Do not ship stick-figure placeholders when real art is expected. - 2D game sprites / animation sheets → run
generate2dsprite(.grok/skills/generate2dsprite/SKILL.md): solid#FF00FFmagentaimagine_text_to_imagesheets + chroma postprocess scripts (magenta is required for the processor). Wire transparent PNGs/GIFs into Canvas/Phaser. Still applygame-asset-core(+ animation/character specialists when relevant). - 2D maps / levels / prop packs → open
generate2dmap(.grok/skills/generate2dmap/SKILL.md). Prefer foundation-only base + separate props/collision for playable maps. Browser default:raw_canvas/ Phaser. Tileable ground/walls → alsogame-tilesetsfor 2×2 seam checks. - Optional denser locomotion → run
video2dsprite(Grokimagine_image_to_video+ sandbox scripts; magenta base). Prefergenerate2dspritefor crisp production heroes. Usegame-animation-framesfor loop/flip-test laws; prefervideo2dspriteover ad-hoc ffmpeg-only harvest in this sandbox.
7. Audio, save, feel
- Audio: unlock
AudioContexton the first user gesture (tap-to-start) or iOS is silent; re-resume onvisibilitychange. (references/audio.md) - Save:
localStorage/IndexedDB with aversionfield + migrations. - Juice: screen shake, hit-stop, eased tweens, particles — cheap, huge perceived-quality lift. Keep presentation separate from simulation.
8. Mobile
- Distinguish canvas buffer size from CSS size; respect
devicePixelRatio. touch-action: none, letterbox-fit to a base resolution, handle orientation.- Touch controls (virtual joystick + action buttons), ≥44px targets.
Stack / engine choice
- 3D → three.js, ideally via @react-three/fiber + drei (fits the React
app; drei gives pointer-lock/controls/loaders) + @react-three/rapier for
physics/character controllers. See
references/3d-libs.md. - 2D → native Canvas 2D is enough for snake/tetris/flappy/platformer; reach for Phaser only when the genre needs it.
- These game deps are not preinstalled —
npm installthem (and make sure they land inpackage.jsonso the Vercel build has them).
Finish criteria (before "done")
- Loads with no console errors; visible gameplay (not a blank canvas).
controlsskill self-test passed (A = left / D = right from chase cam while moving forward; flip one sign if inverted). Not screenshot-only.- 3D upright & camera-agrees self-tests pass (§3, §4).
- Runs on mobile viewport with touch controls.
- Production build (
npm run build) renders the built output, not just dev. - Share / X card: open the
ogskill — custompublic/og.jpgand"type": "x:game"insrc/lib/og/site.json. X usesog:type="x:game"to present the unfurl as a game card; do not usetwitter:cardor inventx:typefor this.browser-smoke/brand-checkwarn when canvas apps omit thesite.jsonfield.