A handheld game console

Manage game states with a simple state machine

Learn how to add game states to your JavaScript game, from the start screen to game over. Build a simple state machine without a game engine, pause the game and restart it cleanly. By the end of this tutorial, you'll have a real game with a score, lives and a high score to beat.

In the game you'll make, you fly your bee around and stay away from the other bees. The longer you stay alive, the more points you score, and every bee that hits you costs a life. The swarm keeps growing, so sooner or later, it's game over. Then you can play again and try to beat your high score.

First, you'll learn what game states are and keep track of them in one variable. Then you'll use them for a start screen, a pause menu and a game over screen, and make sure every new game really starts from scratch. Finally, you'll give each state its own object, so new screens are easy to add and manage.

What is a game state?

Right now, your game starts the moment the page loads, and it never ends. The bees just keep on flying. A real game has a beginning and an end, and a few screens in between.

Each of those screens is a game state. The game is always in exactly one of them, and the state decides what the game draws and how it reacts to the player. Your game gets these four states:

  • Start - Shows the name of the game and waits for you to start playing. The bees already fly around in the background.
  • Playing - The game itself. Everything moves, the score goes up and the bees can hurt you.
  • Paused - Everything stands still, with a menu on top.
  • Game over - Shows your score and waits for you to play again.

The game moves from one state to another when the player does something, like clicking to start or pressing P to pause. Some changes happen by themselves, like game over when you run out of lives. This diagram shows how the game gets from one state to another:

A game that works like this is called a state machine. It has a fixed set of states, and only one of them is active at a time. On this page, you'll build one in plain JavaScript, without a library or a game engine.

Not everything that changes during a game is a state. Your score and your lives change all the time, while the game stays in the playing state. Those are just numbers the game keeps track of. The state is the mode the game is in, and it decides what happens to those values.

You might have seen the term game state in the tutorial about local storage. There, the game state is the data you save, like the upgrades the player bought. On this page, it's the screen the game is on.

Keep track of the state in one variable

You could use a boolean for every state, like isPaused and isGameOver. That works for a while, until something sets both of them to true. Press P on the game over screen, and your game is paused and over at the same time. Which screen should it draw then?

One variable can't have two values at once. Store the state as a string, and the game can only ever be in one state:

// The state of the game:
// 'start', 'playing', 'paused' or 'gameover'
let gameState = 'start';

You built your game loop over the previous parts of this series, starting with the JavaScript game loop tutorial. In this tutorial, you'll build on it again. Right now, gameLoop() does all the work itself: it calls update() on every game object, checks for collisions, clears the canvas and then calls draw() on every game object. Move that work into two new functions for the whole game, also called update() and draw(), and let a switch statement decide what happens in each state:

function gameLoop(timeStamp) {
    // Calculate how much time has passed, capped at a maximum
    let secondsPassed = (timeStamp - oldTimeStamp) / 1000;
    oldTimeStamp = timeStamp;
    secondsPassed = Math.min(secondsPassed, 0.1);
    stateTime += secondsPassed;

    update(secondsPassed);
    draw();

    window.requestAnimationFrame(gameLoop);
}

function update(secondsPassed) {
    switch (gameState) {
        case 'start':
            // The bees already fly around behind the title
            updateObjects(secondsPassed);
            break;
        case 'playing':
            updateObjects(secondsPassed);
            break;
        case 'paused':
        case 'gameover':
            // Everything stands still
            break;
    }
}

function draw() {
    // Clear the canvas and draw all game objects
    context.clearRect(0, 0, canvas.width, canvas.height);
    for (let i = 0; i < gameObjects.length; i++) {
        gameObjects[i].draw();
    }

    // Draw the screen for the current state on top
    switch (gameState) {
        case 'start':
            drawStartScreen();
            break;
        case 'playing':
            drawHud();
            break;
        case 'paused':
            drawHud();
            drawPauseScreen();
            break;
        case 'gameover':
            drawHud();
            drawGameOverScreen();
            break;
    }
}

update() calls updateObjects(), which holds the code that used to be in your game loop. It updates all game objects and checks for collisions. draw() works the same way: it draws all game objects, and then uses a switch to draw the screen for the current state on top. You'll write those screens in the next sections.

To switch to another state, don't change the variable directly. Call a function instead, so every change happens in one place:

// How many seconds the game has been in this state
let stateTime = 0;

function changeState(newState) {
    gameState = newState;
    stateTime = 0;
}

