We're about to get into the weeds in this first section, but stay with me! The rest will be fun stuff, I promise!
Switching Tabs and Wreaking Havoc¶
With all of this talk in chapter 1 & 2 about time-based modelling and about utilizing the delta-time in between frames, my comment at the end of the last chapter where I mentioned creating a fixed timestep seems counterintuitive, right? Let's do an experiment. Run the game, and click to launch the ball. Then, as quickly as you can, switch to a new tab. Now: count to five. Click back into your old tab, and what do you see? Something impossible, most likely.
Firefox
Chromium
In both browsers, I saw that the ball had phased through all or part of the brick wall, breaking bricks that were impossible to collide with. In Chromium, it's obvious that the tab was completely suspended, and when switching back to the tab, the dt
was equal to 5 seconds, which placed our ball's position far outside the bounds of the canvas. Then, it got bounded to the canvas thanks to the code we wrote, and it appears on top of the wall of bricks. In Firefox, it seems as though the tab was not fully suspended, but it was running at an extremely low framerate, which explains why the ball made an impossible collision.
So we have a problem here, and there are a few ways people tend to fix it:
- Check the
dt
value each frame to make sure it doesn't exceed some arbitary large value, like maybe one fourth of a second. - Don't use
dt
at all. - Create a fixed timestep, and if the computer is running slowly, run multiple updates before rendering.
We are going to combine option 3 and a little bit of option 1.
Creating a Fixed Time Step¶
The first thing we need to set is our desired framerate. For this game, I want to pin it at 60 frames per second, but you could choose whatever you prefer. Then, we need to specify the max delta before the game essentially pauses. For this, I chose 3 frames. So on a device with low performance, the framerate can drop below 60 frames per second, but the game won't run any faster than the desired 60 frames per second.
Now, we need to adjust our main
game loop. We bound dt
to the maxDelta
, but if the timeElapsed
is lower than maxDelta
, we add timeElapsed
to the dt
. Then, if dt
is greater than the framerate
of 60 fps, we need to update! So we call the update
function and subtract the frameRate
from dt
. If dt
is greater than the frameRate
, we will need to update the simulation multiple times before rendering, to keep it in sync.
On a computer with frame drops from time-to-time, the update will keep in sync so that the simulation will run at the same speed on a high-end computer, and on a laggy computer. However, if the game experiences a large delta-time, such as from switching tabs in the browser, it will only update the max allowed amount of times - in our case - three times.
Quick Book Plug
There are lots of similar implementations for this if you search the internet, but the method I went with is outlined in the book HTML5 Games: Novice to Ninja By Earle Castledine. I highly recommend it.
Visual Variety¶
Let's add some pizazz to our breakout game by taking a cue from the Atari game Arkanoid and give each row of bricks a different color! Here's the array of six colors that I chose.
Let's modify our number of bricksDown
to reflect the number of colors, so if you want to add or remove colors, the number of rows will reflect that accordingly.
Adjust the render
function to use the colors from our array instead of the hardcoded colors we had in the last chapter. Now our game has some visual variety!
Keeping Score¶
Most arcade games display the score and lives at the top of the screen, but we aren't actually keeping score right now. Let's fix that by adding a new global variable score
, initializing it in the setup
function, and updating the score each time a brick is broken. I arbitrarily chose to award 100 points for each brick that's broken.
Rendering Score and Lives¶
We've dutifly kept track of the score
and player.lives
so far. Now comes the time to render them to the screen so the player can see what's going on! I added this code to the bottom of the render
function to draw the player's lives in the top-left of the screen, and the score to the top-right of the screen.
Game Over¶
Now it's finally time to implement the win and lose screens if the game is over! First, let's render it to the screen. We use the ternary operator to determine whether to display "You Win!" or "Game Over!" Then, we draw the text to the center of the screen and add a message "Click to Restart" fifty pixels below it. We could draw a background on the screen, but I like drawing the text on top of the paddle and ball sitting in their last positions.
Since we're telling players to click to restart, we'd better figure that out... In the update function, we can check whether the game is over, and if it is, we restart the game when the mouse is clicked. Regardless, if the game is over, we should return from the update
function without updating any of the game physics.
Since the user is going to be clicking to restart the game, we need to set mouse.click = false
in startup
to prevent the ball from immidiately launching.
Creating Sound Effects¶
I used the wonderful JFXR website to create these sound effects! They are all public domain, so you can use them for whatever you want. JFXR is a really cool program that was inspired by sfxr and bfxr. It has several presets, a mutator function, and the ability to tweak as many variables as your heart desires! If you don't know what you're doing, but want to get a taste for what it can do, just hit the "random" button a bunch, and you might hear something you like.
Playing Sound Effects¶
Delving into the world of the Web-Audio API is something that I want to do justice to in a separate tutorial. It is the best way to implement music and sound effects for web games, but it is certainly not the simplest. I'm going to outline a method that I've used before which is a quick way to get sound effects into your game.
Let's start by creating our sfx
object. We need to add the paths to your sound effects. These are the sound effects I provided in the breakout repo. Then, I created a simple play
function that accepts a path, loads an Audio Element and plays it. We also implemented a preload
function that fetches the audio immidiately after the script loads so that it will be cached and ready to go in the user's browser. Otherwise, the player may experience some lag while the sound effect loads for the first time.
Make sure to call the preload function before the setup
function at the bottom of the file.
Couldn't the play
function be optimized?
I am intentionally loading the Audio element each time that play
is called, because it will create a new audio channel so that two of the same sound effects can be played and heard at the same time. If we had one Audio element for the wall_bounce
, we would have to stop the old sfx from playing to start a new one if the ball hit a corner. Luckily, browsers cache audio, so the actual fetching of the audio from the url becomes a performance non-issue after the first load.
Now, we need to edit the update
function to play all of these awesome sound effects. Note the many uses of // ...
to explain that I am omitting code. I'm just trying to show where I started the sound effects.
We Did It!
It's a wonderful thing to create a game from scratch, isn't it? Hopefully you learned something that you can bring to your next game project, or maybe you could even use this as the base for your own creativity-fueled browser-based breakout game! In any case, feel free to check out my other work here on Scipress, codepen, and codeberg