Skip to content
Phaser.js Tutorial — Build 2D Browser Games

Phaser.js Tutorial — Build 2D Browser Games

DodaTech Updated Jun 7, 2026 9 min read

Phaser.js is a free, open-source 2D game framework for building HTML5 games that run in any modern browser — no plugins, no installs, just JavaScript and a canvas element.

What You’ll Learn

By the end of this tutorial, you’ll understand Phaser.js scenes, sprite management, arcade physics, keyboard input, and how to build a complete browser-based game with score tracking and collision detection.

Why Phaser.js Matters

Browser games reach the widest possible audience — no app store approval, no platform-specific code, just a URL. Phaser.js powers thousands of games across the web, from educational tools to marketing campaigns. At DodaTech, we use Phaser.js for interactive product demos in Doda Browser.

Phaser.js Learning Path

    flowchart LR
  A[Game Dev Overview] --> B[Phaser.js]
  B --> C{You Are Here}
  C --> D[Complete Browser Game]
  style C fill:#f90,color:#fff
  
Prerequisites: JavaScript basics (variables, functions, objects). Familiarity with HTML Canvas helps but isn’t required.

What Is Phaser.js? (The “Why” First)

Think of Phaser.js as a pre-built game engine for the browser. Instead of writing raw JavaScript to handle rendering loops, physics calculations, and input detection, Phaser provides all of that out of the box. You just write the game logic.

Without Phaser, you’d write hundreds of lines of boilerplate. With Phaser, a working game takes under 100 lines.

Setting Up Phaser.js

You can include Phaser via CDN in a single HTML file:

<!DOCTYPE html>
<html>
<head>
  <title>My First Phaser Game</title>
  <script src="https://cdn.jsdelivr.net/npm/phaser@3.60.0/dist/phaser.min.js"></script>
</head>
<body>
  <script>
    // Your game code here
  </script>
</body>
</html>

Save this as phaser-game.html and open it in any browser. No build tools, no servers, no installs.

Phaser Architecture — Scenes

Everything in Phaser is organized into Scenes. Think of a scene like a room in a game — you have a menu scene, a gameplay scene, a game-over scene. Each scene has lifecycle methods:

MethodWhen It RunsUsed For
preload()Before scene startsLoading assets (images, sounds)
create()Once, after preloadSetting up game objects
update()Every frameGame logic, movement, collisions

Your First Phaser Scene

Let’s create a simple scene that displays a character and makes it move:

const config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  scene: {
    preload: preload,
    create: create,
    update: update
  }
};

const game = new Phaser.Game(config);

function preload() {
  // Load a placeholder texture
  this.load.image('player', 'https://labs.phaser.io/assets/sprites/phaser-dude.png');
}

function create() {
  // Place the player in the center
  this.player = this.add.image(400, 300, 'player');

  // Store cursor keys for input
  this.cursors = this.input.keyboard.createCursorKeys();
}

function update() {
  const speed = 5;

  // Reset velocity
  this.player.setVelocity(0, 0);

  // Check cursor keys
  if (this.cursors.left.isDown) {
    this.player.x -= speed;
  } else if (this.cursors.right.isDown) {
    this.player.x += speed;
  }

  if (this.cursors.up.isDown) {
    this.player.y -= speed;
  } else if (this.cursors.down.isDown) {
    this.player.y += speed;
  }
}

What this does: Creates an 800×600 game window, loads a character sprite, and lets you move it with arrow keys. But wait — this uses manual position updates. Let’s upgrade to Phaser’s physics system.

Adding Arcade Physics

Phaser’s Arcade Physics handles movement, gravity, and collisions automatically:

const config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  physics: {
    default: 'arcade',
    arcade: {
      gravity: { y: 300 },    // Gravity pulls everything down
      debug: false             // Set true to see hitboxes
    }
  },
  scene: {
    preload: preload,
    create: create,
    update: update
  }
};

const game = new Phaser.Game(config);

function preload() {
  this.load.image('player', 'https://labs.phaser.io/assets/sprites/phaser-dude.png');
  this.load.image('platform', 'https://labs.phaser.io/assets/sprites/platform.png');
}

function create() {
  // Create a static platform group
  const platforms = this.physics.add.staticGroup();
  platforms.create(400, 568, 'platform').setScale(2).refreshBody();

  // Create player with arcade physics
  this.player = this.physics.add.sprite(100, 450, 'player');

  // Player collides with platforms (doesn't fall through)
  this.physics.add.collider(this.player, platforms);

  // Keyboard input
  this.cursors = this.input.keyboard.createCursorKeys();
}

