Guide: Prompt Engineering for Phaser Game Generation


AI coding assistants have gotten remarkably good at generating Phaser.js games — but only when you prompt them correctly. After building 20+ games on this site with DeepSeek V4 Flash, we’ve extracted a repeatable prompt engineering pattern that turns a vague idea into a working, playable game in under 100 lines of effective prompt text.

This guide walks through the pattern step by step, with real prompt templates you can copy.

Prerequisites

  • Access to an AI coding assistant (we use DeepSeek V4 Flash via OpenRouter)
  • Basic JavaScript knowledge (variables, functions, objects)
  • A browser to test the output
  • 20 minutes

Why Prompt Structure Matters

AI models don’t “know” what you want. They predict token sequences based on the context you provide. A vague prompt like “make a space game” leaves the model to guess everything — game genre, control scheme, art style, physics model, Phaser version. The result is almost always broken.

A well-structured prompt constrains the problem space. Every constraint you add eliminates a branch of possible failures. The goal is to reduce the model’s degrees of freedom to exactly the ones you want it to solve.

The pattern we use has four parts, applied in order:

  1. System constraints — Phaser version, CDN, file format, rendering requirements
  2. Game specification — What the player does, what the rules are, what winning/losing looks like
  3. Implementation details — Controls, visual style, data structures, score mechanics
  4. Anti-patterns to avoid — Things the model tends to get wrong that you want it to skip

Let’s walk through each one.

Step 1: Pin the Framework Version

Phaser has gone through major API changes across versions. Phaser 2, 3, and 3.60+ all have different APIs for scene creation, physics, and text rendering. If you don’t specify the version, the model might write code for Phaser 2 while you’re loading Phaser 3.80 from CDN.

Bad:

Create a Phaser game where you dodge asteroids.

Good:

Create a Phaser 3.80+ game using the CDN from cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js. Use ES5-compatible syntax (no ES modules). The game must use the Phaser.Scale.FIT configuration with CENTER_BOTH to handle responsive sizing.

This blocks three common failure modes: version mismatch, ES module import errors in a single-HTML setup, and broken scaling on mobile. The model knows exactly which API surface to target.

Step 2: Specify the Game Loop in Human Terms

AI models reason well about game loops when you describe them as cycles: input → update → collision check → render. Phaser’s update() method is exactly this loop. Describe what happens each frame, and what triggers state changes.

Prompt section template:

Game loop:
- Player moves left/right with arrow keys at speed 300
- Enemies spawn every 2 seconds from the top of the screen at random X positions
- Enemies fall downward at speed 150
- If an enemy overlaps the player, reset the game
- Player collects coins that spawn randomly (1 coin every 3 seconds)
- Score increases by 10 per coin collected
- Show score as text in the top-left corner
- Game resets on collision after a 1-second delay

This is the most important section. Every line maps to a line of code the model will write. If any line is ambiguous — like “enemies move toward the player” without specifying speed — the model guesses, and the guess is often wrong.

Step 3: Define Visual Style and Data Structures

Without visual constraints, models default to Phaser’s un-styled defaults — black text, white rectangles, no personality. Define the aesthetic in concrete terms.

