Games can be one of the most complicated pieces of software to create. And the most fun!
It helps to use modern best practices to avoid nasty bugs or more easily fix ones that come up.
This saves you time and headaches but more importantly, it will help you make a better game.
In this article, we will show you how to create a game in Phaser 3 using modern JavaScript and best practices.
We'll be creating the same game from the official Making your first Phaser 3 game guide. This way you can compare and contrast the differences.
In Part 4 we added collecting stars and displaying the score. Go check it out if you haven't already.
Dropping Bombs with a Spawner
We are going to deviate significantly from the official Phaser guide so hang on to your hats. 🤓
To start we will use a BombSpawner
class to handle creating bombs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import Phaser from 'phaser'
export default class BombSpawner
{
/**
* @param {Phaser.Scene} scene
*/
constructor(scene, bombKey = 'bomb')
{
this.scene = scene
this.key = bombKey
this._group = this.scene.physics.add.group()
}
get group()
{
return this._group
}
spawn(playerX = 0)
{
const x = (playerX < 400) ? Phaser.Math.Between(400, 800) : Phaser.Math.Between(0, 400)
const bomb = this.group.create(x, 16, this.key)
bomb.setBounce(1)
bomb.setCollideWorldBounds(true)
bomb.setVelocity(Phaser.Math.Between(-200, 200), 20)
return bomb
}
}
|
We created BombSpawner.js
in src/scenes
but you can choose a different location.
Having a separate class for bomb spawning logic lets us encapsulate common logic and reuse code. It also keeps GameScene
from getting cluttered. These are the same reasons we had for creating the ScoreLabel
.
Which means BombSpawner
can be used in multiple Scenes and spawn bombs in more places without duplicating any code. 👍
The constructor
takes a reference to a Scene and an optional bombKey
. Notice we use the key on line 25. This is how we will continue keeping our string literals DRY. Both parameters will be given by the GameScene
.
Note: the comments above the constructor
is JSDoc to provide type information. This helps with autocomplete or IntelliSense in your code editor.
BombSpawner in the GameScene
Now let's create a BombSpawner
instance in the GameScene
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
import Phaser from 'phaser'
import ScoreLabel from '../ui/ScoreLabel'
import BombSpawner from './BombSpawner'
const GROUND_KEY = 'ground'
const DUDE_KEY = 'dude'
const STAR_KEY = 'star'
const BOMB_KEY = 'bomb'
export default class GameScene extends Phaser.Scene
{
constructor()
{
super('game-scene')
this.player = undefined
this.cursors = undefined
this.scoreLabel = undefined
this.bombSpawner = undefined
}
preload()
{
this.load.image('sky', 'assets/sky.png')
this.load.image(GROUND_KEY, 'assets/platform.png')
this.load.image(STAR_KEY, 'assets/star.png')
this.load.image(BOMB_KEY, 'assets/bomb.png')
this.load.spritesheet(DUDE_KEY,
'assets/dude.png',
{ frameWidth: 32, frameHeight: 48 }
)
}
create()
{
this.add.image(400, 300, 'sky')
const platforms = this.createPlatforms()
this.player = this.createPlayer()
const stars = this.createStars()
this.scoreLabel = this.createScoreLabel(16, 16, 0)
this.bombSpawner = new BombSpawner(this, BOMB_KEY)
this.physics.add.collider(this.player, platforms)
this.physics.add.collider(stars, platforms)
this.physics.add.overlap(this.player, stars, this.collectStar, null, this)
this.cursors = this.input.keyboard.createCursorKeys()
}
}
|
First, we import BombSpawner
on line 4. Then we create a constant BOMB_KEY
on line 9.
While not strictly necessary, we add a bombSpawner
property in the constructor as a best practice.
The 'bomb'
string literal in preload()
is replaced on line 28 by the BOMB_KEY
constant.
Then on line 46 we create a BombSpawner
and set it to this.bombSpawner
.
You won't see anything different at http://localhost:8000 because this is all set up code… to set you up for great success! 😅
Using the BombSpawner
We are going to spawn a bomb each time a star is collected and have the bombs collide with the platforms.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
import Phaser from 'phaser'
import ScoreLabel from '../ui/ScoreLabel'
import BombSpawner from './BombSpawner'
export default class GameScene extends Phaser.Scene
{
constructor()
{
super('game-scene')
this.player = undefined
this.cursors = undefined
this.scoreLabel = undefined
this.stars = undefined
this.bombSpawner = undefined
}
// ...
create()
{
this.add.image(400, 300, 'sky')
const platforms = this.createPlatforms()
this.player = this.createPlayer()
this.stars = this.createStars()
this.scoreLabel = this.createScoreLabel(16, 16, 0)
this.bombSpawner = new BombSpawner(this, BOMB_KEY)
const bombsGroup = this.bombSpawner.group
this.physics.add.collider(this.player, platforms)
this.physics.add.collider(this.stars, platforms)
this.physics.add.collider(bombsGroup, platforms)
this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this)
this.cursors = this.input.keyboard.createCursorKeys()
}
collectStar(player, star)
{
star.disableBody(true, true)
this.scoreLabel.add(10)
if (this.stars.countActive(true) === 0)
{
// A new batch of stars to collect
this.stars.children.iterate((child) => {
child.enableBody(true, child.x, 0, true, true)
})
}
this.bombSpawner.spawn(player.x)
}
// ...
}
|
A new stars
instance property is added to the constructor. We need this reference to reset the stars after all are collected in collectStar(player, start)
starting on line 49.
Next, we set this.stars
to the created stars group on line 27 and use it in this.physics.add.collider()
on line 35 and this.physics.add.overlap()
on line 38.
Remember that the BombSpawner
class had a group
property? We use it to create the collider for bombs and platforms on line 36.
Lastly, we spawn a bomb each time a star is collected on line 57.
Give it a try at http://localhost:8000 to see bombs being dropped!
Wrapping up with Game Over
To close the loop on our game we will handle the player getting hit with a bomb.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
import Phaser from 'phaser'
import ScoreLabel from '../ui/ScoreLabel'
import BombSpawner from './BombSpawner'
export default class GameScene extends Phaser.Scene
{
constructor()
{
super('game-scene')
this.player = undefined
this.cursors = undefined
this.scoreLabel = undefined
this.stars = undefined
this.bombSpawner = undefined
this.gameOver = false
}
create()
{
this.add.image(400, 300, 'sky')
const platforms = this.createPlatforms()
this.player = this.createPlayer()
this.stars = this.createStars()
this.scoreLabel = this.createScoreLabel(16, 16, 0)
this.bombSpawner = new BombSpawner(this, BOMB_KEY)
const bombsGroup = this.bombSpawner.group
this.physics.add.collider(this.player, platforms)
this.physics.add.collider(this.stars, platforms)
this.physics.add.collider(bombsGroup, platforms)
this.physics.add.collider(this.player, bombsGroup, this.hitBomb, null, this)
this.physics.add.overlap(this.player, this.stars, this.collectStar, null, this)
this.cursors = this.input.keyboard.createCursorKeys()
}
update()
{
if (this.gameOver)
{
return
}
// ...
}
hitBomb(player, bomb)
{
this.physics.pause()
player.setTint(0xff0000)
player.anims.play('turn')
this.gameOver = true
}
// ...
}
|
The game ends once the player hits a bomb. We handle this with the gameOver
property. It is created in the constructor and used to early exit the update()
method.
A collider for the player and bombs is created on line 37 with this.hitBomb
as the callback when a collision happens.
The hitBomb(player, bomb)
method simply disables physics, tints the player red, and then sets this.gameOver
to true.
And that's it! Try it out at http://localhost:8000 to play the completed game! 🎉
Next Steps
You now know how to use modern JavaScript with Phaser 3!
We tried to keep things similar to the official Phaser guide in earlier parts to help you ease into things.
That means there are more things you can do! Try creating a StarSpawner
? Or encapsulating player controls logic? Take what you've learned and apply it!
Then make more levels or Scenes and experience the joys of code reuse. 😍
Let us know in the comments below if anything doesn’t work or is unclear. We’ll be happy to fix or clarify!
Be sure to sign up for our newsletter so you don't miss any future Phaser 3 game development tips and techniques!
Drop your email into the box below.
Learn to make an Infinite Jumper in Phaser 3 with modern JavaScript!
Drop your email into the box below to get this free 60+ page book and join our newsletter.
Learn more about the book here.