Intermission: Refactoring
Before we proceed with the rest of the lessons, let’s refactor the code to make it easier for us to change and maintain the code later. This should not change the behavior of the game, so this is just an intermission rather than a full afternoon chapter.
Refactoring Functions
First on our list of things to refactor are our create() and update() functions. They’re getting bigger and they will be worse as we proceed with the workshop. We’ll refactor them by splitting these large functions into smaller functions.
Refactoring create
Let’s start by extracting functions out of create(). Replace the contents of the function with:
15 create: function () {
16 this.setupBackground();
17 this.setupPlayer();
18 this.setupEnemies();
19 this.setupBullets();
20 this.setupExplosions();
21 this.setupText();
22
23 this.cursors = this.input.keyboard.createCursorKeys();
24 },
Then insert the following after render():
79 //
80 // create()- related functions
81 //
82 setupBackground: function () {
83 this.sea = this.add.tileSprite(0, 0, 800, 600, 'sea');
84 this.sea.autoScroll(0, 12);
85 },
86
87 setupPlayer: function () {
88 this.player = this.add.sprite(400, 550, 'player');
89 this.player.anchor.setTo(0.5, 0.5);
90 this.player.animations.add('fly', [ 0, 1, 2 ], 20, true);
91 this.player.play('fly');
92 this.physics.enable(this.player, Phaser.Physics.ARCADE);
93 this.player.speed = 300;
94 this.player.body.collideWorldBounds = true;
95 // 20 x 20 pixel hitbox, centered a little bit higher than the center
96 this.player.body.setSize(20, 20, 0, -5);
97 },
98
99 setupEnemies: function () {
100 this.enemyPool = this.add.group();
101 this.enemyPool.enableBody = true;
102 this.enemyPool.physicsBodyType = Phaser.Physics.ARCADE;
103 this.enemyPool.createMultiple(50, 'greenEnemy');
104 this.enemyPool.setAll('anchor.x', 0.5);
105 this.enemyPool.setAll('anchor.y', 0.5);
106 this.enemyPool.setAll('outOfBoundsKill', true);
107 this.enemyPool.setAll('checkWorldBounds', true);
108
109 // Set the animation for each sprite
110 this.enemyPool.forEach(function (enemy) {
111 enemy.animations.add('fly', [ 0, 1, 2 ], 20, true);
112 });
113
114 this.nextEnemyAt = 0;
115 this.enemyDelay = 1000;
116 },
117
118 setupBullets: function () {
119 // Add an empty sprite group into our game
120 this.bulletPool = this.add.group();
121
122 // Enable physics to the whole sprite group
123 this.bulletPool.enableBody = true;
124 this.bulletPool.physicsBodyType = Phaser.Physics.ARCADE;
125
126 // Add 100 'bullet' sprites in the group.
127 // By default this uses the first frame of the sprite sheet and
128 // sets the initial state as non-existing (i.e. killed/dead)
129 this.bulletPool.createMultiple(100, 'bullet');
130
131 // Sets anchors of all sprites
132 this.bulletPool.setAll('anchor.x', 0.5);
133 this.bulletPool.setAll('anchor.y', 0.5);
134
135 // Automatically kill the bullet sprites when they go out of bounds
136 this.bulletPool.setAll('outOfBoundsKill', true);
137 this.bulletPool.setAll('checkWorldBounds', true);
138
139 this.nextShotAt = 0;
140 this.shotDelay = 100;
141 },
142
143 setupExplosions: function () {
144 this.explosionPool = this.add.group();
145 this.explosionPool.enableBody = true;
146 this.explosionPool.physicsBodyType = Phaser.Physics.ARCADE;
147 this.explosionPool.createMultiple(100, 'explosion');
148 this.explosionPool.setAll('anchor.x', 0.5);
149 this.explosionPool.setAll('anchor.y', 0.5);
150 this.explosionPool.forEach(function (explosion) {
151 explosion.animations.add('boom');
152 });
153 },
154
155 setupText: function () {
156 this.instructions = this.add.text( 400, 500,
157 'Use Arrow Keys to Move, Press Z to Fire\n' +
158 'Tapping/clicking does both',
159 { font: '20px monospace', fill: '#fff', align: 'center' }
160 );
161 this.instructions.anchor.setTo(0.5, 0.5);
162 this.instExpire = this.time.now + 10000;
163 },
We also added a call to this.sea.autoScroll() so that we can remove the this.sea.tilePosition.y += 0.2 from the update() later.
Refactoring update
Now replace the contents of update() with the following:
26 update: function () {
27 this.checkCollisions();
28 this.spawnEnemies();
29 this.processPlayerInput();
30 this.processDelayedEffects();
31 },
Insert the new functions after the create() functions:
122 //
123 // update()- related functions
124 //
125 checkCollisions: function () {
126 this.physics.arcade.overlap(
127 this.bulletPool, this.enemyPool, this.enemyHit, null, this
128 );
129
130 this.physics.arcade.overlap(
131 this.player, this.enemyPool, this.playerHit, null, this
132 );
133 },
134
135 spawnEnemies: function () {
136 if (this.nextEnemyAt < this.time.now && this.enemyPool.countDead() > 0) {
137 this.nextEnemyAt = this.time.now + this.enemyDelay;
138 var enemy = this.enemyPool.getFirstExists(false);
139 // spawn at a random location top of the screen
140 enemy.reset(this.rnd.integerInRange(20, 780), 0);
141 // also randomize the speed
142 enemy.body.velocity.y = this.rnd.integerInRange(30, 60);
143 enemy.play('fly');
144 }
145 },
146
147 processPlayerInput: function () {
148 this.player.body.velocity.x = 0;
149 this.player.body.velocity.y = 0;
150
151 if (this.cursors.left.isDown) {
152 this.player.body.velocity.x = -this.player.speed;
153 } else if (this.cursors.right.isDown) {
154 this.player.body.velocity.x = this.player.speed;
155 }
156
157 if (this.cursors.up.isDown) {
158 this.player.body.velocity.y = -this.player.speed;
159 } else if (this.cursors.down.isDown) {
160 this.player.body.velocity.y = this.player.speed;
161 }
162
163 if (this.input.activePointer.isDown &&
164 this.physics.arcade.distanceToPointer(this.player) > 15) {
165 this.physics.arcade.moveToPointer(this.player, this.player.speed);
166 }
167
168 if (this.input.keyboard.isDown(Phaser.Keyboard.Z) ||
169 this.input.activePointer.isDown) {
170 this.fire();
171 }
172 },
173
174 processDelayedEffects: function () {
175 if (this.instructions.exists && this.time.now > this.instExpire) {
176 this.instructions.destroy();
177 }
178 },
Reducing Hard-coded Values
Apart from long functions, our game also has many hard-coded values and this may affect code readability and maintenance later.
Eliminating all hard-coded values would be overkill especially for a tutorial like this, so our goal here would be show the ways how we could reduce them.
Using Relative Values
A good portion of the hard-coded values are x-y coordinates. Replacing them with values relative to game.width and game.height will allow us to change the size of the game later with minimal impact to the code.
Let’s start with the background tile sprite:
setupBackground: function () {
this.sea = this.add.tileSprite(0, 0, 800, 600, 'sea');
this.sea = this.add.tileSprite(0, 0, this.game.width, this.game.height, 'sea');
this.sea.autoScroll(0, 12);
},
Then we change the player starting location to the bottom middle of the screen:
setupPlayer: function () {
this.player = this.add.sprite(400, 550, 'player');
this.player = this.add.sprite(this.game.width / 2, this.game.height - 50, 'player');
this.player.anchor.setTo(0.5, 0.5);
Also the instruction text:
setupText: function () {
this.instructions = this.add.text( 400, 500,
this.instructions = this.add.text(
this.game.width / 2,
this.game.height - 100,
'Use Arrow Keys to Move, Press Z to Fire\n' +
And finally the spawn location for the enemies:
spawnEnemies: function () {
if (this.nextEnemyAt < this.time.now && this.enemyPool.countDead() > 0) {
this.nextEnemyAt = this.time.now + this.enemyDelay;
var enemy = this.enemyPool.getFirstExists(false);
// spawn at a random location top of the screen
enemy.reset(this.rnd.integerInRange(20, 780), 0);
enemy.reset(this.rnd.integerInRange(20, this.game.width - 20), 0);
// also randomize the speed
enemy.body.velocity.y = this.rnd.integerInRange(30, 60);
One advantage of using relative values is that we can change the dimensions of the game without having to change any of the code. For example, here’s the game with the height and width flipped at app.js:

Using Constants
We can also replace many hard-coded values with constants. If you open boot.js, you’ll see that all of the constants that we need for this workshop are already defined under the BasicGame object. All we need to do is to replace the existing code with their respective constants:
setupBackground: function () {
this.sea = this.add.tileSprite(0, 0, this.game.width, this.game.height, 'sea');
this.sea.autoScroll(0, 12);
this.sea.autoScroll(0, BasicGame.SEA_SCROLL_SPEED);
},
setupPlayer: function () {
...
this.physics.enable(this.player, Phaser.Physics.ARCADE);
this.player.speed = 300;
this.player.speed = BasicGame.PLAYER_SPEED;
this.player.body.collideWorldBounds = true;
setupEnemies: function () {
...
this.nextEnemyAt = 0;
this.enemyDelay = 1000;
this.enemyDelay = BasicGame.SPAWN_ENEMY_DELAY;
},
setupBullets: function () {
...
this.nextShotAt = 0;
this.shotDelay = 100;
this.shotDelay = BasicGame.SHOT_DELAY;
},
setupText: function () {
...
this.instructions.anchor.setTo(0.5, 0.5);
this.instExpire = this.time.now + 10000;
this.instExpire = this.time.now + BasicGame.INSTRUCTION_EXPIRE;
},
spawnEnemies: function () {
if (this.nextEnemyAt < this.time.now && this.enemyPool.countDead() > 0) {
...
// also randomize the speed
enemy.body.velocity.y = this.rnd.integerInRange(30, 60);
enemy.body.velocity.y = this.rnd.integerInRange(
BasicGame.ENEMY_MIN_Y_VELOCITY, BasicGame.ENEMY_MAX_Y_VELOCITY
);
enemy.play('fly');
}
},
fire: function () {
...
bullet.body.velocity.y = -500;
bullet.body.velocity.y = -BasicGame.BULLET_VELOCITY;
},