{
  "name": "2d-game",
  "title": "2D Game - Phaser 3 Games",
  "description": "Build 2D games with Phaser: scene structure, physics, input, sprites, genre recipes (platformer, scroller, arcade, puzzle, endless runner)",
  "guid": "sk_plat_2dgm",
  "category": "Templates",
  "requiredTools": [
    "add",
    "file_write",
    "project_deploy"
  ],
  "content": "# 2D Game - Phaser 3 Games\n\n**2D Game** is the Phaser-based game template on Gipity. All files are fully editable - no locked template layer. Uses Phaser 3.80.1 via CDN with arcade physics.\n\n**When to use this:** When the user asks for a 2D game - platformer, side-scroller, arcade, puzzle, endless runner, top-down, shooter, or RPG. For simple games (wordle, quiz, card games), use `web-simple`. For 3D multiplayer apps, use `3d-engine` for a blank template or `3d-world` for a playable rocket-launcher starter.\n\n## Quick Start - Start Here\n\n**STRONGLY RECOMMENDED:** Begin every 2D game by adding the `2d-game` template with `add`. It sets up Phaser 3, boot/game scenes, config, settings, and favicons for you. Only hand-roll files if the user explicitly tells you to skip the template.\n\n```\nadd name=2d-game title=\"<Game Name>\"\n```\n\n**Starting over in an existing project:** If `src/` already exists and the user wants a clean rebuild, call `file_delete` on `src` first, then run `add` normally. Or pass `force=true` to `add` to overwrite in one step - destructive, so confirm with the user first. Unrelated content (media, data, notes) is preserved either way.\n\n**Naming:** Use the user's name verbatim if given. If they didn't specify, blend \"Gip\" or \"Gipity\" into the name (e.g. \"Gipity Racer\", \"Gip Tac Toe\") - be creative but don't force it.\n\nThis creates a playable game immediately - colored rectangle player, keyboard controls, arcade physics, ground platform. Then edit `scenes/game.js` and `settings.js` to build your game.\n\n## Project Structure\n\n```\nsrc/\n  index.html            - Phaser CDN, game container, module entry\n  js/\n    config.js           - Phaser.Game config, scene registration\n    settings.js         - Tunable values (canvas, colors, physics, player, gameplay)\n    strings.js          - User-facing display text\n    scenes/\n      boot.js           - Preloader with progress bar\n      game.js           - Main game scene (gameplay logic)\n  css/\n    styles.css          - Page layout, canvas styling\n  images/\n    favicon-192.png\n    favicon.ico\n```\n\nAll files are editable. No read-only engine layer.\n\n## Phaser Essentials\n\n### Scene Lifecycle\nEvery scene has three key methods:\n- `preload()` - load assets (images, spritesheets, audio)\n- `create()` - set up game objects, physics, input\n- `update()` - runs every frame, game logic here\n\n### Loading Assets\nIn `boot.js` preload():\n```js\nthis.load.image('player', './images/player.png');\nthis.load.spritesheet('hero', './images/hero.png', { frameWidth: 32, frameHeight: 48 });\nthis.load.audio('jump', './audio/jump.mp3');\n```\n\n### Creating Game Objects\n```js\n// Sprite (from loaded image)\nthis.player = this.physics.add.sprite(400, 300, 'player');\n\n// Rectangle (no image needed)\nthis.player = this.add.rectangle(400, 300, 32, 48, 0xf26522);\nthis.physics.add.existing(this.player);\n\n// Static group (platforms)\nthis.platforms = this.physics.add.staticGroup();\nthis.platforms.create(400, 568, 'ground');\n```\n\n### Physics (Arcade)\n```js\n// Gravity is set in config.js via settings.physics.gravity\nbody.setVelocityX(speed);\nbody.setVelocityY(jumpForce);  // negative = up\nbody.setBounce(0.2);\nbody.setCollideWorldBounds(true);\n\n// Collisions\nthis.physics.add.collider(player, platforms);\nthis.physics.add.overlap(player, coins, collectCoin, null, this);\n```\n\n### Input\n```js\n// Keyboard\nthis.cursors = this.input.keyboard.createCursorKeys();  // up, down, left, right\nif (this.cursors.left.isDown) body.setVelocityX(-speed);\n\n// Specific keys\nthis.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);\n\n// Touch/pointer\nthis.input.on('pointerdown', (pointer) => { ... });\n```\n\n### Text\n```js\nthis.add.text(400, 30, 'Score: 0', {\n  fontSize: '24px', color: '#ffffff', fontStyle: 'bold',\n}).setOrigin(0.5);\n```\n\n### Camera\n```js\nthis.cameras.main.startFollow(this.player, true, 0.1, 0.1);\nthis.cameras.main.setBounds(0, 0, worldWidth, worldHeight);\n```\n\n## Common Phaser 3 Pitfalls\n\nThese are the failure modes that most often turn the whole screen black. Read before writing a game scene.\n\n- **`Graphics.fillEllipse` does NOT exist.** Phaser 3 Graphics has `fillCircle(x, y, r)`, `fillRect`, `fillTriangle`, `fillRoundedRect`, and `beginPath` + `arc` + `fillPath` - but no ellipse drawcall. For an oval, use `fillCircle` and set `gfx.scaleX` / `gfx.scaleY`, or draw it with `beginPath`/`arc`/`fillPath`.\n- **Don't attach physics to a raw `Graphics` object.** Graphics has no width/height for the body to size against, so the body ends up with `{0,0}` dimensions or drifts from the drawn shape. Use a `Sprite` (from a loaded image) or a `Rectangle` / `Zone` for the physics body, and draw a follower `Graphics` that tracks `body.x`/`body.y` each frame.\n- **Set world bounds for scrolling worlds.** The default physics world is the canvas size. For a side-scroller or large map, call `this.physics.world.setBounds(0, 0, worldWidth, height)` in `create()` - otherwise platforms and objects beyond the canvas get ignored by physics.\n- **Use object hashes, not sparse arrays, for spatial maps.** `this.segments = {}` with string/number keys works; `this.segments = []` with large indices becomes sparse and breaks `Object.keys`/`values` enumeration in subtle ways.\n- **Don't double-create physics bodies.** Either call `this.physics.add.existing(obj)` OR add `obj` to a `staticGroup` with `group.add(obj)` - not both. Double-creation silently misplaces the body relative to the visual.\n- **Always verify renders, not just console.** A missing API or a bad draw can throw once during init and leave a black canvas with nothing further to report. After deploying, capture a screenshot using the `browser` agent tool's `screenshot` action - don't trust \"clean console\" as proof.\n\n## Build Incrementally\n\nFor anything non-trivial, resist the urge to write the whole game in one `Write` call. Work in small, verified steps:\n\n1. Add the template (`gipity add 2d-game`) and deploy - confirm the starter game renders.\n2. Customize ONE element (e.g. replace the player rectangle with your sprite). Deploy, screenshot, confirm it renders.\n3. Add the next element (ground, enemies, collectibles) one at a time, deploying and verifying between each.\n4. Only after the core loop works, layer on polish (parallax, particles, HUD, touch controls).\n\nA 500-line rewrite of `scenes/game.js` is very hard to debug when something breaks - a single bad API call turns the whole screen black with no useful error. Small steps keep the failure surface tiny.\n\n## Verification After Deploy\n\nAfter every `gipity deploy dev`:\n- Run `gipity page-inspect <deploy-url>` to surface console errors.\n- **On the first deploy of a new game**, and any time you've made significant visual changes, also use the `browser` agent tool's `screenshot` action and look at the image. A clean console is NOT sufficient proof for Canvas/WebGL apps - render failures are often silent.\n- If you see a black screen with a clean console: assume a sync error fired during Phaser init (most commonly a missing API like `fillEllipse`, or physics attached to a raw Graphics). Re-read the \"Common Phaser 3 Pitfalls\" section above before rewriting.\n\n### Animations\n```js\nthis.anims.create({\n  key: 'walk',\n  frames: this.anims.generateFrameNumbers('hero', { start: 0, end: 3 }),\n  frameRate: 10,\n  repeat: -1,\n});\nthis.player.anims.play('walk', true);\n```\n\n## Adding a New Scene\n\n1. Create `src/js/scenes/myScene.js`:\n```js\nexport class MyScene extends Phaser.Scene {\n  constructor() { super('MyScene'); }\n  create() { /* ... */ }\n  update() { /* ... */ }\n}\n```\n\n2. Register in `config.js`:\n```js\nimport { MyScene } from './scenes/myScene.js';\n// Add to scene array: scene: [Boot, Game, MyScene],\n```\n\n3. Switch scenes: `this.scene.start('MyScene');`\n\n## Genre Recipes\n\n### Side-Scroller / Platformer\n- Keep gravity enabled (default)\n- Add platforms as static physics bodies\n- Camera follows player: `this.cameras.main.startFollow(player)`\n- Set world bounds larger than canvas: `this.physics.world.setBounds(0, 0, 3200, 600)`\n\n### Top-Down (Zelda-style)\n- Disable gravity: set `settings.physics.gravity = 0`\n- Add 4-way movement (up/down/left/right)\n- Use `body.setVelocity(x, y)` for diagonal movement\n\n### Endless Runner\n- Auto-scroll: move obstacles left each frame, spawn new ones off-screen right\n- Single input: jump on tap/space\n- Increase speed over time: `speed += dt * acceleration`\n\n### Arcade / Shooter\n- Bullets: `this.physics.add.group()` with `body.setVelocity()`\n- Enemy spawning: `this.time.addEvent({ delay: 1000, callback: spawnEnemy, loop: true })`\n\n### Puzzle\n- Disable gravity, disable physics or use minimal physics\n- Grid-based: snap positions to grid `Math.round(x / tileSize) * tileSize`\n- Input: pointer clicks to select/move pieces\n\n## Mobile / Touch\n\nPhaser handles touch automatically for pointer events. For virtual controls:\n```js\n// On-screen buttons\nconst jumpBtn = this.add.rectangle(700, 500, 80, 80, 0x333333, 0.5).setInteractive();\njumpBtn.on('pointerdown', () => { body.setVelocityY(jumpForce); });\n```\n\nFor mobile-responsive canvas, the template uses `Phaser.Scale.FIT` + `CENTER_BOTH` by default.\n\n## Deploy Verification\n\nUse the browser tool to verify deploys when it matters - first deploy, structural changes (new pages, new frameworks, changed imports), or when something might have broken. Skip verification for trivial changes (copy tweaks, style adjustments, config values).\n\nTo verify: `browser action=open url=<deployed-url>` - waits for async modules, captures console errors automatically. Check output for `[Console errors captured after page load]`. Use `browser action=screenshot` to confirm visual correctness.\n\n**Debugging in production:** Add `console.error()` calls to app code for diagnostics, redeploy, then use `browser action=console` to read the output. Remove debug logging when done.\n"
}