function update() {
  // Horizontal movement
  if (this.cursors.left.isDown) {
    this.player.setVelocityX(-200);
  } else if (this.cursors.right.isDown) {
    this.player.setVelocityX(200);
  } else {
    this.player.setVelocityX(0);
  }

  // Jump only if on the ground
  if (this.cursors.up.isDown && this.player.body.touching.down) {
    this.player.setVelocityY(-500);
  }
}

What’s new: Gravity now pulls the player down. The collider prevents the player from falling through platforms. body.touching.down checks if the player is on the ground before allowing a jump. This prevents double-jumping.

Complete Game: Coin Collector

Let’s build a complete mini-game with collectibles, score tracking, and a win condition:

const config = {
  type: Phaser.AUTO,
  width: 800,
  height: 600,
  physics: {
    default: 'arcade',
    arcade: {
      gravity: { y: 300 },
      debug: false
    }
  },
  scene: {
    preload: preload,
    create: create,
    update: update
  }
};

let score = 0;
let scoreText;

const game = new Phaser.Game(config);

function preload() {
  // Using built-in Phaser textures for simplicity
  this.load.image('sky', 'https://labs.phaser.io/assets/skies/space3.png');
  this.load.image('platform', 'https://labs.phaser.io/assets/sprites/platform.png');
  this.load.image('star', 'https://labs.phaser.io/assets/sprites/star.png');
  this.load.image('bomb', 'https://labs.phaser.io/assets/sprites/bomb.png');
  this.load.spritesheet('dude',
    'https://labs.phaser.io/assets/sprites/dude.png',
    { frameWidth: 32, frameHeight: 48 }
  );
}

function create() {
  // Background
  this.add.image(400, 300, 'sky');

  // Platforms
  const platforms = this.physics.add.staticGroup();
  platforms.create(400, 568, 'platform').setScale(2).refreshBody();
  platforms.create(600, 400, 'platform');
  platforms.create(50, 250, 'platform');
  platforms.create(750, 220, 'platform');

  // Player sprite with animations
  this.player = this.physics.add.sprite(100, 450, 'dude');
  this.player.setBounce(0.2);
  this.player.setCollideWorldBounds(true);

  // Player animations
  this.anims.create({
    key: 'left',
    frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
    frameRate: 10,
    repeat: -1
  });
  this.anims.create({
    key: 'turn',
    frames: [{ key: 'dude', frame: 4 }],
    frameRate: 20
  });
  this.anims.create({
    key: 'right',
    frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
    frameRate: 10,
    repeat: -1
  });

  // Stars collectible group
  this.stars = this.physics.add.group({
    key: 'star',
    repeat: 11,
    setXY: { x: 12, y: 0, stepX: 70 }
  });
  this.stars.children.iterate((child) => {
    child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
  });

  // Bombs (enemies)
  this.bombs = this.physics.add.group();

  // Score display
  score = 0;
  scoreText = this.add.text(16, 16, 'Score: 0', {
    fontSize: '32px',
    fill: '#fff'
  });

  // Colliders
  this.physics.add.collider(this.player, platforms);
  this.physics.add.collider(this.stars, platforms);
  this.physics.add.collider(this.bombs, platforms);

  // Overlaps
  this.physics.add.overlap(this.player, this.stars, collectStar, null, this);
  this.physics.add.collider(this.player, this.bombs, hitBomb, null, this);

  // Input
  this.cursors = this.input.keyboard.createCursorKeys();
}

function update() {
  if (this.cursors.left.isDown) {
    this.player.setVelocityX(-200);
    this.player.anims.play('left', true);
  } else if (this.cursors.right.isDown) {
    this.player.setVelocityX(200);
    this.player.anims.play('right', true);
  } else {
    this.player.setVelocityX(0);
    this.player.anims.play('turn');
  }

  if (this.cursors.up.isDown && this.player.body.touching.down) {
    this.player.setVelocityY(-500);
  }
}

function collectStar(player, star) {
  star.disableBody(true, true);  // Remove the star

  score += 10;
  scoreText.setText('Score: ' + score);

  // When all stars are collected, spawn a bomb
  if (this.stars.countActive(true) === 0) {
    this.stars.children.iterate((child) => {
      child.enableBody(true, child.x, 0, true, true);
    });

    const x = (player.x < 400) ?
      Phaser.Math.Between(400, 800) :
      Phaser.Math.Between(0, 400);

    const bomb = this.bombs.create(x, 16, 'bomb');
    bomb.setBounce(1);
    bomb.setCollideWorldBounds(true);
    bomb.setVelocity(Phaser.Math.Between(-200, 200), 20);
  }
}

function hitBomb(player, bomb) {
  this.physics.pause();
  player.setTint(0xff0000);
  player.anims.play('turn');
  alert('Game Over! Score: ' + score);
}

