Skip to content

Adding sounds

Audio is a major and important part of making a game feel good. It is often underlooked and can make a huge difference in how a game feels.

While the basic audio system is usable and works, there’s a more extensive version we are implementing. It was designed/spec’ed with the good folks at A Shell In The Pit as they have a lot of experience doing and implementing audio at our target scale.

Since audio tends to be fairly straight forward from a content perspective, all we need to do is drop some audio assets into our project. When we build, the asset pipeline will automatically generate the necessary audio.lx metadata files for us to use!

For our bee game example, we’re going to use a useful website called Freesound.org - they provide an invaluable service for getting audio to prototype with or use for your game. Make sure you pay attention to the requirements for each sound that you download!.

Instead of music, we’re going to be using ambience. We know our game takes place above the rooftops, so we’d like a sort of city atmosphere. We also have a bouncing sound - one when the bee bounces without a flower, and one when we do bounce on the flower. In our case, we aim to find some sounds that don’t require editing, and can be used roughly as is.

We’ll bring these into a folder called audio/ along with the credits file (this way when it’s time to credit properly we can make sure we didn’t forget anything):

If we run a build, we can see that the asset pipeline took care of generating the data needed to load + use the audio already:

The background sound is an easy one - we simply play it in the ready function in our game.

import "luxe: audio" for Audio

Often we’ll want a sound to loop continously - the API makes this easy with :::js Audio.loop(...). Like with other assets, we should see them in code completion when we go reach for one using the Asset.audio api like this:

That’s it really! The basic usage of audio is really simple. There’s also :::js Audio.play for one shot sounds.

var bg_sound = Asset.audio("audio/city")
Audio.loop(bg_sound)
Scene.create(world, Asset.scene("scene/level"))

When the bee collides with something, we’ll play our bounce sound. We already have a function that does stuff when we collide with something, so for now we just play our sound in here.

handle_collision() {
Arcade.add_collision_callback(player) {|entity_a, entity_b, state, normal, overlap_dist|
if(state != CollisionEvent.begin) return
Audio.play(Asset.audio("audio/bounce.bee"))
...
}
} //handle_collision

If you play the game and bounce around, you should hear the sound playing. This works well, but sometimes we land on a surface and it plays the sound way too many times in rapid succession! This is a common problem with audio, so we need to not play it too frequently. We’ll do a very simple timer to prevent it spamming the sound.

To do that we’ll add a new variable to our class:

var last_bounce: Num = 0

And change our audio play line to check how much time has passed, and don’t play it if it was already played recently (we use 0.25 seconds):

if(World.time(world) > last_bounce + 0.25) {
last_bounce = World.time(world)
Audio.play(Asset.audio("audio/bounce.bee"))
}

We also play an additional sound when we bounce on the flower. The sounds combining is intentional, as it should create a nice mix of sound in this case.

We already have code handling what happens when you bounce on a flower, so it’s as easy as adding one line:

if(Tags.has_tag(entity_b, "flower")) {
Anim.play(entity_b, "anim/bounce")
Audio.play(Asset.audio("audio/bounce.flower"))
}

It’s common for games to have a volume slider for Sounds, Music and Voices as separate sliders. The term for that is “audio bus”, we play sounds on the SFX bus and so on. We always have global volume control as well, as everything will eventually come out the main bus (global bus). So how do we do that in luxe?

Defining a bus is often done as an asset so it can be referenced easily in the Audio API. We’ll do that inside audio/bus/ by creating a sounds.bus.lx and ambience.bus.lx.

Inside both files, we have a very simple volume value!

bus = {
volume = 0.75
}

Here’s the variant of Audio.play (and Audio.loop) that accepts a bus:

Audio.play(source: AudioAsset, as3D: Bool, bus: AudioBus, volume: Num)

Like before, we get the completion since our bus is an asset, and we grab it from Audio.bus:

if(World.time(world) > last_bounce + 0.25) {
last_bounce = World.time(world)
Audio.play(Asset.audio("audio/bounce.bee"), false, Asset.bus("audio/bus/sounds"), 1)
}

And the ambience:

Audio.loop(bg_sound, false, Asset.bus("audio/bus/ambience"), 1)

So we have a few layers of sound volumes to control:

There’s two places typically, on play/loop you can specify a volume to start with or you can use the audio api to change the volume after playing it:

//change flower bounce audio manually
var sound = Audio.play(...)
Audio.volume(sound, 0.5)

To control the bus volume for sounds or ambience (as you might do in a UI!) we use the Bus API.

import "luxe: audio" for Audio, Bus

And then we can call the set (or get) the volume:

Bus.set_volume(Asset.bus("audio/bus/ambience"), 0.75)
Bus.set_volume(Asset.bus("audio/bus/sounds"), 0.75)

If you’d like to experiment, add this to your tick method in your game. What this does is calculate a 0...1 value (ratio) of where your mouse is horizontally on the window. The further left the mouse is the quieter the volume of the ambience, and the further right the louder it will be.

var dx = Input.mouse_x() / width
Bus.set_volume(Asset.bus("audio/bus/ambience"), dx)

When you do this, you’ll notice the sounds are unaffected by the volume change.

Experiment

Experiment with the audio api using the sound instances returned by playing a sound