Skip to content
Phaser Getting Started — Complete Beginner's Guide (Build Your First 2D Game)

Phaser Getting Started — Complete Beginner's Guide (Build Your First 2D Game)

DodaTech Updated Jun 6, 2026 12 min read

Phaser Getting Started — Complete Beginner’s Guide (Build Your First 2D Game)

Learn Phaser.js from scratch: set up a game canvas, understand scenes, load sprites and audio, handle keyboard input, and build your first 2D browser game.

What You’ll Learn

  • What a Phaser game config object does and how to write one
  • How scenes work — think of them as acts in a play
  • Loading images, spritesheets, and audio into your game
  • Moving a character with keyboard arrow keys
  • Building a complete collect-the-coins mini game from scratch

Phaser is the most popular 2D game framework for the browser — but you don’t need to be a game developer to use it. By the end, you’ll have a working game.

Why Getting Started with Phaser Matters

Every game begins with a foundation: a canvas to draw on, a loop that runs 60 times per second, and a way to respond to player input. Phaser handles all of this so you can focus on gameplay.

At DodaTech, we build interactive security training simulators that teach penetration testing and defensive strategies through gamified scenarios. These simulators use Phaser game mechanics — collision detection for zone alerts, sprite management for character movements, and scene transitions for module navigation. The same skills you learn here power real-world training tools used by security teams.

Prerequisites: You need basic JavaScript (variables, functions, classes) and HTML (script tags, DOM elements). No game development experience required.

Learning Path

    flowchart LR
    A[Getting Started] --> B[Sprites & Physics]
    B --> C[Scenes & State]
    C --> D[Input & Audio]
    D --> E[Tilemaps & Level Design]
    A -->|You are here| A
  

Phaser vs Raw Canvas: When to Use What

ApproachBest ForTrade-off
Phaser2D games, interactive simsHeavier (900KB) but full-featured
Raw Canvas APISimple drawing, data vizManual physics, input, audio
WebGL directly3D, complex shadersSteep learning curve

Phaser wraps Canvas and WebGL rendering into one consistent API. You write your game logic once; Phaser picks the best renderer.

Setting Up Phaser — The Container Analogy

Think of a Phaser game like setting up a stage for a play. You need:

  • A stage (the HTML page)
  • A curtain area (a <div> where the game renders)
  • A playbill (the config object that tells Phaser what to do)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My Phaser Game</title>
  <script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script>
</head>
<body>
  <div id="game"></div>
  <script>
    // game code here
  </script>
</body>
</html>

The <script> tag loads Phaser from a CDN — no installation needed. For local development, you can also install via npm: npm install phaser and import it with a bundler like Webpack.

The Game Config — Your Blueprint

Every Phaser project starts with a config object. It’s like a blueprint that tells Phaser: “Here’s how big the canvas is, what physics to use, and what scenes to load.”

const config = {
  type: Phaser.AUTO,         // Try WebGL first, fall back to Canvas
  width: 800,                // Canvas width in pixels
  height: 600,               // Canvas height in pixels
  backgroundColor: '#1a1a2e',
  physics: {
    default: 'arcade',
    arcade: { gravity: { y: 0 }, debug: false }
  },
  scene: [MainScene]         // At least one scene required
};

const game = new Phaser.Game(config);

Why Phaser.AUTO? It tries WebGL rendering first because it’s faster (uses your GPU). If WebGL isn’t available (older browsers), it falls back to Canvas mode. You don’t have to worry about which one — Phaser decides.

Why new? The new keyword creates an actual game instance. Without it, Phaser.Game(config) returns undefined and nothing renders. This is one of the most common beginner mistakes.

PropertyValuesWhat It Does
typePhaser.AUTO, Phaser.WEBGL, Phaser.CANVASChooses renderer
width / heightpixelsGame canvas dimensions
backgroundColorhex stringBackground color before anything renders
physicsobjectConfigures physics (arcade, matter, or none)
scenearray of classesScenes the game manages

Scene Lifecycle — Like Acts in a Play

A Scene in Phaser is like an act in a play. Each act has a beginning, middle, and end. Similarly, each scene has three core methods that run in order:

class MainScene extends Phaser.Scene {
  constructor() {
    super({ key: 'MainScene' });  // Give the scene a name
  }

