Making Your First Phaser 3 Game in Modern Javascript - Part 5

Write better code using modern JavaScript for Phaser 3

by on 7 minute read


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.

Phaser 3 JavaScript HTML5 Guide Tutorial

Want tips and techniques more suited for you?


You may also like...


Video Guides


Beginner Guides


Articles Recommended For You

Fix Stretched Image Distortions in Phaser 3 with 9-Slice Scaling

by on

Are you having image distortion problems when scaling to make a button or panel graphic bigger? Multiple versions of the …

5 minute read

Command Pattern to Undo Player Actions

by on

Are you looking for a clean and reusable way to implement undo for player actions? Perhaps you are making a turn-based …

15 minute read

Advanced Logging with the Strategy Pattern

by on

Have you ever tried debugging a problem with your game that only seems to happen in production? The Developer Tools …

7 minute read

State Pattern for Character Movement in Phaser 3

by on

Writing clean and well-organized code is something all game developers aspire to. We want code to be reusable and easy …

7 minute read

Didn't find what you were looking for?


comments powered by Disqus