Immersive game sounds and background music play a huge role in determining the success or failure of any game. In this tutorial, we will learn how to add sounds and music to our game using the AudioStream family of nodes.
Godot’s audio system
To reproduce sounds, Godot’s audio system uses elements called audio buses. An audio bus is a “place” through which audio is channeled. A bus can modify audio data by applying one or more effect processors and then route it to another bus.
Audio buses can be found in the bottom panel of the Godot editor:
By default, there is only one audio bus, the master bus (it’s always the leftmost one). This is the bus that outputs the audio to your speakers.
The other audio buses can be flexibly routed to other buses. Routing can only happens from buses on the right to buses further to the left, to avoids infinite loops.
When using a node for audio playback, you must choose which bus to send the audio to (by default it’s the master bus). For our simple game, we will only use the master bus.
Godot’s audio interface mainly uses the decibel scale, the same used by audio professionals. The decibel (dB) scale is a relative (logarithmic) scale. For every 6 dB, sound amplitude doubles or halves.
Click the button below to download all the sound assets we need for the game.
Contains music ©2019 Joshua McLean (mrjoshuamclean.com)
Licensed under Creative Commons Attribution 4.0 International
In the FileSystem panel, create a new folder called Sounds and import into it all the downloaded audio files.
In the main game scene, add an AudioStreamPlayer node to Player, and rename it Music.
The AudioStream family of nodes is used to send sounds to audio buses. AudioStreamPlayer is the standard, non-positional stream player. The sound stream can come from many places, but is most commonly loaded from the filesystem. Godot supports WAV (.wav) and Ogg Vorbis (.ogg) audio files.
Drag the mountain-trials.ogg file to the Stream property in the Inspector, then turn on Autoplay to play the music when the game starts. You can adjust the volume of the music to your taste using the Volume Db property. For background music, I set it to -6 Db. Finally, set Pause → Mode to Process, so that the music doesn’t stop playing when the game is paused.
The music will be looped, because by default the Ogg Vorbis files are imported with the Loop property activated.
Add another AudioStreamPlayer to Player and rename it to MusicGameOver and drag the night-chip.ogg file to the Stream property in the Inspector. We will use this node to play the game over music when the player dies.
Now open the Player.gd script and edit the hit() function to stop the background music and play the game over music:
func hit(damage): health -= damage emit_signal("player_stats_changed", self) if health <= 0: set_process(false) $AnimationPlayer.play("Game Over") $Music.stop() $MusicGameOver.play() else: $AnimationPlayer.play("Hit")
Stopping and playing a sound is very simple: just call the stop() and play() functions of AudioStreamPlayer.
This is what will happen when the player dies:
Let’s add the sound effects starting from the player’s attack. Add an AudioStreamPlayer2D node to Player and rename it SoundAttack.
AudioStreamPlayer2D is a variant of AudioStreamPlayer that emits sound in a 2D positional environment. When the node is close to the left of the screen, the panning will go left. When close to the right side, it will go right.
Drag the attack.wav file to the Stream property in the Inspector and set Volume Db to 6. Unlike .ogg files, by default .wav files are not looped.
Open the Player.gd script and go to the _input() function. In the code that handles the attack input action, we must add the code to play the sound, immediately after the code that plays the attack animation:
if event.is_action_pressed("attack"): # Check if player can attack var now = OS.get_ticks_msec() if now >= next_attack_time: # What's the target? var target = $RayCast2D.get_collider() if target != null: if target.name.find("Skeleton") >= 0: # Skeleton hit! target.hit(attack_damage) if target.is_in_group("NPCs"): # Talk to NPC target.talk() return # Play attack animation attack_playing = true var animation = get_animation_direction(last_direction) + "_attack" $Sprite.play(animation) # Play attack sound $SoundAttack.play() # Add cooldown time to current time next_attack_time = now + attack_cooldown_time
Now open the Skeleton.tscn scene and add an AudioStreamPlayer2D to the Skeleton node. Rename it SoundAttack. Drag the attack.wav file to the Stream property in the Inspector and set Volume Db to 6.
Open the Skeleton.gd script and insert in the _on_AnimatedSprite_frame_changed() function the code to play the attack sound:
func _on_AnimatedSprite_frame_changed(): if $AnimatedSprite.animation.ends_with("_attack") and $AnimatedSprite.frame == 1: var target = $RayCast2D.get_collider() if target != null and target.name == "Player" and player.health > 0: player.hit(attack_damage) # Play attack sound $SoundAttack.play()
Since the attack animation of the skeleton is slower, the sound is reproduced at frame 1 of the animation for better audio/video sync.
Add another AudioStreamPlayer2D to the Skeleton node and rename it SoundDeath. Drag the death.wav file to the Stream property and set Volume Db to 6.
In the Skeleton.gd script, edit the hit() function to play the death sound when the skeleton die:
func hit(damage): health -= damage if health > 0: $AnimationPlayer.play("Hit") else: $Timer.stop() direction = Vector2.ZERO set_process(false) other_animation_playing = true $AnimatedSprite.play("death") emit_signal("death") # Play death sound $SoundDeath.play() # 80% probability to drop a potion on death if rng.randf() <= 0.8: var potion = potion_scene.instance() potion.type = rng.randi() % 2 get_tree().root.get_node("Root").add_child(potion) potion.position = position # Add XP to player player.add_xp(25)
This is the result after adding the attack and death sounds:
Go back to the main scene, add an AudioStreamPlayer2D node to Player and rename it SoundFireball. Drag the fireball.wav file to the Stream property and set Volume Db to 6.
Open the Player.gd script and go to the _input() function. In the code that handles the fireball input action, add the code to play the fireball sound, immediately after the code that plays the fireball animation:
elif event.is_action_pressed("fireball"): var now = OS.get_ticks_msec() if mana >= 25 and now >= next_fireball_time: # Update mana mana = mana - 25 emit_signal("player_stats_changed", self) # Play fireball animation attack_playing = true var animation = get_animation_direction(last_direction) + "_fireball" $Sprite.play(animation) # Play fireball sound $SoundFireball.play() # Add cooldown time to current time next_fireball_time = now + fireball_cooldown_time
Now, open the Fireball.tscn scene and add an AudioStreamPlayer2D to the Fireball node. Rename it SoundExplosion. Drag the explosion.wav file to the Stream property and set Volume Db to 6.
Go the Fireball.gd script. At the end of the _on_Fireball_body_entered() function add this code:
# Play explosion sound $SoundExplosion.play()
Picking up objects and using potions
Return to the main scene and add another AudioStreamPlayer2D to Player. Rename it SoundObject. Drag the object.wav file to the Stream property and set Volume Db to 6.
Now open the Player.gd script and in the _input() function edit the code that handles the potion drinking like this:
elif event.is_action_pressed("drink_health"): if health_potions > 0: health_potions = health_potions - 1 health = min(health + 50, health_max) emit_signal("player_stats_changed", self) # Play sound $SoundObject.play() elif event.is_action_pressed("drink_mana"): if mana_potions > 0: mana_potions = mana_potions - 1 mana = min(mana + 50, mana_max) emit_signal("player_stats_changed", self) # Play sound $SoundObject.play()
Then, add at the end of the add_potion() function the code to play the sound when a potion is added to the inventory:
# Play sound $SoundObject.play()
We want to play the same sound when we pick up the necklace needed to complete Fiona’s quest. Let’s see a different way to handle the reproductions of this sound.
Open the Necklace.tscn scene. Add an AudioStreamPlayer2D to the Necklace node and rename it SoundObject. Drag object.wav to the Stream property and set Volume Db to 6.
Now, go to the Necklace.gd script.
The most obvious thing we can do is to add $SoundObject.play() to the _on_Necklace_body_entered() function to play the sound. But if you try to do this, the sound will not play!
This happens because at the end of this function, the Necklace node is deleted from the scene tree. As a result, the SoundObject node is also deleted and doesn’t play the sound. So, we’ll have to connect to the finished() signal of the SoundObject node to delete Necklace only after the sound has finished playing.
Connect the finished() signal of SoundObject to Necklace, and move the queue_delete() call to the _on_SoundObject_finished() function:
func _on_SoundObject_finished(): get_tree().queue_delete(self)
Finally, edit the _on_Necklace_body_entered() function to play the sound:
func _on_Necklace_body_entered(body): if body.name == "Player": $SoundObject.play() hide() fiona.necklace_found = true
Now that Necklace is not deleted immediately, it would remain visible until the sound ends. To solve this problem, we make it invisible by calling the hide() function.
Level up sound
Now we just have to add one last sound: the sound that notify us of the level advancement of the player.
Go back to the main scene and add an AudioStreamPlayer to the LevelPopup node. Rename it SoundLevelUp. Drag level_up.wav to the Stream property and set Volume Db to 6.
Open the LevelPopup.gd script and at the end of the _on_Player_player_level_up() function add this code:
# Play level up sound $SoundLevelUp.play()
In this simple tutorial we learned the basics of Godot’s audio system and how to add music and sounds to our game using the nodes of the AudioStream family.
You can try the game with sounds and music included by clicking here:
In the next tutorial we’ll add a menu to restart and quit the game.