The game loop adds secondsPassed to stateTime every frame, so you always know how long the game has been in the current state. You'll need that for anything that depends on time, like text that fades in and out or a short wait on the game over screen.

Try the four states below. Click the canvas to start, and press P to pause. You steer your bee just like in the game controls tutorial: with the arrow keys or WASD, or by holding down the mouse button or keeping your finger on the screen. In this demo, getting hit by one bee is enough to end the game, so fly into one to see the state change to 'gameover'. The state screens don't exist yet, so the demo shows the name of the current state instead. The code under the demo shows the last few calls to changeState(). The comment after each call says how long the game spent in the state it was leaving:

Live demo The code updates as you play around
let gameState = 'start';

Create a start screen

As soon as the page loads, your game is in the start state. The bees fly around, but the game hasn't really begun yet. It's waiting for the player. Draw the name of the game on top, and a line that tells the player what to do:

function drawStartScreen() {
    // A light layer, so the text stands out from the bees
    context.fillStyle = 'rgba(255, 255, 255, 0.6)';
    context.fillRect(0, 0, canvasWidth, canvasHeight);

    context.fillStyle = '#303030';
    drawText('Pollen Rush', 375, 130, 'bold 48px Arial');

    // Fade the text in and out, once every second
    let pulse = Math.sin(stateTime * 2 * Math.PI);
    context.globalAlpha = 0.6 + 0.4 * pulse;
    drawText('Click to start', 375, 270, '24px Arial');
    context.globalAlpha = 1;
}

function drawText(text, x, y, font) {
    context.font = font;
    context.textAlign = 'center';
    context.textBaseline = 'middle';
    context.fillText(text, x, y);
}

drawText() is a small helper that draws a line of text, centered on the point you give it. You'll use it for every screen in this game. Need a refresher on fonts and text alignment? It's all in the tutorial about drawing on the canvas. The line under the title fades in and out with stateTime and Math.sin(), a trick from the smooth canvas animation tutorial.

A click, a tap, the space bar or Enter starts the game. Check for Space and Enter in onKeyDown():

function onKeyDown(event) {
    // ...

    // Space or Enter to start
    if (event.code == 'Space' || event.code == 'Enter') {
        pressStart();
    }
}

function pressStart() {
    if (gameState == 'start') {
        changeState('playing');
    }
}

Here's the start screen. Click or tap it to start playing:

In this demo, nothing can hurt you yet. And when you click or tap outside the canvas, the demo goes back to the start screen.

Keep score and lose lives only while playing

Now the game needs something to play for. You'll add a score that goes up, bees that can hurt you and new bees that join the swarm. But all of that should only happen in the playing state, not on the start screen or the game over screen.

The score and the new bees are easy. Update them in the playing case of update(), and they stop by themselves in every other state:

let score = 0;

// ...

case 'playing':
    updateObjects(secondsPassed);
    updateSpawning(secondsPassed);

    // 10 points for every second you stay alive
    score += 10 * secondsPassed;
    break;

The lives need a check of their own. Your game checks every pair of game objects for a collision in detectCollisions(), and it keeps doing that on the start screen, because the bees fly around there too. So when a bee touches yours, check the state before it costs you a life:

let lives = 3;

function hitPlayer() {
    // Only while playing, and not right after a hit
    if (gameState != 'playing' || player.invincibleTime > 0) {
        return;
    }

    lives--;
    if (lives == 0) {
        changeState('gameover');
        return;
    }

    // Two seconds to get away
    player.invincibleTime = 2;
}

Call hitPlayer() in detectCollisions() when one of the two bees is player, your own bee. After a hit, invincibleTime gives you two seconds to get away, and your bee blinks until the time runs out. Without that, the swarm would take all your lives in a split second, because every frame in which you touch a bee counts as a new hit. The Player class counts invincibleTime down in its update(), and makes your bee blink in its draw(). You'll find both in the complete code.

Pause the game

With states, pausing is easy: switch to the paused state. In update(), the paused case does nothing, so nothing moves. draw() keeps drawing, so the game stays on the screen, frozen, with the pause screen on top:

function togglePause() {
    if (gameState == 'playing') {
        changeState('paused');
    } else if (gameState == 'paused') {
        changeState('playing');
    }
}