Visual style:
- Background: dark blue (#0a0a2e)
- Player: green rectangle, 32x32 pixels
- Enemies: red circles, radius 16
- Coins: yellow circles, radius 10
- Text: white, 20px monospace
- No background image — use solid fill colors

Data structures matter too. Models sometimes create deeply nested objects that are hard to debug. Tell it exactly what state you want:

Data:
- score: integer (starts at 0)
- gameOver: boolean (starts false)
- Enemies stored in a group (this.enemies)
- Coins stored in a group (this.coins)

Step 4: Block Known Anti-Patterns

This is the trick that improved our first-pass success rate from ~40% to ~80%. Explicitly tell the model what NOT to do.

In our experience, these anti-patterns cause the most failures:

Anti-pattern Why it fails How to block
document.getElementById(...) inside Phaser Breaks Phaser lifecycle, canvas ownership issues “Do not use document.getElementById — use Phaser methods only”
Bare setInterval or setTimeout for game timing Phaser’s time system handles this natively; manual timers desync “Use this.time.addEvent for all timed spawning, not setInterval”
Default Phaser text (32px sans-serif, no word wrap) Looks unpolished, breaks at different viewports “Use monospace font at 20px for all text”
Ref to this inside non-arrow callbacks Loses scene context, TypeError on update “Use arrow functions for all callbacks, or store this as const scene = this
Manual canvas sizing Breaks Phaser.Scale.FIT “Do not set canvas.style.width — use Phaser.Scale configuration only”

Pro tip: Save your anti-pattern block as a reusable prompt fragment. We use the same one for every game:

Do NOT use: document.getElementById, setInterval, setTimeout, ES modules, import/export syntax, or manual canvas sizing. Use arrow functions for callbacks. Use this.time.addEvent for timers. Use Phaser.Scale.FIT with CENTER_BOTH.

Step 5: The Iteration Loop

Even with a perfect prompt, the first output often needs fixes. The iteration loop is:

  1. Copy the output into a .html file
  2. Open in browser — does the canvas render?
  3. Open DevTools Console — any errors?
  4. Feed errors back — copy the full error + stack trace + 5 lines of surrounding code back to the AI
  5. Repeat until it works

What usually breaks on first pass:

  • Typo in scene key names (Phaser is case-sensitive about scene keys)
  • Missing super() keyword in scene constructors
  • Physics group configuration mismatches (arcade vs matter physics)
  • Text objects positioned off-screen

Each of these is a ~30-second fix once you know what to look for.

Full Prompt Example

Here’s the complete prompt we used to generate the 3D Ball Balance tutorial game:

Create a single HTML file with embedded JavaScript that implements a 3D ball balance game using Three.js r128 loaded from cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js.

Game mechanics:
- A flat platform (square, 20 units wide) sits in the scene
- A blue sphere (radius 0.5) sits on top of the platform
- WASD or arrow keys tilt the platform on X and Z axes
- Ball rolls in the direction of the tilt (accelerated by gravity, slowed by friction)
- Golden coins (10 of them) are scattered on the platform surface
- Ball collects a coin when it touches it (coin disappears, score +1)
- If ball falls off the platform edge, game over (shows score, press R to restart)
- Score displayed top-left in 20px white monospace text

Implementation constraints:
- Do NOT use document.getElementById, setInterval, setTimeout, ES modules, or import/export
- Use arrow functions for all callbacks
- Physics is custom (gravity + friction vectors), not a physics engine
- Platform has a subtle grid texture (use a CanvasTexture or simple wireframe)
- Ambience: dark background (#111), single directional light, soft fill light
- Shadows enabled for ball and coins
- Coins rotate slowly and bob up/down
- Bot mode: when ?bot=true URL param is present, AI controls the ball by tilting toward the nearest coin

This prompt is 240 words and generated 329 lines of working Three.js code on the first try.

Key Takeaways

  1. Specify the version — Without a Phaser/Three.js version constraint, models write for the wrong API surface. Always pin it.

  2. Describe the game loop as a cycle — Input → update → collision → state change. The model maps this directly to Phaser’s update() lifecycle.

  3. Block anti-patterns explicitly — The model will default to setInterval, document.getElementById, and bad text styling unless you tell it not to. A reusable block list costs nothing and saves 2-3 iterations per game.

  4. One file, no modules — Single HTML files with CDN scripts and <script> tags eliminate dependency issues. ES modules in a local file require a server; plain scripts just work when double-clicked.

  5. Include the error in your next prompt — Models fix their own bugs 90% of the time when you give them the exact error message plus 10 lines of surrounding context. Just “it doesn’t work” gets you nowhere.