  preload() {
    // 1. Load assets (images, sounds)
    // Runs first — like gathering props before a play
  }

  create() {
    // 2. Set up game objects, text, input
    // Runs after preload — like actors taking their positions
  }

  update(time, delta) {
    // 3. Game loop — runs every frame (about 60 times/second)
    // The play is happening now — this is where movement and logic go
  }
}

preload() — Gathering Your Props

Before the play starts, you need your props (assets). That’s what preload() is for:

preload() {
  this.load.image('background', 'assets/bg.png');
  this.load.image('player', 'assets/player.png');
  this.load.image('coin', 'assets/coin.png');
  this.load.audio('collect', 'assets/collect.wav');
}

Each this.load.image() call has three parts:

  1. Key ('background') — a nickname you choose. You’ll use this in create() to reference the asset.
  2. Path ('assets/bg.png') — where the file lives relative to your HTML page.
  3. Optional — additional settings like cross-origin or file type overrides.

Important: The key must be unique. If you use the same key twice, the second one overwrites the first. And the key in preload() must match exactly in create()'player' and 'Player' are different!

create() — Setting the Stage

After all assets finish loading, create() runs. Now you can place your sprites, add physics, set up input, and display text:

create() {
  this.add.image(400, 300, 'background');        // Center background
  this.player = this.add.sprite(100, 500, 'player');
  this.coins = this.physics.add.staticGroup();
}

Why can’t we do this in preload()? Because preload() only loads files. The rendering engine, physics system, and input handlers aren’t ready yet. If you try this.add.text() in preload(), Phaser throws a runtime error.

update() — The Game Loop

Once everything is set up, update() runs roughly 60 times per second. This is where you check for input, move objects, and handle game logic:

update(time, delta) {
  // delta = milliseconds since the LAST frame
  // At 60fps, delta is about 16.67ms
  if (this.cursors.left.isDown) {
    this.player.x -= 200 * (delta / 1000);  // Frame-independent movement
  }
}

Why multiply by delta? Without it, movement speed depends on frame rate. A player on a 144Hz monitor moves 2.4x faster than someone on 60Hz. By multiplying by delta / 1000, you convert pixels-per-frame to pixels-per-second — consistent on any screen.

Loading Assets — Images, Spritesheets, Audio

Images

this.load.image('key', 'path/to/image.png');
// Usage: this.add.image(x, y, 'key');

The key is your nickname for the image. Use it in create() to place the image on screen.

Spritesheets — One File, Many Frames

A spritesheet is a single image that contains multiple frames of animation arranged in a grid. Think of it like a flipbook:

this.load.spritesheet('player',
  'assets/player-run.png',
  { frameWidth: 32, frameHeight: 48 }  // Each frame is 32x48 pixels
);

// Create animation in create()
this.anims.create({
  key: 'run',
  frames: this.anims.generateFrameNumbers('player', { start: 0, end: 5 }),
  frameRate: 10,
  repeat: -1  // -1 = loop forever
});

Audio — Always Provide Fallbacks

Different browsers support different audio formats. MP3 works everywhere except some Firefox versions prefer OGG. Provide both:

this.load.audio('music', [
  'assets/music.ogg',
  'assets/music.mp3'
]);

Phaser will try each format in order and use the first one the browser supports. This is called format fallback.

Keyboard Input

Think of keyboard input like a remote control for your game. Phaser gives you two ways to use it:

  1. Cursor keys — a pre-built object for arrow keys, space, and shift
  2. Custom keys — bind any keyboard key to an action
create() {
  // Method 1: Cursor keys (arrow keys + space + shift)
  this.cursors = this.input.keyboard.createCursorKeys();

  // Method 2: WASD keys (custom)
  this.wasd = {
    up: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W),
    down: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S),
    left: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A),
    right: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D)
  };

  // One-shot action (press once to jump)
  this.input.keyboard.on('keydown-SPACE', () => {
    this.player.setVelocityY(-300);
  });
}

update() {
  const speed = 200;
  if (this.cursors.left.isDown) {
    this.player.x -= speed * (this.game.loop.delta / 1000);
  } else if (this.cursors.right.isDown) {
    this.player.x += speed * (this.game.loop.delta / 1000);
  }
}