function drawPauseScreen() {
    // Darken the game behind the menu
    context.fillStyle = 'rgba(0, 0, 0, 0.5)';
    context.fillRect(0, 0, canvasWidth, canvasHeight);

    context.fillStyle = 'white';
    drawText('Paused', 375, 100, 'bold 40px Arial');
}

P and Escape are the usual keys for pausing a game. Call togglePause() for both of them in onKeyDown(), right after the check for the space bar. Their codes are 'KeyP' and 'Escape'.

You don't have to stop anything else. Everything that counts down during the game, like invincibleTime and the timer for the next bee, counts down in updateObjects() or updateSpawning(). The paused case calls neither of them, so those timers stop too, and when you resume, they pick up right where they left off.

Notice that the game loop keeps running while the game is paused. That's on purpose: the loop still has to draw the pause screen. Because the loop keeps running, oldTimeStamp stays up to date, so the first frame after a pause is just a normal frame, not one big jump forward in time. For a pause screen, it's simpler to keep the loop running and let the state decide what moves.

Add a pause menu

A pause screen usually has a menu. This one has three buttons: Resume to continue, Restart to start over and Quit to go back to the start screen. On a phone, there's no P key, so there's also a pause button in the top-right corner while you play. All of these buttons are rectangles on the canvas, just like the on-screen touch buttons in the game controls tutorial. This time, each button in the menu also stores the function it calls:

// The pause button in the top-right corner
const pauseButton = {x: 696, y: 14, width: 40, height: 40};

// The buttons on the pause screen
const menuButtons = [
    {
        text: 'Resume',
        x: 285, y: 150, width: 180, height: 46,
        onClick: resumeGame
    },
    {
        text: 'Restart',
        x: 285, y: 212, width: 180, height: 46,
        onClick: startGame
    },
    {
        text: 'Quit',
        x: 285, y: 274, width: 180, height: 46,
        onClick: quitGame
    }
];

function resumeGame() {
    changeState('playing');
}

function quitGame() {
    changeState('start');
}

A click now means something different in every state. On the start screen and the game over screen, it starts a game. While you play, it steers your bee, unless it hits the pause button. And on the pause screen, it presses one of the menu buttons. So let a switch on the state decide what the click does. Here's the whole onPointerDown():

function onPointerDown(event) {
    updatePointer(event);

    switch (gameState) {
        case 'start':
        case 'gameover':
            pressStart();
            break;
        case 'playing':
            if (isOnButton(pauseButton)) {
                pauseGame();
            } else {
                // Fly toward the mouse or finger while it's down
                pointer.isDown = true;
                canvas.setPointerCapture(event.pointerId);
            }
            break;
        case 'paused':
            // Click a button on the pause screen
            for (let i = 0; i < menuButtons.length; i++) {
                if (isOnButton(menuButtons[i])) {
                    menuButtons[i].onClick();
                }
            }
            break;
    }
}

Buttons that you draw on the canvas have one downside: the keyboard and screen readers can't find them. If that matters for your game, put HTML buttons on top of the canvas instead. A real <button> works with Tab and Enter right away, and a screen reader can read it out loud. In this game, P and Escape already do the most important thing: they continue the game.

Pause the game automatically

What happens when the player clicks somewhere else on the page in the middle of a game? The canvas loses focus, so the keys stop working, but the bees keep flying. By the time the player comes back to the game, they've probably lost a life or two. And when the player switches to another tab, the browser stops running your game loop, so the game freezes. It picks up again the moment they come back, before they've even found their bee on the screen.

To fix this, pause the game when the canvas loses focus or the tab is hidden. You've already used both events: blur in the game controls tutorial, and visibilitychange in the JavaScript game loop tutorial. Add both listeners in init(), the function that sets up your game when the page loads:

// Pause when you click somewhere else or switch tabs
canvas.addEventListener('blur', pauseGame);
document.addEventListener('visibilitychange', function () {
    if (document.hidden) {
        pauseGame();
    }
});

function pauseGame() {
    if (gameState == 'playing') {
        changeState('paused');
    }
}

pauseGame() only pauses a game that's running. On the start screen and the game over screen, there's nothing to pause.

Don't resume the game automatically when the player comes back. They may not be ready yet, or they may just be checking something. Let them continue when they want to.

Try pausing the game below. Start the game, and then press P, tap the pause button, click outside the canvas or switch to another tab. On this page, the demos also pause when you scroll them out of view. The pause screen also tells you why the game paused. Only this demo does that, so you can follow what's going on. The buttons work just like they do in your own game:

Show a game over screen

When your last life is gone, hitPlayer() switches to the game over state. Just like on the pause screen, the game stands still, with your score on top:

function drawGameOverScreen() {
    // Darken the game behind the text
    context.fillStyle = 'rgba(0, 0, 0, 0.5)';
    context.fillRect(0, 0, canvasWidth, canvasHeight);

    context.fillStyle = 'white';
    drawText('Game over', 375, 120, 'bold 48px Arial');
    drawText(
        'Score: ' + Math.floor(score), 375, 185, '28px Arial'
    );

    // A click only starts a new game after a second
    if (stateTime > 1) {
        drawText('Click to play again', 375, 300, '24px Arial');
    }
}

Why wait a second? When a bee hits you, you're probably still pressing keys or tapping like crazy to get away. The game over screen appears, your next tap starts a new game right away, and you never even see your score. So pressStart() ignores the player for one second. It also calls startGame() now, instead of changeState('playing'). You'll write startGame() in the next section. Here's the new pressStart():

function pressStart() {
    if (gameState == 'start') {
        startGame();
    }

    // Wait a second, so you don't skip the game over screen by accident
    if (gameState == 'gameover' && stateTime > 1) {
        startGame();
    }
}

The same goes for a key that the player holds down. As long as you hold a key down, the browser keeps sending keydown events, with event.repeat set to true. Holding the space bar would start one game after another. Ignore those repeats for actions like starting and pausing. Here's the whole onKeyDown(), with the check for repeats right before the checks for the keys that start and pause the game:

function onKeyDown(event) {
    keys[event.code] = true;

    // Don't let the game keys scroll the page
    if (gameKeys.includes(event.code)) {
        event.preventDefault();
    }

    // A key that you hold down only counts once
    if (event.repeat) {
        return;
    }

    // Space or Enter to start, P or Escape to pause
    if (event.code == 'Space' || event.code == 'Enter') {
        pressStart();
    } else if (event.code == 'KeyP' || event.code == 'Escape') {
        togglePause();
    }
}

A high score gives the player a reason to play just one more time. Check it when the game is over, and save it in local storage, so it's still there on the next visit. Do that once, at the moment the game ends, in a function that then switches to the game over state:

let highScore = 0;
let isNewHighScore = false;

function gameOver() {
    // Keep the best score, and save it for your next visit
    isNewHighScore = Math.floor(score) > highScore;
    if (isNewHighScore) {
        highScore = Math.floor(score);
        localStorage.setItem('beeHighScore', highScore);
    }
    changeState('gameover');
}

Why not check the high score in update() or drawGameOverScreen()? Those run every frame. The first frame would save your score as the new high score. From the second frame on, your score would no longer be higher than the high score, because it would already be the high score, so New best! would disappear right away. Code that should run only once belongs in the place where the state changes.

When the page loads, read the high score back in init():

// The best score from an earlier visit, or 0
highScore = Number(localStorage.getItem('beeHighScore'));

Reset everything when the game restarts

So what does startGame() do? The score goes back to 0 and the lives go back to 3, of course. Here's a first try:

function startGame() {
    score = 0;
    lives = 3;
    changeState('playing');
}

If you only reset the score and the lives, your bee starts the new game right where the last one ended, with the swarm bee that hit you still right next to it. And the bees that joined the swarm during the game are all still there. Every variable that changes during a game has to go back to how it was at the start, and it's easy to forget one.

The fix is to put all of it in one function, resetGame(), and call it at the start of every game, including the first one. It builds a new world, with a new swarm and your bee in the middle:

function resetGame() {
    // Your own bee, in the middle of the canvas
    player = new Player(context, 375, 200);

    // A small swarm, not too close to you
    gameObjects = [];
    for (let i = 0; i < 6; i++) {
        let spot = findSpawnSpot();
        gameObjects.push(createBee(spot.x, spot.y));
    }
    gameObjects.push(player);

    // Everything else back to how it was at the start
    score = 0;
    lives = 3;
    isNewHighScore = false;
    spawnInterval = 5;
    spawnTimer = spawnInterval;
    nextBee = null;
}

function startGame() {
    resetGame();
    changeState('playing');
}

resetGame() replaces createWorld() from the previous parts. init() calls it when the sprite sheet with the bee images has loaded, so the first game is built in exactly the same way as every game after it. If resetGame() forgets to build something, like the swarm, you'll notice it right away in the first game, instead of only after a restart.

