Phaser.js Tutorial — Build 2D Browser Games
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
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:
| Method | When It Runs | Used For |
|---|---|---|
preload() | Before scene starts | Loading assets (images, sounds) |
create() | Once, after preload | Setting up game objects |
update() | Every frame | Game 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:
| System | Best For | Features |
|---|---|---|
| Arcade | Simple 2D games | AABB collision, gravity, velocity |
| Matter.js | Complex physics | Rigid bodies, friction, constraints |
| Impact | Retro tile-based | Grid-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
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