What this game does:

  • Player moves with arrow keys and jumps with up arrow
  • Collect all 12 stars to win
  • Each star is worth 10 points
  • When all stars are collected, they respawn and a bomb enemy appears
  • Touching a bomb ends the game

Understanding Phaser Physics

Phaser has three physics systems:

SystemBest ForFeatures
ArcadeSimple 2D gamesAABB collision, gravity, velocity
Matter.jsComplex physicsRigid bodies, friction, constraints
ImpactRetro tile-basedGrid-aligned movement, tilemaps

For most 2D browser games, Arcade physics is all you need.

Common Mistakes Beginners Make

1. Forgetting to Call refreshBody() on Static Groups

When you scale or move a static group member, call refreshBody() to update its physics body. Otherwise collisions won’t match the visual.

2. Not Checking body.touching.down for Jumping

Without checking body.touching.down, the player can jump infinitely while in the air. Always verify the player is grounded.

3. Creating Objects Outside create()

Put game object creation in create(), not preload(). The preload() method is only for loading assets — the canvas isn’t ready for game objects yet.

4. Forgetting That Static Physics Bodies Don’t Move

Use staticGroup for platforms and walls. Use dynamic group or physics.add.sprite for moving objects.

5. Not Setting setCollideWorldBounds(true)

Without this, the player can walk off the screen and disappear. Always enable world bounds for characters.

6. Overlapping Without a Callback

this.physics.add.overlap() requires a callback function. Without it, nothing happens when objects overlap — the overlap is detected but not acted upon.

7. Using the Wrong Physics System

Arcade physics is for simple rectangle-based collisions. If you need polygon collision or friction, switch to Matter.js from the start — migrating mid-project is painful.

Practice Questions

1. What are the three main Phaser scene methods and when do they run?

preload() — loads assets before the scene starts. create() — sets up game objects once. update() — runs every frame for game logic.

2. Why check body.touching.down before allowing a jump?

Without it, the player can jump infinitely in the air. body.touching.down is only true when the player is standing on a platform.

3. What’s the difference between collider and overlap?

collider causes objects to physically bounce off each other. overlap detects when objects touch without physics separation — useful for collectibles.

4. What does refreshBody() do and when is it needed?

It updates the physics body after changing a static object’s position or scale. Needed because static bodies don’t automatically recalculate their bounds.

5. Challenge: Add a double-jump mechanic.

Track jump count. Allow a second jump when body.touching.down is false but jump count is less than 2. Reset jump count on landing.

Mini Project: Platform Level Editor

Create a simple HTML page that generates Phaser platform positions as JSON, which you can paste into your game:

<!DOCTYPE html>
<html>
<head><title>Platform Generator</title></head>
<body>
  <h3>Click to place platforms. Copy the JSON output.</h3>
  <pre id="output">[]</pre>
  <script>
    const platforms = [];

    document.addEventListener('click', (e) => {
      platforms.push({ x: e.clientX, y: e.clientY });
      document.getElementById('output').textContent =
        JSON.stringify(platforms, null, 2);
    });
  </script>
</body>
</html>

Use the output to build your levels in Phaser:

const levelData = [ /* paste here */ ];
levelData.forEach(p => {
  platforms.create(p.x, p.y, 'platform');
});

FAQ

Is Phaser.js free?
Yes, Phaser.js is completely free and open-source under the MIT license. You can use it for commercial games with no royalties or fees.
What browsers does Phaser support?
Phaser 3 supports all modern browsers (Chrome, Firefox, Safari, Edge) and uses WebGL with Canvas fallback. It also works on mobile browsers.
Do I need a server to run Phaser games?
No. You can open the HTML file directly in a browser during development. For multiplayer or asset management, a web server helps but isn’t required.
Can Phaser make mobile games?
Yes, Phaser games run in mobile browsers. For native app store distribution, wrap your game in a WebView using Cordova or Capacitor.
Does Phaser support multiplayer?
Phaser itself doesn’t include networking. You integrate it with WebSocket, Socket.io, or a game backend like Colyseus for multiplayer.

Try It Yourself

Run your Phaser game and open the browser’s developer tools (F12). In the console, try:

// Move the player programmatically
game.scene.scenes[0].player.x = 200;

// Check active scene
game.scene.scenes[0].scene.sys.settings.active;

// Pause physics
game.scene.scenes[0].physics.pause();

// Resume physics
game.scene.scenes[0].physics.resume();

This runtime manipulation is invaluable for debugging. The same approach is used in Doda Browser’s developer tools to debug interactive web applications.

What’s Next

What’s Next

Congratulations on completing this Phaser.js 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