Afternoon 4: Health, Score, and Win/Lose Conditions
Our game looks more like a real game now, but there’s still a lot of room for improvement.
Enemy Health
Phaser makes modifying enemy toughness easy for us because it supports health and damage calculation.
Before we could implement health to our enemies, let’s first add a hit animation (finally using the last frame of the sprite sheet):
setupEnemies: function () {
...
this.enemyPool.setAll('checkWorldBounds', true)
// Set the animation for each sprite
this.enemyPool.forEach(function (enemy) {
enemy.animations.add('fly', [ 0, 1, 2 ], 20, true);
enemy.animations.add('hit', [ 3, 1, 3, 2 ], 20, false);
enemy.events.onAnimationComplete.add( function (e) {
e.play('fly');
}, this);
});
this.nextEnemyAt = 0;
The new animation is a very short non-looping blinking animation which goes back to the original fly animation once it ends.
Let’s now add the health. Sprites in Phaser have a default health value of 1 but we can override it anytime:
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, this.game.width - 20), 0);
enemy.reset(
this.rnd.integerInRange(20, this.game.width - 20), 0,
BasicGame.ENEMY_HEALTH
);
enemy.body.velocity.y = this.rnd.integerInRange(
BasicGame.ENEMY_MIN_Y_VELOCITY, BasicGame.ENEMY_MAX_Y_VELOCITY
);
enemy.play('fly');
}
},
We could have used enemy.health = BasicGame.ENEMY_HEALTH but reset() already has an optional parameter that does the same.
And finally, let’s create a new function to process the damage, centralizing the killing and explosion animation:
enemyHit: function (bullet, enemy) {
bullet.kill();
this.explode(enemy);
enemy.kill();
this.damageEnemy(enemy, BasicGame.BULLET_DAMAGE);
},
playerHit: function (player, enemy) {
this.explode(enemy);
enemy.kill();
// crashing into an enemy only deals 5 damage
this.damageEnemy(enemy, BasicGame.CRASH_DAMAGE);
this.explode(player);
player.kill();
},
203 damageEnemy: function (enemy, damage) {
204 enemy.damage(damage);
205 if (enemy.alive) {
206 enemy.play('hit');
207 } else {
208 this.explode(enemy);
209 }
210 },
Using damage() automatically kill()s the sprite once its health is reduced to zero.

Player Score
We don’t need to explain how important it is to display the player’s current score on the screen. Everyone just knows it.
First set the score rewarded on kill:
setupEnemies: function () {
...
this.enemyPool.setAll('outOfBoundsKill', true);
this.enemyPool.setAll('checkWorldBounds', true);
this.enemyPool.setAll('reward', BasicGame.ENEMY_REWARD, false, false, 0, true);
// Set the animation for each sprite
this.enemyPool.forEach(function (enemy) {
We used the full form of the setAll() function. The last four parameters are default, and we only change the last parameter to true which forces the function to set the reward property even though it isn’t there.
Next step is to add the setupText() code for displaying the starting score:
setupText: function () {
this.instructions = this.add.text(
this.game.width / 2,
this.game.height - 100,
'Use Arrow Keys to Move, Press Z to Fire\n' +
'Tapping/clicking does both',
{ font: '20px monospace', fill: '#fff', align: 'center' }
);
this.instructions.anchor.setTo(0.5, 0.5);
this.instExpire = this.time.now + BasicGame.INSTRUCTION_EXPIRE;
this.score = 0;
this.scoreText = this.add.text(
this.game.width / 2, 30, '' + this.score,
{ font: '20px monospace', fill: '#fff', align: 'center' }
);
this.scoreText.anchor.setTo(0.5, 0.5);
},
And then let’s add it to our enemy damage/death handler:
damageEnemy: function (enemy, damage) {
enemy.damage(damage);
if (enemy.alive) {
enemy.play('hit');
} else {
this.explode(enemy);
this.addToScore(enemy.reward);
}
},
221 addToScore: function (score) {
222 this.score += score;
223 this.scoreText.text = this.score;
224 },

Player Lives
Sudden death games are cool, but may be “unfun” for others. Most people are used to having lives and retries in their games.
First, let’s create a new sprite group representing our lives at the top right corner of the screen.
create: function () {
this.setupBackground();
this.setupPlayer();
this.setupEnemies();
this.setupBullets();
this.setupExplosions();
this.setupPlayerIcons();
this.setupText();
this.cursors = this.input.keyboard.createCursorKeys();
},
...
118 setupPlayerIcons: function () {
119 this.lives = this.add.group();
120 // calculate location of first life icon
121 var firstLifeIconX = this.game.width - 10 - (BasicGame.PLAYER_EXTRA_LIVES * 30);
122 for (var i = 0; i < BasicGame.PLAYER_EXTRA_LIVES; i++) {
123 var life = this.lives.create(firstLifeIconX + (30 * i), 30, 'player');
124 life.scale.setTo(0.5, 0.5);
125 life.anchor.setTo(0.5, 0.5);
126 }
127 },
For the life icons, we just used the player’s sprite and scaled it down to half its size by modifying the scale property.
With the life tracking done, let’s add the blinking ghost animation on player death:
this.player.animations.add('fly', [ 0, 1, 2 ], 20, true);
this.player.animations.add('ghost', [ 3, 0, 3, 1 ], 20, true);
this.player.play('fly');
Then let’s modify playerHit() to activate “ghost mode” for 3 seconds and ignore everything around us while we’re a ghost:
playerHit: function (player, enemy) {
// check first if this.ghostUntil is not not undefined or null
if (this.ghostUntil && this.ghostUntil > this.time.now) {
return;
}
// crashing into an enemy only deals 5 damage
this.damageEnemy(enemy, BasicGame.CRASH_DAMAGE);
this.explode(player);
player.kill();
var life = this.lives.getFirstAlive();
if (life !== null) {
life.kill();
this.ghostUntil = this.time.now + BasicGame.PLAYER_GHOST_TIME;
this.player.play('ghost');
} else {
this.explode(player);
player.kill();
}
},
And finally, we modify the processDelayedEffects() function to check if the ghost mode has already expired:
processDelayedEffects: function () {
if (this.instructions.exists && this.time.now > this.instExpire) {
this.instructions.destroy();
}
if (this.ghostUntil && this.ghostUntil < this.time.now) {
this.ghostUntil = null;
this.player.play('fly');
}
},

Win/Lose Conditions, Go back to Menu
One of the last things we need to implement is a game ending condition. Currently, our player can die, but there’s no explicit message whether the game is over or not. On the other hand, we also don’t have a “win” condition.
Let’s implement both to wrap up our prototype.
Create a new function to display the end game message:
255 displayEnd: function (win) {
256 // you can't win and lose at the same time
257 if (this.endText && this.endText.exists) {
258 return;
259 }
260
261 var msg = win ? 'You Win!!!' : 'Game Over!';
262 this.endText = this.add.text(
263 this.game.width / 2, this.game.height / 2 - 60, msg,
264 { font: '72px serif', fill: '#fff' }
265 );
266 this.endText.anchor.setTo(0.5, 0);
267
268 this.showReturn = this.time.now + BasicGame.RETURN_MESSAGE_DELAY;
269 },
Modify the playerHit() function to call the “Game Over!” message:
playerHit: function (player, enemy) {
...
} else {
this.explode(player);
player.kill();
this.displayEnd(false);
}
Do the same to the addToScore() function, but now to destroy all enemies (preventing accidental death and also stopping them from spawning) and display “You Win!!!” message upon reaching 2000 points:
addToScore: function (score) {
this.score += score;
this.scoreText.text = this.score;
if (this.score >= 2000) {
this.enemyPool.destroy();
this.displayEnd(true);
}
},
(No need to set 2000 as a constant because it’s only a temporary placeholder. We’ll change this value in the last afternoon chapter.)
Let’s also display a “back to main menu” message a few seconds after the game ends. In processDelayedEffects():
this.player.play('fly');
}
if (this.showReturn && this.time.now > this.showReturn) {
this.returnText = this.add.text(
this.game.width / 2, this.game.height / 2 + 20,
'Press Z or Tap Game to go back to Main Menu',
{ font: '16px sans-serif', fill: '#fff'}
);
this.returnText.anchor.setTo(0.5, 0.5);
this.showReturn = false;
}
},
Since our main menu button is the same action as firing bullets, we can modify processPlayerInput() function to allow us to quit the game:
if (this.input.keyboard.isDown(Phaser.Keyboard.Z) ||
this.input.activePointer.isDown) {
this.fire();
if (this.returnText && this.returnText.exists) {
this.quitGame();
} else {
this.fire();
}
}
},
Before going back to the main menu, let’s destroy all objects in the world to allow us to play over and over again:
quitGame: function (pointer) {
// Here you should destroy anything you no longer need.
// Stop music, delete sprites, purge caches, free resources, all that good stuff.
this.sea.destroy();
this.player.destroy();
this.enemyPool.destroy();
this.bulletPool.destroy();
this.explosionPool.destroy();
this.instructions.destroy();
this.scoreText.destroy();
this.endText.destroy();
this.returnText.destroy();
// Then let's go back to the main menu.
this.state.start('MainMenu');
}
Going back to the main menu will display a black screen with text. This is because we skipped loading the title page image in preloader.js. To properly display the main menu, let’s temporarily add the pre-loading in mainMenu.js:
BasicGame.MainMenu.prototype = {
preload: function () {
this.load.image('titlepage', 'assets/titlepage.png');
},
create: function () {
Enjoy playing your prototype game!