There's one more thing that startGame() shouldn't do: set up the game a second time. Add the event listeners and start the game loop once, in init(), and never in startGame(). If every new game adds its listeners again, one press of P after a restart calls togglePause() twice. The game pauses and resumes right away, so P seems to stop working. And if startGame() starts the game loop too, every restart adds another one, so every frame your game does all of its work twice, then three times, and so on.

Give each state its own object

With four states, the switch statements are still easy to read. But every new screen adds a case to update(), draw() and onPointerDown(), and more checks like gameState == 'playing'. Picture a game with a level select, a settings screen and a shop. The code for a single screen ends up spread across the whole file.

So give each state its own object instead. Every state gets the same four functions: update() and draw(), and onKeyDown() and onPointerDown() for the controls. Here are the start state and the playing state:

const states = {
    start: {
        update(secondsPassed) {
            // The bees already fly around behind the title
            updateObjects(secondsPassed);
        },
        draw() {
            drawStartScreen();
        },
        onKeyDown(code) {
            if (isStartKey(code)) {
                startGame();
            }
        },
        onPointerDown(event) {
            startGame();
        }
    },

    playing: {
        update(secondsPassed) {
            updateObjects(secondsPassed);
            updateSpawning(secondsPassed);

            // 10 points for every second you stay alive
            score += 10 * secondsPassed;
        },
        draw() {
            drawHud();
            drawPauseButton();
        },
        onKeyDown(code) {
            if (isPauseKey(code)) {
                pauseGame();
            }
        },
        onPointerDown(event) {
            if (isOnButton(pauseButton)) {
                pauseGame();
            } else {
                // Fly toward the mouse or finger while it's down
                pointer.isDown = true;
                canvas.setPointerCapture(event.pointerId);
            }
        }
    }

    // ... and the same for paused and gameover
};

// The state the game is in right now
let state = states.start;

isStartKey() and isPauseKey() are two small helpers. They check for Space or Enter, and for P or Escape. The game loop and the event listeners don't need a switch anymore. They pass everything on to the current state:

function gameLoop(timeStamp) {
    // ...

    state.update(secondsPassed);
    draw();

    window.requestAnimationFrame(gameLoop);
}

function onKeyDown(event) {
    // ...

    // The current state decides what the key does
    state.onKeyDown(event.code);
}

function onPointerDown(event) {
    updatePointer(event);

    // The current state decides what the click or tap does
    state.onPointerDown(event);
}

At the end of draw(), state.draw() takes the place of the switch. To switch states, you now pass the object itself: changeState(states.paused) instead of changeState('paused'). And a check like gameState == 'playing' becomes state == states.playing, for example in hitPlayer() and pauseGame().

The paused state and the game over state work the same way, each with its own keys and clicks. To see what the game does on the pause screen, you only have to look at states.paused. A new screen is a new object, and all of its code is in one place. You'll find all four states in the complete code.

Run code when a state begins or ends

Some code has to run only once, at the moment the game switches from one state to another. You've already seen an example: gameOver() saves the high score right before the game switches to the game over state. With state objects, that code can move into the state itself. Give a state an enter() function, and the game calls it when it enters that state. An exit() function runs when the game leaves it:

function changeState(newState) {
    // Let the old state finish, and the new state begin
    if (state.exit) {
        state.exit();
    }
    state = newState;
    stateTime = 0;
    if (state.enter) {
        state.enter();
    }
}

Not every state needs them, so changeState() only calls them when the state has them. The game over state saves the high score in its enter(). Now hitPlayer() can call changeState(states.gameover), just like every other change, and you don't need gameOver() anymore:

gameover: {
    enter() {
        // Keep the best score, and save it for next time
        isNewHighScore = Math.floor(score) > highScore;
        if (isNewHighScore) {
            highScore = Math.floor(score);
            localStorage.setItem('beeHighScore', highScore);
        }
    },

    // ... update(), draw() and the controls
}

enter() and exit() often come in pairs: what one turns on, the other turns off. In the game controls tutorial, you turned off scrolling on the canvas with touch-action: none, so dragging your finger steers the bee instead of scrolling the page. But on the start screen and the game over screen, there's nothing to steer, and on a phone, the player might just want to scroll past the game. So only turn scrolling off while the game is playing:

playing: {
    enter() {
        // Your finger steers the bee instead of scrolling
        canvas.style.touchAction = 'none';
    },
    exit() {
        // On every other screen, the page scrolls again
        canvas.style.touchAction = 'auto';
    },

    // ... update(), draw() and the controls
}

One thing doesn't belong in the playing state's enter(): resetGame(). The game also enters the playing state when you continue after a pause, and that shouldn't start a new game. That's why startGame() calls resetGame() itself.

See enter() and exit() in action below. Start the game, pause it, resume it and then lose. In this demo, getting hit by one bee is enough to end the game. The code under the demo shows each call to enter() and exit() as it happens:

Live demo The code updates as you play around
let state = states.start;

This is still the state machine from the start of this page, only now with an object for every state. Its full name is finite state machine: a fixed number of states, only one of them active at a time, and clear moments when the game switches from one to another. Many game engines work in a similar way: in Phaser, for example, you'd make each state a scene.

So when should you switch from strings to objects? For a small game with a few simple screens, a switch is fine. Once a screen gets its own buttons, timers or things to clean up, objects keep each screen's code in one place, so your game stays easy to change and extend.

Play the finished game

That's everything you need. Your game now has a start screen, a score, lives, a pause menu and a game over screen, and your bee dodges a swarm that keeps growing. The complete code is further down. Click or tap the canvas below to start, and see how long you can stay alive:

What's your best score? Share it in the comments below. And if you change the game, for example by adding faster bees or power-ups, let us know how it went.

Want the bees to hunt you down, instead of just bouncing around? In the tutorial on flocking with boids, a hunter chases the nearest bird in the flock, and the swarm can chase you the same way. And the tutorial on pathfinding shows how an enemy can find its way to you, even around walls.

Common questions about game states

How do I add a countdown before the game starts?

Add a state between the start screen and the game, like states.countdown, and let startGame() switch to it instead of states.playing. The countdown's draw() shows Math.ceil(3 - stateTime) in the middle of the canvas, so you see 3, 2, 1. When stateTime reaches 3, its update() switches to states.playing. The same countdown works after a pause: let resumeGame() switch to it too, so the player has a moment to get ready.

What's a state stack?

A state stack is a list of states, with the active one on top. When you pause, you put the pause screen on top of the game with stack.push(states.paused), and when you continue, you take it off again with stack.pop(). The game loop only updates the top state, stack[stack.length - 1], but it draws all of them, from the bottom up. That's handy when screens sit on top of each other, like a settings screen that you open from the pause menu.

Can a game object have states too?

Yes. Your bee could have its own states, like flying, hurt and invincible, each with its own animation and its own rules. It works just like the game states on this page, only for one object. In this game, invincibleTime is a simple version of that: while it's above 0, your bee is invincible and blinks.

Do I need a library for a state machine?

Not for a game like this. The state machine in this game is just an object for every state and a function that switches between them, so a few lines of plain JavaScript are enough. Libraries like XState add extras, such as states inside states and a fixed list of allowed changes. They help in a big app with a lot of states, but in a small game, it's easier to see what's going on when you write it yourself.

How do I add a level select or a settings screen?

Add a state for each screen, with its own buttons. For a screen with a lot of settings, HTML is often easier than the canvas. Sliders, checkboxes and text fields come with the browser, and they work with the keyboard and screen readers. Show them on top of the canvas when the game switches to that state, and hide them when it leaves.

How do I save the game when the player closes the tab?

Listen for visibilitychange, and save the game in local storage when document.hidden is true. That event also fires when the player closes the tab or switches to another app on their phone. It's more reliable than beforeunload, which mobile browsers often skip.

How do game states work in Phaser or PixiJS?

Phaser has scenes. Each scene has its own create() and update(), and you switch between them with this.scene.start(). Several scenes can run at the same time, like a HUD on top of the game. PixiJS only draws, so you build the states yourself, just like on this page. A container for each screen makes it easy to show and hide all of its objects at once.

The complete code

Here's the complete code of the game, with an object for every state. It picks up where the game controls tutorial left off, and it loads the sprite sheet from spicyyoghurt.com. Paste it into an HTML file and open it in your browser:

What's next?

Your bee now has a real game to play, with a start screen, a score, lives, a pause menu and a game over screen. You've learned how to keep track of the state of your game, switch between states in one place, and reset everything when the game starts over. Every state is now an object of its own, so adding a new screen is easy. And your high score is still there when you come back.

If you have any questions, feel free to ask them in the comment section. You can