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.

Import sounds

Click the button below to download all the sound assets we need for the game.

Download “SimpleRPG Sounds and Musics” music_sounds.zip – 3 MB

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.

Background music

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:

Attack sound

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.

Skeleton death

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:

Fireball 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()

Conclusions

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.


4 Comments

Avatar

James · February 10, 2020 at 11:27 am

Wow! This is great Davide Pesce.
I learnt a lot from these tutorial. Can’t wait for the next one. Thanks for doing this.
Continue the hard work this is really helping some of us. 🙂

Thanks! 🙂

Avatar

Anastasia · March 28, 2020 at 1:58 pm

Hi there!
Great tutorial as always! Didnt have any problems this time ( ;
I just dropped in to say that i’d be very cool if you could show us how we can have a sound that plays when the NPC is talking, like a ‘voice’ kind of
Again really liked this one, you’re doing a great job

    Avatar

    Davide Pesce · March 29, 2020 at 1:27 pm

    Hi Anastasia!
    it’s very easy to add a sound effect when the NPC speaks:

    • add an AudioStreamPlayer2D node to the NPC (call it for example SoundTalk) and set it up to play the sound effect you want
    • in the NPC script, before each dialoguePopup.open() statement, add $SoundTalk.play()

    You can also create multiple AudioStreamPlayer2D nodes and use different sounds depending on what the NPC says (for example to show different emotions)

      Avatar

      Anastasia · March 29, 2020 at 4:58 pm

      Thanks so much!

Leave a Reply

Your email address will not be published. Required fields are marked *

By continuing to browse this Website, you consent to the use of cookies. More information

This Website uses:

By continuing to browse this Website without changing the cookies settings of your browser or by clicking "Accept" below, you consent to the use of cookies.

Close