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.
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.
Quick Start - Start Here
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.
add name=2d-game title="<Game Name>"
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.
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.
This 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.
Project Structure
src/
index.html - Phaser CDN, game container, module entry
js/
config.js - Phaser.Game config, scene registration
settings.js - Tunable values (canvas, colors, physics, player, gameplay)
strings.js - User-facing display text
scenes/
boot.js - Preloader with progress bar
game.js - Main game scene (gameplay logic)
css/
styles.css - Page layout, canvas styling
images/
favicon-192.png
favicon.ico
All files are editable. No read-only engine layer.
Phaser Essentials
Scene Lifecycle
Every scene has three key methods:
preload()- load assets (images, spritesheets, audio)create()- set up game objects, physics, inputupdate()- runs every frame, game logic here
Loading Assets
In boot.js preload():
this.load.image('player', './images/player.png');
this.load.spritesheet('hero', './images/hero.png', { frameWidth: 32, frameHeight: 48 });
this.load.audio('jump', './audio/jump.mp3');
Creating Game Objects
// Sprite (from loaded image)
this.player = this.physics.add.sprite(400, 300, 'player');
// Rectangle (no image needed)
this.player = this.add.rectangle(400, 300, 32, 48, 0xf26522);
this.physics.add.existing(this.player);
// Static group (platforms)
this.platforms = this.physics.add.staticGroup();
this.platforms.create(400, 568, 'ground');
Physics (Arcade)
// Gravity is set in config.js via settings.physics.gravity
body.setVelocityX(speed);
body.setVelocityY(jumpForce); // negative = up
body.setBounce(0.2);
body.setCollideWorldBounds(true);
// Collisions
this.physics.add.collider(player, platforms);
this.physics.add.overlap(player, coins, collectCoin, null, this);
Input
// Keyboard
this.cursors = this.input.keyboard.createCursorKeys(); // up, down, left, right
if (this.cursors.left.isDown) body.setVelocityX(-speed);
// Specific keys
this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
// Touch/pointer
this.input.on('pointerdown', (pointer) => { ... });
Text
this.add.text(400, 30, 'Score: 0', {
fontSize: '24px', color: '#ffffff', fontStyle: 'bold',
}).setOrigin(0.5);
Camera
this.cameras.main.startFollow(this.player, true, 0.1, 0.1);
this.cameras.main.setBounds(0, 0, worldWidth, worldHeight);
Common Phaser 3 Pitfalls
These are the failure modes that most often turn the whole screen black. Read before writing a game scene.
Graphics.fillEllipsedoes NOT exist. Phaser 3 Graphics hasfillCircle(x, y, r),fillRect,fillTriangle,fillRoundedRect, andbeginPath+arc+fillPath- but no ellipse drawcall. For an oval, usefillCircleand setgfx.scaleX/gfx.scaleY, or draw it withbeginPath/arc/fillPath.- Don't attach physics to a raw
Graphicsobject. 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 aSprite(from a loaded image) or aRectangle/Zonefor the physics body, and draw a followerGraphicsthat tracksbody.x/body.yeach frame. - 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)increate()- otherwise platforms and objects beyond the canvas get ignored by physics. - Use object hashes, not sparse arrays, for spatial maps.
this.segments = {}with string/number keys works;this.segments = []with large indices becomes sparse and breaksObject.keys/valuesenumeration in subtle ways. - Don't double-create physics bodies. Either call
this.physics.add.existing(obj)OR addobjto astaticGroupwithgroup.add(obj)- not both. Double-creation silently misplaces the body relative to the visual. - 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
browseragent tool'sscreenshotaction - don't trust "clean console" as proof.
Build Incrementally
For anything non-trivial, resist the urge to write the whole game in one Write call. Work in small, verified steps:
- Add the template (
gipity add 2d-game) and deploy - confirm the starter game renders. - Customize ONE element (e.g. replace the player rectangle with your sprite). Deploy, screenshot, confirm it renders.
- Add the next element (ground, enemies, collectibles) one at a time, deploying and verifying between each.
- Only after the core loop works, layer on polish (parallax, particles, HUD, touch controls).
A 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.
Verification After Deploy
After every gipity deploy dev:
- Run
gipity page-inspect <deploy-url>to surface console errors. - On the first deploy of a new game, and any time you've made significant visual changes, also use the
browseragent tool'sscreenshotaction and look at the image. A clean console is NOT sufficient proof for Canvas/WebGL apps - render failures are often silent. - 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.
Animations
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('hero', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1,
});
this.player.anims.play('walk', true);
Adding a New Scene
- Create
src/js/scenes/myScene.js:
export class MyScene extends Phaser.Scene {
constructor() { super('MyScene'); }
create() { /* ... */ }
update() { /* ... */ }
}
- Register in
config.js:
import { MyScene } from './scenes/myScene.js';
// Add to scene array: scene: [Boot, Game, MyScene],
- Switch scenes:
this.scene.start('MyScene');
Genre Recipes
Side-Scroller / Platformer
- Keep gravity enabled (default)
- Add platforms as static physics bodies
- Camera follows player:
this.cameras.main.startFollow(player) - Set world bounds larger than canvas:
this.physics.world.setBounds(0, 0, 3200, 600)
Top-Down (Zelda-style)
- Disable gravity: set
settings.physics.gravity = 0 - Add 4-way movement (up/down/left/right)
- Use
body.setVelocity(x, y)for diagonal movement
Endless Runner
- Auto-scroll: move obstacles left each frame, spawn new ones off-screen right
- Single input: jump on tap/space
- Increase speed over time:
speed += dt * acceleration
Arcade / Shooter
- Bullets:
this.physics.add.group()withbody.setVelocity() - Enemy spawning:
this.time.addEvent({ delay: 1000, callback: spawnEnemy, loop: true })
Puzzle
- Disable gravity, disable physics or use minimal physics
- Grid-based: snap positions to grid
Math.round(x / tileSize) * tileSize - Input: pointer clicks to select/move pieces
Mobile / Touch
Phaser handles touch automatically for pointer events. For virtual controls:
// On-screen buttons
const jumpBtn = this.add.rectangle(700, 500, 80, 80, 0x333333, 0.5).setInteractive();
jumpBtn.on('pointerdown', () => { body.setVelocityY(jumpForce); });
For mobile-responsive canvas, the template uses Phaser.Scale.FIT + CENTER_BOTH by default.
Deploy Verification
Use 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).
To 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.
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.