Phaser Tutorial for Beginners: Your First Game

Phaser is the most widely used HTML5 game framework, and Phaser 3 is the current production version. It handles the boilerplate — game loop, asset loading, sprite rendering, physics, input — that you’d otherwise build by hand. This Phaser tutorial for beginners walks through setting up Phaser 3 via CDN, creating your first Scene, loading a sprite, and adding keyboard movement. No build tools, no npm, no terminal — just an HTML file.
Key takeaways
- Phaser 3 loads via a single CDN script tag — no build setup required to start.
- A Phaser game is configured with a config object passed to `new Phaser.Game()`.
- Scenes contain `preload`, `create`, and `update` lifecycle functions that Phaser calls automatically.
- Sprites are loaded in `preload`, added to the scene in `create`, and moved in `update`.
- Phaser 3 includes Arcade Physics for simple collision and velocity-based movement — enough for most 2D games.
Why Phaser
You can build browser games in vanilla JavaScript — and you should, at least once. But once you want sprite atlases, tilemaps, particle systems, physics, audio, and animation, the boilerplate becomes the project. Phaser handles all of it and exposes a consistent API. The trade-off is a larger download (Phaser 3 is around 1 MB minified) and a learning curve for the framework’s conventions.
Phaser is open-source under the MIT license, maintained by Photon Storm. It powers thousands of indie games on itch.io, Kongregate-era portals (RIP), and Facebook Instant Games. For broader engine context, see our HTML5 game engine roundup.
Setup via CDN
The fastest way to start is loading Phaser from a CDN. No npm, no Webpack, no build step. Create an HTML file:
<!DOCTYPE html>
<html>
<head>
<title>My Phaser Game</title>
</head>
<body>
<div id="game"></div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/phaser.min.js"></script>
<script src="game.js"></script>
</body>
</html>
Use the latest Phaser 3 version from the CDN. The div is where Phaser will inject the canvas; you can also let Phaser create the canvas at the body level by omitting the div and the `parent` config field.
The config object
Phaser’s entry point is a single `new Phaser.Game(config)` call. The config defines the canvas size, the renderer, the physics system, and the list of Scenes.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: 'game',
backgroundColor: '#222',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: false
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
`Phaser.AUTO` lets Phaser pick WebGL if available, falling back to Canvas. `parent: ‘game’` ties the canvas to the div in your HTML. `physics.default: ‘arcade’` enables Arcade Physics — the simplest of Phaser’s three physics systems and the right choice for almost all 2D games.
The Scene lifecycle
Every Phaser Scene has three core methods:
- preload(): Load assets (images, sounds, JSON, sprite atlases). Called once before the Scene starts.
- create(): Set up the Scene — add sprites, configure physics, attach input handlers. Called once after preload finishes.
- update(): Per-frame logic. Called automatically each frame at the target frame rate (usually 60fps).
You can also use ES6 classes that extend `Phaser.Scene`, which is the conventional approach for larger games. For a beginner tutorial, the inline-function approach above is clearer.
Loading a sprite
Phaser needs an image. Use any 32×32 or 64×64 PNG. If you don’t have one, grab a placeholder from any free sprite site or use a 32×32 colored square you draw in any image editor.
function preload() {
this.load.image('player', 'assets/player.png');
}
`this.load.image(key, path)` registers the image under a key. The key is how you’ll reference the sprite later — call it whatever you want as long as you’re consistent.
Adding the sprite to the Scene
let player;
function create() {
player = this.physics.add.sprite(400, 300, 'player');
player.setCollideWorldBounds(true);
}
`this.physics.add.sprite(x, y, key)` creates a sprite with a physics body attached. `setCollideWorldBounds(true)` keeps the sprite inside the canvas — without it, the player can walk off-screen.
If you want a non-physics sprite (no velocity, no collision), use `this.add.sprite(x, y, key)` instead. For a movable player, you almost always want the physics-attached version.
Input handling
Phaser provides a keyboard cursor helper that gives you bound objects for the arrow keys.
let cursors;
function create() {
player = this.physics.add.sprite(400, 300, 'player');
player.setCollideWorldBounds(true);
cursors = this.input.keyboard.createCursorKeys();
}
`cursors.up.isDown`, `cursors.down.isDown`, `cursors.left.isDown`, `cursors.right.isDown` are booleans you check each frame in `update`.
Movement in update
function update() {
player.setVelocity(0);
if (cursors.left.isDown) player.setVelocityX(-200);
if (cursors.right.isDown) player.setVelocityX(200);
if (cursors.up.isDown) player.setVelocityY(-200);
if (cursors.down.isDown) player.setVelocityY(200);
}
Reset velocity to zero at the top of each frame, then set it based on input. This produces snappy movement — release the key and the player stops immediately. For sliding/momentum, you’d skip the reset and let drag (set via `player.setDrag(…)`) decelerate naturally.
Setting velocity directly (rather than position) lets Arcade Physics handle the actual movement, collision response, and frame-rate independence for you. This is the entire point of using a physics body — manual position updates would require you to handle collisions and dt math yourself.
Adding more sprites
Multiple sprites work the same way. Load each in preload, instantiate in create, manage in update. For collidable obstacles:
let walls;
function create() {
// ... existing player setup ...
walls = this.physics.add.staticGroup();
walls.create(200, 500, 'wall');
walls.create(600, 500, 'wall');
this.physics.add.collider(player, walls);
}
`staticGroup()` creates a group of non-moving sprites. `this.physics.add.collider(player, walls)` registers collision detection between the player and any wall — Phaser handles the rest. The player will simply stop when colliding instead of passing through.
Animation
Sprite sheets unlock animation. Load a sprite sheet in preload using `this.load.spritesheet(key, path, { frameWidth, frameHeight })`. Then create animations in create:
this.anims.create({
key: 'walk',
frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
player.anims.play('walk');
`repeat: -1` loops indefinitely. Switching animations is a single `.play()` call — `player.anims.play(‘idle’)`, `player.anims.play(‘jump’)`, etc.
What to build next
Once the base player-moves-around-a-room works, the natural progression:
- Add coins to collect. Use `this.physics.add.overlap(player, coins, collectCoin)` to call a function on overlap.
- Add gravity. Set `gravity.y` in config to 300 or 500. Make the player jump on space.
- Add a tilemap. Phaser’s Tilemap API loads JSON exported from Tiled (a free tile editor) and renders entire levels.
- Add multiple Scenes. A title screen Scene that transitions to a game Scene, then to a game-over Scene. Phaser’s Scene Manager handles transitions.
- Save high scores. Use localStorage. Phaser does not include persistence — that’s standard browser APIs.
Phaser 3 documentation
Phaser 3’s official documentation lives at docs.phaser.io and the GitHub repo hosts hundreds of working examples. The Phaser team also maintains a tutorial called “Making your first Phaser 3 game” that’s the canonical starting point — this article covers the same ground in a more condensed form.
For framework comparisons, see our Phaser vs Unity vs Godot guide for browser game development.
Common beginner mistakes
- Forgetting to preload an asset. Calling `this.add.sprite(x, y, ‘key’)` before `preload` registers the key produces a missing-texture error.
- Putting setup code in update. Object creation in update runs every frame and tanks performance. Setup goes in create.
- Confusing Arcade Physics with Matter.js. Arcade is the simple, fast physics. Matter is the full rigid-body simulation. Beginners almost always want Arcade.
- Not enabling physics on sprites. A sprite added with `this.add.sprite` has no physics body and won’t collide. Use `this.physics.add.sprite` if you need physics.
- Missing the parent div. Without `parent: ‘id’`, Phaser appends the canvas to the body, which is sometimes what you want and sometimes a layout disaster.
Frequently asked questions
What’s the difference between Phaser 2 and Phaser 3?
Phaser 3 is a complete rewrite. It supports WebGL by default, has a modern Scene system, and replaces Phaser 2’s legacy physics. New projects should always use Phaser 3 — Phaser 2 is in maintenance mode and not recommended for new work.
Do I need to know npm to use Phaser?
No. The CDN script tag is enough for any tutorial-level project. npm becomes useful when you want bundling, TypeScript, or third-party plugins — but it’s not required to start.
Can I use Phaser with TypeScript?
Yes. Phaser ships with TypeScript definitions. Set up a Vite or Webpack project, install `phaser` from npm, and you have full type support. The CDN-and-script-tag approach is the JavaScript-only route.
Which physics system should I use in Phaser 3?
Arcade Physics for almost any 2D game — platformers, top-down RPGs, shooters. Matter.js for physics-puzzle games with realistic collisions and rotations. Impact Physics is deprecated and not worth learning.
How big is a Phaser game’s download?
The Phaser 3 minified library is approximately 1 MB. Your game’s assets (sprites, sounds, tilemaps) add on top of that. A typical small Phaser game is 1-3 MB total — heavier than vanilla JS but light by general web standards.
The bottom line
Phaser is the right choice when your game needs more than a single canvas and a request loop — when sprites, animations, physics, and Scene transitions start mattering. The starting boilerplate is small (config object, preload-create-update Scene), and the API has remained stable across versions 3.50+. For a vanilla-JS counterpoint to compare against, the Chrome Dino game on this site runs without any framework and shows what the no-engine path looks like at a small scale.