Why use isDown vs events? Use isDown for continuous actions like movement (hold to keep moving). Use keydown-SPACE events for single actions like jumping or shooting (press once, act once).

You Might Be Wondering…

“Why do I need a web server? Can’t I just open the HTML file?” Modern browsers block loading local files (especially audio and JSON) for security reasons. This is called the same-origin policy. Use VS Code Live Server, npx serve ., or python -m http.server 8000 — all free and easy.

“What’s the difference between this.add.image and this.add.sprite?” An Image is a static picture — no physics, no animation. A Sprite can have physics, play animations, and respond to input. Use images for backgrounds and decorations; use sprites for characters and interactive objects.

Common Mistakes

  1. Forgetting new with Phaser.Gameconst game = Phaser.Game(config) silently fails because it returns undefined. Always use new: const game = new Phaser.Game(config).

  2. Accessing scene properties in preload() — Methods like this.add, this.physics, and this.input aren’t available during preload. Only use this.load in preload().

  3. Asset key mismatches — The key in load.image('player', ...) must exactly match the key in this.add.image(100, 100, 'player'). Case matters: 'player''Player'.

  4. Frame-dependent movement without delta — Movement like this.player.x += 5 runs faster on high-FPS displays. Always use delta: this.player.x += 200 * (delta / 1000).

  5. Not setting world collision bounds — Sprites can move off-screen entirely without this.player.setCollideWorldBounds(true).

  6. Missing fallback audio formats — Always provide both MP3 and OGG: this.load.audio('sfx', ['sfx.mp3', 'sfx.ogg']).

Practice Questions

  1. What is the purpose of Phaser.AUTO in the game config? It tries WebGL rendering first and falls back to Canvas if WebGL is unavailable, ensuring compatibility across browsers.

  2. Why does update() receive a delta parameter, and how is it used? delta is the time in milliseconds since the last frame. It’s used to make movement frame-independent: x += speed * (delta / 1000) ensures consistent speed at any frame rate.

  3. What happens if you call this.add.image() inside preload()? Phaser throws a runtime error because the rendering system isn’t ready yet. Only this.load methods are available in preload().

  4. What’s the difference between this.add.image() and this.physics.add.sprite()? image() creates a static display object with no physics body or animation. sprite() creates a physics-enabled object that can collide, animate, and respond to forces.

  5. Challenge: Modify the mini project below to add a moving enemy that patrols horizontally. Use a sprite, setVelocityX(), and setBounce(1) to make it reverse direction when hitting world bounds. How would you make the player lose a life on contact?

FAQ

What is the difference between Phaser.AUTO, Phaser.WEBGL, and Phaser.CANVAS?
Phaser.AUTO tries WebGL first and falls back to Canvas if WebGL is unavailable. WEBGL forces WebGL rendering (faster, supports filters). CANVAS forces 2D canvas rendering (slower but more compatible).
Do I need a web server to run Phaser games?
Yes. The browser’s same-origin policy blocks local file loading for audio and some image assets. Use VS Code Live Server, npx serve ., or python -m http.server 8000.
Can I use Phaser with React or Vue?
Yes. Mount the Phaser game in a div using useEffect (React) or onMounted (Vue). Pass the canvas ref as the parent element in game config.
How do I handle window resizing?
Use this.scale.resize(width, height) or set scale.mode: Phaser.Scale.FIT in game config to auto-scale the canvas.
What is a scene key and why does it matter?
The key uniquely identifies a scene for starting, stopping, or switching between scenes. If omitted, the class name is used as the key.

Try It Yourself

Here’s a complete, runnable HTML file that creates a game with a player character, coins, arrow-key movement, and a score counter. Copy this into a file, serve it with a local server, and play:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello Phaser</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      background: #0f0f23;
    }
  </style>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script>
<script>
class GameScene extends Phaser.Scene {
  constructor() {
    super({ key: 'GameScene' });
  }

  preload() {
    this.createPlaceholderAssets();
  }

