How to create a platform game with Phaser.js
In this tutorial I’ll use Phaser.js to create a platform game.
The player can move around with the left/right arrow keys, and jump with the up arrow key. The goal is to pick up all the stars in the game:
When the player picks up all the stars, we’re going to display “Game over” on top, and there’s nothing else to do.
It’s very very simple, but it’s a start of a game that can be very fun to continue making, and it’s a great way to demonstrate both Phaser and how JavaScript can work as a game creation programming language.
Setup the project
Create an empty folder, and run
npm init -y
to initialize a minimal package.json
file.
Then run
npm install phaser
Install Parcel if you haven’t:
npm install -g parcel-bundler
Now create an app.js
file, and run
parcel watch app.js
This will tell Parcel to build our game in the dist/app.js
file.
Create an index.html
file with this content:
<!DOCTYPE html>
<html>
<head>
<script src="./dist/app.js"></script>
</head>
</html>
Install browser-sync
to run an HTTP server with the content of this folder:
npm install -g browser-sync
then run
browser-sync start --server --files "."
The above command watches all files in the current folder (and all subfolders) for changes, and launches a web server on port 3000, automatically opening a browser window to connect to the server.
Any time you change a file, the browser will refresh, so we can be more speedy in our development process.
Great! We can start.
Initialize Phaser
Open app.js
and write:
import Phaser from 'phaser'
Let’s add a minimal configuration:
const config = {
width: 800,
height: 600,
backgroundColor: 0xffffff,
scene: {
preload,
create,
update
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
}
}
const game = new Phaser.Game(config)
Now create the 3 functions preload()
, create()
and update()
:
function preload() {
}
function create() {
}
function update() {
}
Add the artwork
I call it “artwork”, but I made it and it’s the ugliest artwork you’ll ever find.
But it works for our simple game. Download those files and save them as:
ground.png
island.png
player.png
star.png
Create an assets
folder, and put them in there.
Create a canvas
Now get back to app.js
.
The first thing we’ll do is to load the assets in preload()
:
function preload() {
this.load.image('ground', 'assets/ground.png')
this.load.image('island', 'assets/island.png')
this.load.image('star', 'assets/star.png')
this.load.spritesheet('player', 'assets/player.png', {
frameWidth: 32,
frameHeight: 48
})
}
We assign a label to each asset. The first 3 are loaded using this.load.image()
, the last one using this.load.spritesheet()
because the image contains several little images, that we’ll use to animate the player.
Therefore we must also set the dimensions of each image in the sprite sheet (32x48 pixels).
Create the platforms
Let’s create the platforms now.
We’re going to create a new physics static group to hold them, because they are not moving objects: they are still.
let platforms = this.physics.add.staticGroup()
Then we add the ground:
platforms.create(400, 588, "ground")
And 5 islands:
platforms.create(600, 450, "island")
platforms.create(50, 250, "island")
platforms.create(650, 220, "island")
platforms.create(250, 520, "island")
platforms.create(250, 320, "island")
The numbers here indicate the X and Y coordinates in the 2D space, relative to the top left corner, and refer to the middle of each image.
This is what you should see at this point:
Add the player
Now let’s add the player.
Create a variable on top of the file, outside any function, called player
:
let player
Then in create()
I’m going to add it near the middle bottom of the screen:
player = this.physics.add.sprite(380, 500, "player")
We set it to have a small bounce:
player.setBounce(0.2)
so it will quickly stand still after being put in the screen, and we set it to not go outside of the screen:
player.setCollideWorldBounds(true)
In this way it will appear and after a couple bounces it will sit at the bottom of the screen:
However, we want the player to stay in top of the ground.
We can do this by adding a collider physics rule between the player and the platforms:
this.physics.add.collider(player, platforms)
In this way, the player will not just stay on top of the ground, but also of the other 5 platforms (islands) we have floating on the screen:
Make the player move
Now let’s make it possible for the player to move.
Let’s add a variable on top of the file, near the player
declaration:
let cursors
Then in create()
add:
cursors = this.input.keyboard.createCursorKeys()
to allow access to the keyboard events.
Now in the update()
function, which is now empty, we’re going to inspect this variable:
function update() {
if (cursors.left.isDown) {
player.setVelocityX(-160)
} else if (cursors.right.isDown) {
player.setVelocityX(160)
} else {
player.setVelocityX(0)
}
}
update()
is continuously called by the game loop, so whenever one key is down we change the player velocity (speed) on the X axis. If no key is pressed, we set it to stand still.
Animate the player
Let’s now make the player image change when it’s moving.
We create 3 animations in create()
: one when the player is still, one when it’s moving to the left, one when it’s moving to the right:
this.anims.create({
key: 'still',
frames: [{ key: 'player', frame: 4 }],
frameRate: 20
})
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
})
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('player', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
})
still
is not really an animation, because it only has one frame. But left
and right
are composed by 4 frames each, and they are infinitely repeated to simulate walking.
Now we need to activate those animations in update()
:
function update() {
if (cursors.left.isDown) {
player.setVelocityX(-160)
player.anims.play('left', true)
} else if (cursors.right.isDown) {
player.setVelocityX(160)
player.anims.play('right', true)
} else {
player.setVelocityX(0)
player.anims.play('still')
}
}
The true
parameter for left
and right
sets the animation to loop.
Jump
To allow the player to jump, add this code into update()
:
if (cursors.up.isDown && player.body.touching.down) {
player.setVelocityY(-330)
}
Whenever the player sits on the bottom (player.body.touching.down
) and the up arrow is pressed, we push it to the top. Gravity will simulate a jump like the ones we do in the real world.
Add the stars
Let’s now add the stars to the screen.
We add 8 stars by creating a physics group, and we make them collide with the platforms, so they’ll sit on top of them:
let stars = this.physics.add.group()
stars.create(22, 0, "star")
stars.create(122, 0, "star")
stars.create(222, 0, "star")
stars.create(322, 0, "star")
stars.create(422, 0, "star")
stars.create(522, 0, "star")
stars.create(622, 0, "star")
stars.create(722, 0, "star")
this.physics.add.collider(stars, platforms)
Allow the player to pick up the stars
To make the player pick up the stars, we’re going to add an overlap physics effect in create()
that is fired when the player meets a star:
this.physics.add.overlap(player, stars, (player, star) => {}, null, this)
When this happens, we hide the star:
this.physics.add.overlap(
player,
stars,
(player, star) => {
star.disableBody(true, true)
},
null,
this
)
Let’s make a stars counter. When one star is picked up, we increment it:
let score = 0
this.physics.add.overlap(
player,
stars,
(player, star) => {
star.disableBody(true, true)
score += 1
},
null,
this
)
and we can display it on top of the screen:
let score = 0
let scoreText = this.add.text(16, 16, "Stars: 0", {
fontSize: "32px",
fill: "#000",
})
this.physics.add.overlap(
player,
stars,
(player, star) => {
star.disableBody(true, true)
score += 1
scoreText.setText("Stars: " + score)
},
null,
this
)
That’s it! Our little game works!
→ I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter
→ JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025