Let’s wrap up our 3D runner game by adding some background music and contextual sounds!
This article is also available on Medium.
Last time, we worked on the game over logic. We saw what to do when the player hits and obstacle and reaches zero healthpoints, and we even implemented a little replay system to let them directly start a new game session.
Today, we’re going to dive into something completely different but crucial to immersion: sounds and music!
This tutorial is available either in video format or in text format — see below 🙂
Sounds & music in video games
When you’re faced with a multimedia content like a movie or a video game, sounds are actually about as important as images. Over the years, video games have improved in their rendition of sounds: from the 8-bit musics we had in the 70s to the grandiose soundtrack we now have on big titles, we’ve certainly come a long way. As technology advanced, we’ve been able to include sounds that were more and more realistic, and even some spatialisation effects (in particular thanks to binaural recordings).
Here, we’ll see how to add two types of sounds to our game: the background music and some contextual sounds.
Background music participates in giving your game a unique identity: it’s about finding the right ambiance to hook the players and have them feel a certain way. It creates an atmosphere, and it should convey meaning, just as much as the 3D models or the game mechanics.
Contextual sounds are usually short noises or sound effects that happen at very specific point in the game to give feedback to the player on a particular action. In strategy games, it can be the fact that units utter some response when you select them; or just in any fighting game, think of how important the punch sound is to actually feeling like you hit something!
Description of our sound files
In our case, Hyperspeed is a simple video game about spaceships with a retro arcade vibe. So, for the music, I decided to go for something with basic repetitive patterns with synths and bass-like sounds. If you want to use in your own version of Hyperspeed, you can get this music for free over here – or, of course, you can also create your own music!
For the contextual sounds, we are going to have two types of sound effects: the “hit” sound when you hit an obstacle, and the “collect” sound when you grab a bonus. Except that, rather than having just one “collect” sound, we are actually going to have different ones depending on the price of the bonus.
Just like with colour, we’re going to say that low-price blue bonuses produce a low-pitched note when you grab them whereas high-price red bonuses produce a high-pitched note.
Once again, the MP3 file is available in the description of the video. The trick is that, as you’ll see, there is only one MP3 file for all the sound effects; and that’s because, as we’ll see very soon, we’ll take small chunks of this file and then only play part of the file when we collect a bonus.
This is a nice way of reducing the amount of resources needed, and also the initial load time: rather than opening and loading lots of little files, you open and load just one somewhat larger file (although it’s honestly just ten seconds long) all at once 🙂
Installing Howler.js and preparing our audio manager
Ok so – let’s assume that you have your audio resources now. You either downloaded them from my links or created your own. So you now have one file for the background music (for me, it’s named
music.mp3) and another file with all the sound effects “glued” together (that’s
I put both my files in an
assets/ folder at the root of my project:
Howler is a JS audio library for webpages that makes it really easy to play one or more sounds at the same time with autoplay, looping, streaming, fade ins/outs and even spatial audio with pans! We won’t go as far as doing spatialised audio here, but be sure to check out their docs if you want to know more 🙂
Just like with the Three.JS lib we installed in the first episode of this series, you can get a minified version of Howler from a CDN, copy it to your project
vendors/ folder, and then reference it in the
index.html head block:
audio-manager.js, and import it in the
This file won’t be very long, but it’s always good practice to separate your codebase into logical chunks, even when they’re short.
In this script, we’re going to create just a single function named
Then, in our
main.js file, in the
window.onload() routine, we’ll just add a little line at the very top that calls this method:
Playing sounds and music!
Adding the background music
Ok – now, let’s go back to our audio manager and make some noise! 🙂
Something really nice with Howler is that it allows you to stack several audio sources on top of each other, just by creating a new
Howl object. It makes it really straight-forward to first add some music, and then work on your sound effects, and just build the thing up gradually, which is really cool!
So, first, let’s focus on our music.
What I want is to load my
music.mp3 file from the
assets/ folder, have the music start automatically when the webpage is loaded and then make it loop indefinitely. All of this can be done by passing some options to the
src key, I pass the path to my
music.mp3 file and the other parameters enable me to enable both autoplay and looping. Be careful, though: if the path that you give is relative, it will take the
index.html directory as root, so you have to put the proper relative path for it to point to your assets folder 😉
Finally, I just have to use the
play() method of this object to actually have it produce sound:
If you save this and reload your page, you’ll instantly hear the music play in the background. That’s pretty cool!
A little improvement we can make is to have the music fade in slightly, rather than starting directly at max volume like this. To have this little volume slide, all we need to do is get the reference ID from our call to
play() (this is something that automatically returned by the
Howl object when we call its
play() method), and then use the
fade function on my music source:
This function expects the initial volume, the final volume, the transition time and the play ID. So, here, I’m saying that it will go from complete silence to the full volume over the course of two thousands milliseconds, or 2 seconds.
And so now if you reload… you can hear that there is a little ramp up of the volume at the very beginning that makes it smoother to the ear 🙂
Defining our sound sprites
Ok, now that we have the background music, let’s take care of the sound effects!
As we saw before, for the sounds, we’ll need to cut down our
sounds.mp3 file into several chunks. For example, in my sound file, I have nine sounds: I have eight synth notes for bonus collection, then an additional “crash” sound for when we collide with an obstacle.
Each sound lasts one second, so we’ll want to chop down the track into nine pieces of one second each.
To do this, we can use a really sweet feature of Howler that allows us to assign a specific chop of sound to a user-defined key. Here, let’s define an object with all of our sounds simply called
Then, let’s add the various subclips inside it.
First, we’ll do a loop from 0 to 7 (included) to get the bonus collection sounds, and assign them to the keys
bonus-2 and so on, up til
bonus-7. To select the chunk of the track that we want, we simply use an array with the start timestamp and the duration of the subclip, both in milliseconds.
For example, the first sound starts at timestamp 0 and lasts a thousand milliseconds, then the second sound starts at timestamp a thousand and also lasts a thousand milliseconds, et caetera:
So, basically, I can write this as
[i*1000, 1000] in my loop:
Finally, we can take the crash sound on its own, at the end of the track, and assign it to the “crash” key, with the array
Now, it’s time to create a second audio source, by making another
Howl object and once again setting its path to our resource file. I’ll also use the volume parameter to dim these effects a little (by using 0.5), and I’ll pass in my chunks in the “sprite” key:
The term “sprite” here is similar to what you see in game dev with spritesheets. These images are large ensemble of smaller pictures that are somewhat related and that are then split into individual pieces. This is used a lot for 2D animation, for example, because it allows you to group all the animation frames of your character on a single image and then “iterate” through these frames one by one, but still gathering them in a single data block.
We’re doing the same thing here: we have a single block of data, our
sounds.mp3 file, and we chop it down into multiple individual pieces that can each be used in isolation, or as part of a group.
With all that said, this time, we are not going to play our audio source directly. Rather, we would like to have our game play the proper subclip whenever we collect a bonus or hit an obstacle.
Using our sound effects
To do this, we will expose our
soundAudio source as a global variable at the top of this script:
And then, going back to our
game.js file, in the
_checkCollisions() function, we will try and access this variable. If it exists, then we will play the matching sound; else, we will just ignore the sound system and stick with the current logic.
This way, we don’t enforce the existence of the sound system and better decouple the two systems, but we can take advantage of it if it’s there.
For the obstacles, it’s quite straight-forward: if we do have a
soundAudio source, we play its “crash” sound sprite (by specifying the name of the chunk to play):
For bonus collection, it’s a little bit more complex. Remember that we cut down the beginning of our file into 8 pieces with the
bonus-7 IDs, but that our bonus prices range from 5 to 20. So, basically, we need to remap the 5 to 20 range to the 0 to 7 (included) range, and this will give us the key of the sound sprite to play.
This is done via a basic math formula, and after we’ve computed the index we can get the associated bonus collection sound and play it when we grab the object!
If you save your files and reload the game, you will see that now, there is the music in the background, just as before, and, whenever you hit an object, a little contextual sound will play. If it’s an obstacle, you will get the “crash” sound; if it’s a bonus, you’ll get a synth note which pitch varies depending on the price of the bonus: blue and purple bonuses have rather low-pitched sounds whereas pink and red ones have higher pitched ones!
Quick tip: to hear the final result, be sure to check out the video version of this tutorial that is available at the top of this article or on my Youtube channel! 🙂
Alright! Today, we’ve seen how to use the Howler.Js library to load, prepare and play sound files, either as a continuous looping background music or as short contextual sounds. We also saw how to split a single file into multiple subclips using sound sprites and we made sure to make the coupling between our game mechanics and the audio manager as loose as possible to avoid tight dependencies!
And actually, at this point in the tutorial, we have all the core features of the game and we can say that we have finished coding up Hyperspeed! 🙂
There are, however, various improvements that we can still discuss, like adding some camera shakes when we hit an object, showing a little popup when we collect a bonus or having fog in the distance. We could also have a debug console to have more info on our game as developers, or add transitions to our UI panels.
So, to tackle those various questions and start off your own game customisation, there will be a few additional bonus episodes in this series. Next time, for example, I’ll talk about the camera shake, the bonus popup and the fog.
Feel free to watch those if you like, and if you’d rather end your journey there, I’m really glad you followed me through this adventure, and I hope you had fun learning how to program this 3D web runner game 😉