  createPlaceholderAssets() {
    // Background — gradient sky
    const bgCanvas = this.textures.createCanvas('background', 800, 600);
    const bgCtx = bgCanvas.context;
    const gradient = bgCtx.createLinearGradient(0, 0, 0, 600);
    gradient.addColorStop(0, '#1a1a4e');
    gradient.addColorStop(0.5, '#4a90d9');
    gradient.addColorStop(1, '#2d5a27');
    bgCtx.fillStyle = gradient;
    bgCtx.fillRect(0, 0, 800, 600);
    bgCtx.fillStyle = '#3a7d32';
    bgCtx.fillRect(0, 520, 800, 80);
    bgCanvas.refresh();

    // Player — a small character
    const playerCanvas = this.textures.createCanvas('player', 32, 48);
    const pCtx = playerCanvas.context;
    pCtx.fillStyle = '#ff6b35';
    pCtx.fillRect(4, 0, 24, 48);
    pCtx.fillStyle = '#ffd166';
    pCtx.fillRect(8, 4, 16, 16);
    playerCanvas.refresh();

    // Coin
    const coinCanvas = this.textures.createCanvas('coin', 24, 24);
    const cCtx = coinCanvas.context;
    cCtx.fillStyle = '#ffd700';
    cCtx.beginPath();
    cCtx.arc(12, 12, 10, 0, Math.PI * 2);
    cCtx.fill();
    cCtx.fillStyle = '#ffaa00';
    cCtx.beginPath();
    cCtx.arc(12, 12, 6, 0, Math.PI * 2);
    cCtx.fill();
    coinCanvas.refresh();
  }

  create() {
    this.add.image(400, 300, 'background');
    this.player = this.physics.add.sprite(100, 460, 'player');
    this.player.setCollideWorldBounds(true);
    this.coins = this.physics.add.staticGroup();
    const coinPositions = [
      [200, 400], [350, 350], [500, 400],
      [650, 350], [280, 300], [550, 300]
    ];
    coinPositions.forEach(([x, y]) => {
      this.coins.create(x, y, 'coin');
    });
    this.physics.add.overlap(
      this.player, this.coins,
      this.collectCoin, null, this
    );
    this.cursors = this.input.keyboard.createCursorKeys();
    this.score = 0;
    this.scoreText = this.add.text(16, 16, 'Coins: 0 / 6', {
      fontSize: '24px', fill: '#ffffff',
      fontFamily: 'monospace',
      stroke: '#000000', strokeThickness: 3
    });
    this.add.text(400, 560, 'Arrow keys to move — Collect the coins!', {
      fontSize: '14px', fill: '#ffffff', fontFamily: 'monospace'
    }).setOrigin(0.5);
  }

  update() {
    const speed = 250;
    const delta = this.game.loop.delta / 1000;
    if (this.cursors.left.isDown) {
      this.player.x -= speed * delta;
    } else if (this.cursors.right.isDown) {
      this.player.x += speed * delta;
    }
  }

  collectCoin(player, coin) {
    coin.destroy();
    this.score++;
    this.scoreText.setText(`Coins: ${this.score} / 6`);
    if (this.score >= 6) {
      this.scoreText.setText('You Win! All coins collected!');
    }
  }
}

const config = {
  type: Phaser.AUTO,
  width: 800, height: 600,
  backgroundColor: '#1a1a2e',
  physics: {
    default: 'arcade',
    arcade: { gravity: { y: 0 }, debug: false }
  },
  scene: [GameScene]
};

new Phaser.Game(config);
</script>
</body>
</html>

Open this in a browser (via a local server) to see a player, 6 coins, arrow-key movement, and a live score. This covers: Phaser.Game config, scene lifecycle, procedural asset generation, sprite creation, static physics groups, overlap detection, keyboard input, and real-time UI updates.

What’s Next

TopicDescription
Phaser Sprites & PhysicsDeep dive into Arcade physics, collisions, and velocity
Phaser Scenes & StateMulti-scene games, transitions, and data persistence
Phaser Input & AudioGamepad support, touch input, audio sprites, and spatial sound
Phaser TilemapsLevel design with Tiled, tile layers, and procedural generation
JavaScript BasicsReview JavaScript fundamentals if any syntax felt unfamiliar

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. These Phaser game mechanics power interactive training simulators used in security awareness programs worldwide.

What’s Next

Congratulations on completing this Phaser Getting Started tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro