Here we are! The final post to finish up our Invaders game! We’ve come a long way, but we’ve saved what is arguably the most important piece for last: game over.

Game Design and the Art of Zen

Let’s discuss game design for just a moment. We’ve previously gone over some very basic aspects of this, namely the game design decisions we made in order to provide the player a feeling of challenge when we built the combat mechanics back in Let’s Make a Game: Invaders (Part 4). It’s important that we give a lot of thought to these types of decisions when creating games, and it’s a heavily researched topic. I’m not going to dive deep into the psychology behind game design, but it’s a fascinating topic and invaluable to what we’re doing here. To keep this post from going too far off the tracks, we’ll just keep in mind a very easy concept: if our game is too easy, too hard, or provides no good reason to play… no one will want to play it.

How do we accomplish that? Odds are pretty high that you’ve played video games yourself, so the best way to approach game design decisions can be distilled down to a single, simple idea: design games that you want to play. Put yourself into the player’s shoes, and some decisions are really easy — like the combat mechanics in the last post.

We increased the challenge in our game through the combat mechanics in a few ways: the player was limited by not only how often they could shoot — only once every second — but also that they could only fire in a single direction (up), with no ability to aim at any particular invader. On the alien invaders’ side we enhanced their firing ability by allowing them to fire at an angle towards the player’s position at that instant. And… y’know… there’s thirty of them. So far, so good. We’ve got a game that’s not too easy, and not too hard, but the player has no incentive. Nothing happens when they kill all the invaders, and if they happen to get killed by the invaders there’s nothing to really make them want to try again. We need to fix that, but how?

In this post we’re going to finish up by creating an incentive for the player to actually play, and in the event that they lose a way to entice them to try again. This is as much about making decisions as a developer as it is about writing the code, and often times those are equally important.

Let’s get started.

Our first step is to obviously…

Open VSCode, and kickstart our project. Remember, we’ll want to click View > Integrated Terminal (if it’s not already visible when you open VSCode), and run npm start to get our game running in the browser.

Time to make the chimichangas.

Giving the Player an Incentive: Points

We’ve all played games that use points to provide an incentive to play. It’s a simple mechanic, but effective. Most mobile games that you play on a smartphone use this method of player incentive, and the old arcade and Atari games did as well. It’s a good introduction to this aspect of game design, and it has the added benefit of making our old-school recreation feel more authentic.

By providing a visible score — how many points the player has accumulated thus far — they have visible positive feedback when they do the game mechanic that we as game designers want them to do. Namely, we want them to shoot the alien invaders. As they destroy each alien invader, their score goes up. If they don’t make it to the end, this also helps in encouraging them to try again: they’ll want to beat their last score!

We’re going to need a couple of new variables to get us started. Open invaders.js, and just beneath our explosions variable declaration:

// This is a collection of explosions.
var explosions;

// The player's current score.
var score = 0;

// The prefix text for the score.
var scorePrefix = 'SCORE: ';

// The combined scorePrefix and score.
var scoreText;
  • score is sort of self-explanatory. It’s the player’s current score, which is a sum of the points awarded for each alien invader destroyed.

  • scorePrefix is the text that will be displayed just before the score, so the player understands what the score number means at a glance.

  • scoreText is going to be the Phaser text object that actually gets rendered on the screen.

We split the score and scorePrefix for two reasons: first, when we update the score we don’t want to have to re-type “SCORE:” because that would be inefficient; second, we want score to remain a pure number. Way back in JavaScript 101 we discussed the difference between strings and numbers, and how JavaScript uses the + math symbol differently between them. If we used only one variable to store our score label and number, we’d have to go through the trouble of turning the score part of the variable back into a number, adding points to it, and then putting it back into the variable. It’s much simpler to keep them separate, and concatenate them — a fancy word meaning “append one item to the end of another item” — when we need to.

Here it is in VSCode:

Keeping track of player score.
Everything we need to keep track of the player's score.

We also need to wire it up to Phaser, so it actually displays. To do this, let’s head into our handleCreate function, and add the following just after our starfield setup:

// HOOK, PART 2: The create hook.
function handleCreate() {

    // ... previous animation and starfield code ...

    // Setup the score text.
    scoreText = this.add.text( 10, 15, scorePrefix + score );

    // ... previous player, aliens, etc. code ...

If you remember all the way back to Let’s Make a Game: Invaders (Part 2), we learned that our handleCreate function is part of the “Phaser train,” and as long as we’re inside of it we get special access to the Phaser scene via this. We also learned that Phaser provides some very easy helper functions to simplify adding new things to the screen. We’re making use of that here, and the text function of Phaser’s add helper takes three simple parameters, which will be instantly familiar from all of the other items we’ve added to our screen in the game.

The first parameter is the x-coordinate where the text will begin. Likewise, the second parameter is the y-coordinate. We want the text to appear 10 pixels from the left side of the screen and 15 pixels from the top. The final parameter is the actual text to display. We want to show both the score label — “SCORE:” — as well as the score number, so we concatenate them. When it shows up on the screen it will appear as “SCORE: 0”.

Adding the score label and number to the screen.
Our score label and number added to the screen.

Each time the player shoots an alien invader their score should increase, and the score text should update. We already have a logical spot to put this code, which is the point in the code where we register that a bullet has collided with an alien invader: the handleEnemyCollision function.

Let’s update that by adding the following just beneath where we deactivate the alien, and remove it from the screen:

// This will handle a bullet colliding with an alien.
function handleEnemyCollision( bullet, alien ) {
    if ( === true && === true ) {

        // ... previous handleCollision code

        // ... previous deactivate and remove code

        // Increment the score.
        score += 20;
        scoreText.setText( scorePrefix + score );

        // Game Over condition: has the player killed all the alien invaders?
        if ( aliens.countActive() === 0 ) {

            // Award a bonus for winning.
            score += 1000;
            scoreText.setText( scorePrefix + score );

This looks pretty easy, right? First, we’re incrementing the score by adding 20 to itself. Then we’re telling our Phaser text object to update the displayed text by concatenating the scorePrefix and score variables.

Then we’re doing something else: giving the player a bonus for destroying all of the alien invaders. We do that by counting how many of them are still active and visible on the screen (aliens.countActive() is a Phaser group function), and if it’s equal to the number 0, we increment the score number by 1,000 before updating the Phaser text object again.

There’s an interesting discussion point in this code that we’ll touch on at the end, and it deals with putting the point amounts here. Something to think about: could there be a good reason to create variables to store our per-invader and bonus points instead of using them inline like we have here?

Scoring points in the alien collision handler.
Scoring points by shooting alien invaders.

Creating a Sense of Consequence: Player Lives

Right now our game doesn’t have any consequences when an invader’s bullet hits the player. It shows a nifty explosion, but that’s it. If nothing bad can happen to the player — if there are no consequences for moving to avoid the bullets — all of the work we put into our game design and combat mechanics is for nothing, and our game will become stale very quickly. We need to instill a sense of urgency and consequence, which increases the challenge of the game even more while also establishing that the game will end if the player is hit too many times.

To do that, we need to keep track of a couple of things. Head back up to our collection of variable declarations, and add the following just beneath our scoreText variable:

// The combined scorePrefix and score.
var scoreText;

// The "health" of the player.
var playerLives;

// This is going to tell us whether the game should end.
var isGameOver = false;

We’re adding two new variables, one specific to keeping track of player hits, and one that will help us determine whether the conditions have been met for game over.

  • playerLives will be a visible collection of Phaser objects representing how many collisions the player has remaining.

  • isGameOver is a simple boolean value that we can use elsewhere in our code to quickly check if certain things have happened that would impact whether the game can continue. We assign it a value of false here because it’s up to us to decide when it becomes true.

Our variables in VSCode:

Player lives and isGameOver
The beginnings of "Game Over" for the player.

Just like we did for the score text, let’s head into the handleCreate function to begin making use of our new variables just beneath our scoreText:

// HOOK, PART 2: The create hook.
function handleCreate() {

    // ... previous animation and starfield code ...

    // Setup the score text.
    scoreText = this.add.text( 10, 15, scorePrefix + score );

    // Setup the player's lives.
    playerLives =;
        this.sys.canvas.width - 185,    // From the right.
        15,                             // From the top.
    createPlayerLives( this );

    // ... previous player, aliens, etc. code ...

This is all code we’ve seen before, so let’s do a quick overview.

First, we assign a value to our playerLives variable, and since this is going to be a collection of Phaser objects we use Phaser’s group as the value. This is almost identical to how we created the collection of explosions, except we’re not going to put items into this collection right now.

Then we add another Phaser text object to the screen to help the player understand what the display of lives means. Since this is only a label for the player’s benefit — and we don’t need to modify or concatenate it to anything — we didn’t create a variable for it. Instead, we just add it directly to the screen. Just like our score text, we have to give it x- and y-coordinate parameters, and the actual text that we want it to display.

Since Phaser measures spatial positioning from the top-left corner of the screen, we need to do a little math if we want our “LIVES:” text to display in the top-right corner. To set the x-coordinate, we ask the scene to tell us how wide it is, and then subtract 185 pixels from it. We set the y-coordinate to 15, just like our score text, and finally we want our label to show a string of “LIVES:“.

Finally, we call a function called createPlayerLives and pass an argument of this to give it a reference to the Phaser scene. We haven’t written this function yet, so after saving this code your browser will show a broken game. We’ll remedy that next, but first let’s take a look at how this looks in VSCode.

Getting our player lives Phaser objects ready
If you save now, the game will be broken. Let's fix that.

We could just display a number of how many lives the player has left, but that’s no fun. Instead, we’ll use slightly tweaked images of our player’s ship to illustrate this, which is why we included a call to createPlayerLives in the handleCreate function. Let’s write that now, just below our handleEnemyCollision code:

// This will create the player's lives (health).
function createPlayerLives( sceneRef ) {

    // Our x-coordinate for the lives images.
    var x = sceneRef.sys.canvas.width - 105;

    // Only 3.
    for ( var i = 0; i < 3; i++ ) {
        // Calculate this life's x-coordinate.
        var lifeX = x + 40 * i;

        // Add a life to our collection of lives.
        var life = playerLives.create( lifeX, 25, 'ship' );

        // Set the life's origin, scale, and opacity.
        life.setOrigin( 0.5, 0.5 );
        life.setScale( 0.5 );
        life.alpha = 0.4;

We already know — from our function call in handleCreate — that this function needs to have a parameter for the Phaser scene reference. We start right off by using that parameter, which we’ve named sceneRef to stay consistent with our other functions that reference the Phaser scene, to create a variable called x. We’ll use x to establish the x-coordinate of each player life image that we add to the screen.

Then we use a loop just as we did when creating our alien invaders to count between 0 and the maximum number of lives that the player is allowed. For each one, we create a helpful variable called lifeX in order to separate the math needed to calculate the x-coordinate where each life will appear. That calculation adds 40 pixels to our x variable, and multiplies the result by which iteration of the loop we’re in. Order of Operations tells us that multiplication happens before addition, so here’s how those calculations will pan out when x is equal to 695 (the canvas width is 800 pixels, and we’re subtracting 105 from it):

  • i = 0: First, 40 x 0 is 0. Then, 695 + 0 is 695.
  • i = 1: First, 40 x 1 is 40. Then, 695 + 40 is 735.
  • i = 2: First, 40 x 2 is 80. Then, 695 + 80 is 775.

We use that lifeX variable as the x-coordinate of the ship sprite when we added it to the playerLives Phaser group. We give it a y-coordinate of 25 because we’re positioning the center point of the ship sprite image, and we want it to appear vertically-centered next to the “LIVES:” text.

Of course, we have to set the life’s origin, but then we do a couple of nifty things to make sure that the player doesn’t confuse these ships for their own. We make them smaller — setScale( 0.5 ) means “half the size of the regular image” — and decrease their opacity, which is a measure of how opaque something is (a window would have an opacity of 0 because we can see all the way through it). We want these to be slightly transparent, so they don’t distract the player too much. We do this by setting the life’s alpha property to 0.4 which means “40% solid, 60% see-through).

In VSCode:

Creating and showing player lives.
Populating our playerLives Phaser group.

And just like we did for the points scoring, we’re going to wire up the playerLives functionality where it makes the most sense: then an alien invader’s bullet collides with the player. Let’s update the handlePlayerCollision function:

// This will handle a bullet colliding with the player.
function handlePlayerCollision( player, bullet ) {

    // If both the player and bullet are active...
    if ( === true && === true ) {

        // Fire the generic collision handler.
        handleCollision( player, bullet );

        // Remove a life.
        var life = playerLives.getFirstAlive();
        if ( life ) {
            life.setActive( false ).setVisible( false );

In order to remove a life from the playerLives group, we’re going to attempt to grab the first one that’s active and visible. Phaser provides the getFirstAlive function as a handy way of doing this for our group. Since Phaser may not find an active and visible life, we check to make sure we have one. If we do, we deactivate it and remove it from the screen.

Player collision
I've been hit!

Displaying “Game Over” to the Player

Whether the player wins by destroying all of the alien invaders, or is destroyed by them, we need to let them know that the game has ended. We also need to provide them a means of trying again. We’re going to create two variables for this, so just beneath our isGameOver variable declaration, add the following:

// This is going to tell us whether the game should end.
var isGameOver = false;

// The "Game Over" screen.
var gameOverModal;

// The "Game Over" text.
var gameOverText;

When the game ends, we’re going to darken the screen as a visual indicator to the player that the game is disabled as well as to draw attention to some text that we’ll display. gameOverModal is just a simple square that will fill the whole screen with a mostly opaque dark color, and gameOverText is the Phaser text object that we’ll put on top of it.

In VSCode:

Variables for displaying
Variables to help us display Game Over status to the player.

We’re going to separate the creation of our Game Over display objects into a new function. Just beneath the createPlayerLives function that we just wrote we’ll write the following — get ready, we’re doing a lot here:

// This will setup and handle our game over screen.
function createGameOverModal( sceneRef ) {

    // Create a "modal" window.
    gameOverModal =;

    // Set its background color.
    gameOverModal.fillStyle( 0x303030, 0.8 );

    // Set its shape, x- and y-coordinates, and size.

    // It shouldn't be visible... yet.
    gameOverModal.visible = false;

    // Get our game over text ready.
    gameOverText = sceneRef.add.text(
        sceneRef.sys.canvas.width / 2,
        sceneRef.sys.canvas.height / 2,
        ' ',
            align: 'center'
    gameOverText.setOrigin( 0.5, 0.5 );

    // It shouldn't be visible... yet.
    gameOverText.visible = false;

    // Handle the player wanting to start over on mouse click.
    sceneRef.input.on( 'pointerdown', ( pointer ) => {

        // Only on a Game Over condition.
        if ( isGameOver ) {

            // Reset everything.
            bullets.clear( true, true );
            enemyBullets.clear( true, true );
            explosions.clear( true, true );
            aliens.clear( true, true );
            playerLives.clear( true, true );

            // Create again.
            createPlayerLives( sceneRef );
            player.setActive( true ).setVisible( true );

            // Hide the text, followed by the modal.
            gameOverText.visible = false;
            gameOverModal.visible = false;            

            // Reset the game over state.
            isGameOver = false;
    }, sceneRef );

Yep, that’s a lot, but nothing we haven’t already seen before! Let’s go over it.

We start off by creating a new Phaser graphics object, and assigning it as the value of gameOverModal. We then tell gameOverModal that it will be a slightly transparent color of dark grey by using two parameters: the first is a special variation of an HTML hex color code, and the second is the opacity value (80% opaque; 20% transparent).

Then, we tell gameOverModal that it will be a rectangle shape using Phaser’s fillRect function. This function takes four parameters: the x-coordinate, y-coordinate, width, and height. Since we want it to fill the entire screen, we set its coordinates to 0 — top-left — and give it the same width and height as the screen.

After that, we make it invisible because we’re just preparing it here, not using it. Not yet.

Next, it’s on to the text, which we handle just like the score and lives text. You’ll notice that we set the text’s coordinates to the screen’s width and height divided in half. We do this because we want this text centered both horizontally and vertically smack in the middle of the screen, so we’re sure to get the player’s attention. We don’t have any text to use yet, so we just set it to a blank string (' '), but… we add one more parameter to text, and it’s an object that lets us define additional display properties. We want the text itself to display as center-aligned.

We wrap up the gameOverText by setting its origin, and hiding it because we’re not ready to use it yet.

The last part of our new function listens for the user to click their mouse anywhere on the screen. This works just like the keyboard handling that we wrote in Let’s Make a Game: Invaders (Part 3), and when the user clicks our code does the following:

  • Checks to make sure that isGameOver is set to true. If it is, then…
  • We reset all of our Phaser groups. Phaser provides a group function called clear that takes two parameters: whether to disable all of the items in the group, and whether to recreate them to their original values. We want to do both.
  • We then call our special functions to createAliens and createPlayerLives, since they need to be re-populated.
  • We put the player back on the screen, just in case the game ended because they died.
  • The Game Over display objects have to be hidden again, since the player wants to restart.
  • Finally, we set isGameOver to false, so we can actually start playing again.

What we’re doing here is forcing the game to start completely over by making use of everything we’ve already created, and just telling all of them to go back to the beginning.

For this VSCode screenshot, I had to remove the comments in order to fit it all!

createGameOverModal function
That's a big function!

A single line of code in the handleCreate function is all we need to wire it up! As the very last line of handleCreate:

// HOOK, PART 2: The create hook.
function handleCreate() {

    // ... previous code ...

    // Setup the game over screen.
    createGameOverModal( this );

In VSCode:

createGameOverModal wired up
All wired up with nowhere to go!

Game Over

Home stretch! It’s been a long journey, but this is it! We’ve got one more function to write, and it’s going to handle some basic cleanup tasks before showing the player that the game has ended. We’ll call it — naturally — handleGameOver, and it’s going to take a simple parameter called didPlayerWin… a boolean that’ll let us know what text to show the user.

After the createGameOverModal function that we just wrote:

// Shows our game over modal with text based on whether the player won.
function handleGameOver( didPlayerWin ) {

    // Set the condition flag, so the aliens stop firing if any are left.
    isGameOver = true;

    // Remove and disable a group item.
    var removeDisableItem = function( item ) {
        item.setActive( false ).setVisible( false );

    // Disable all bullets, so no one can fire.
    Phaser.Utils.Array.Each( bullets.getChildren(), removeDisableItem );
    Phaser.Utils.Array.Each( enemyBullets.getChildren(), removeDisableItem );
    Phaser.Utils.Array.Each( aliens.getChildren(), removeDisableItem );

    // Disable the player.
    player.setActive( false ).setVisible( false );

    // The text to display, based on whether the player won.
    var displayText = ( didPlayerWin )
        ? ' YOU WON! \n\n Click to restart.'
        : ' GAME OVER \n\n Click to restart.';

    // Set the text.
    gameOverText.setText( displayText );

    // Show the modal, followed by the text.
    gameOverModal.visible = true;
    gameOverText.visible = true;

We’re doing quite a bit here, so let’s take a look:

  • We set isGameOver to true, and we’re going to use that next to make sure the aliens stop firing if any are left.

  • We create an inline function called removeDisableItem, which we’ll use with our bullets, enemyBullets, and aliens groups to make sure they immediately deactivate and disappear from the screen.

  • We use a handy Phaser utility function to loop through our three groups, and for each one we tell it to run our inline removeDisableItem function.

  • We disable the player, and remove the ship from the screen.

  • We create a variable called displayText, and set its value according to whether the didPlayerWin parameter is true or false. This looks different, right? It’s called a conditional or ternary operation, and it’s a shortcut way of saying, “If this thing is true then the value is this, otherwise the value is that.” Here, we’re saying, “If the player won the value of displayText is ’ YOU WON…’, otherwise the value of displayText is ’ GAME OVER…’.” Notice the two \n in each of those strings? That tells Phaser that it should insert a new line in that location. It’s the same as pressing the Enter key twice when you’re typing text in Notepad or Microsoft Word.

  • We then update gameOverText with the value of displayText, and finish off by making both the gameOverModal and gameOverText visible.

Phew! Let’s see that in VSCode before we wire everything up:

handleGameOver function
Handling "Game Over"...

Alright, let’s put a bow on this puppy! We need to wire up our new function, so that it’s actually used. We’ll start by updating our handleUpdate function:

// HOOK, PART 3: The update hook.
function handleUpdate( time ) {
    // Scroll our starfield background.
    starfield.tilePositionY += isGameOver ? 0.5 : 2;

    // ... previous cursor handling code ...
    // If the invaders haven't fired recently - and the game isn't over - fire one.
    if ( time > lastAlienBulletTime && !isGameOver ) {
        fireEnemyBullet( player, time, this );

We updated two lines here. First, we use another ternary operation to change the scrolling speed of our starfield background. This isn’t strictly necessary, but slowing it down when isGameOver is set to true not only makes it more visually interesting, but also less distracting to the player.

Then we updated the conditions for when the alien invaders can fire by adding an additional condition to make sure that the game isn’t over yet. When we include ! in front of a boolean variable, we’re saying, “If this variable is not true,” so now our fireEnemyBullet function will only be called if the invaders haven’t fired recently and if the game is not over.

Game Over in handleUpdate
Updating the update... Update-ception.

Let’s wire up the Game Over condition for the player losing all of their lives by updating handlePlayerCollision:

// This will handle a bullet colliding with the player.
function handlePlayerCollision( player, bullet ) {

    // If both the player and bullet are active...
    if ( === true && === true ) {

        // ... previous code ...

        // Game Over condition: has the player lost all their lives?
        if ( playerLives.countActive() < 1 ) {
            handleGameOver( false );

Super simple, right? We’re just checking how many items are left in playerLives, and calling handleGameOver with an argument of false — because the parameter is didPlayerWin — to end the game.

handlePlayerCollision update
Sorry, old chap. Better luck next time.

And last… but not least… we write some code to handle the other game-ending condition, which is when the player has destroyed all of the enemy invaders. Let’s head into handleEnemyCollision this time, and update it like so:

// This will handle a bullet colliding with an alien.
function handleEnemyCollision( bullet, alien ) {
    if ( === true && === true ) {

        // ... previous code ...

        // Game Over condition: has the player killed all the alien invaders?
        if ( aliens.countActive() === 0 ) {

            // ... previous code ...

            // Handle Game Over.
            handleGameOver( true );

We already wrote the code to count the number of remaining aliens when we awarded a score bonus. All we need to do here is add our call to handleGameOver, and pass true because the player did win.

handleEnemyCollision update
Well done, captain! You eliminated the alien invader threat!

Save your invaders.js file, and check out the game in the web browser!


You did it! You wrote a complete game that you can play anytime in a web browser, and you did it from scratch! Well done! You should absolutely show your new creation to friends and family, or even just play it yourself.

I have some final thoughts on this project that I’d like to share:

  • No code is “perfect,” and there will always be bugs. It’s absolutely critical to play-test everything. I intentionally left a bug in this code to help you practice finding and fixing bad code. This is a much larger topic that we’ll cover in some depth in a future post, but it’s important to know that squashing bugs is a very useful and expected skill for developers. I really want you to attempt to find and fix this on your own — it’s a small one that you’ll have to play the game a bit to find — but if you can’t find it, or you want to cheat and skip ahead you can find the solution here.

  • You don’t have to stop improving this game just because these posts are over! There are still quite a few things that can be done to improve the game. Here’s a few ideas:

    • Make the game harder when the player wins, and wants to restart. I mentioned this earlier, but moving the per-invader and bonus points to their own variables would help in this. Each time the player beats the game and clicks continue, make the aliens fire a little more frequently, make their bullets travel a little faster, and increase the points awarded.
    • You could show a “high scores” screen when the game ends. To do this, I encourage you to do a little research on a built-in functionality in every web browser called localStorage, which you could use to save the score each time the game ends and then display these scores later.
    • The Game Over display objects are a bit plain right now. They’re very simple. You could make them more interesting by experimenting with some other pieces of Phaser’s engine. There’s lots of good ideas and code examples on Phaser’s “Labs” site.
    • Experiment a bit with the graphical assets. If you don’t have access to Adobe Photoshop, Affinity Photo is a fantastic low-cost alternative; if you’re familiar with Photoshop you’ll feel right at home. If you’re looking for something free, there’s always GIMP!

I want to thank you so much for taking the time to go through these posts. I hope you learned a bit, and maybe even had a little fun!

The next project series will be focusing on some more practical development for the web, which means we’ll be creating our very own website!

You can find the source code for this series at GitHub.