*You can find the links to the previous parts at the bottom of this tutorial.
Music and sound effects can add a new dimension to your game. Imagine playing your favorite video game — now imagine it without sound. What impact would that have on your experience? Would it still be your favorite game? In this part, we’ll be adding the second last piece to our game, which is music and sound effects.
In Godot, you have three primary options for playing audio:
AudioStreamPlayer: This is a general audio player suitable for playing music or any non-positional audio. Use this when you don’t need the audio to have a position in the world. For example, if you have background music or dialog music that should be played irrespective of the position of the characters or objects in the game, use AudioStreamPlayer.
AudioStreamPlayer2D: This is designed for 2D games where you need positional audio. It stimulates the position of the sound in a 2D space and will make sounds quieter the further away they are from the listener (camera, usually). Use this when you are making a 2D game and you want the audio to have a position in your 2D world (e.g., a sound effect that occurs at a certain location on the screen).
AudioStreamPlayer3D: Similar to AudioStreamPlayer2D, but for 3D games. It takes into account the position of the sound in three-dimensional space. Use this in 3D games when you want the sound to emanate from a specific location in the 3D world.
Throughout this tutorial, we will use our AudioStreamPlayer node for constant stable sounds such as our Background music or Dialog music. We will use our AudioStreamPlayer2D node however for our sound effects — because we want some panning in the sound and we also want the sound loudness to depend on the location of the sound.
WHAT YOU WILL LEARN IN THIS PART:
- How to work with the AudioStreamPlayer & AudioStreamPlayer2D node.
In your project, you’ll see in your Assets directory that I already added a folder with Music in it for us to use. You are welcome to go and source your audio, but for our game, I’m going to be using those assets.
STEP 1: MUSIC
Background Music
In your Player scene, add a new AudioStreamPlayer node. This will serve as the audio container for your background music when the game plays.
Rename it to “BackgroundMusic”.
Since we’ll be adding numerous AudioStreamPlayer nodes, let’s organize them underneath a Node2D node renamed “Music”.
Our background music will be the audio file “arguement-loop-27901.mp3”. Godot supports both .wav and .mp3 audio extensions. We can assign this audio file to our BackgroundMusic node in the inspector Panel underneath the Stream property. You can drag this audio file into the property, or you can add it by clicking on it and assigning it via the Quick Load method.
This audio file is already imported with its looping value enabled, so we don’t need to worry about it playing and then stopping once it reaches the end! If you need to use an audio file that isn’t preconfigured for looping, you can re-import it in your Import dock with looping enabled.
To play this BackgroundMusic node, we can reference it in our code wherever we want it to play via the play() method. In our case, we want it to play when the Player enters the scene — which is when our level starts.
### Player.gd
#older code
func _ready():
#older code
#play background music
$Music/BackgroundMusic.play()
Now if you run your scene and you start a level, the background music should play.
Pause Menu Music
We also want our pause menu to play its track when the player presses the ui_pause input. When the pause button is pressed, we need to stop our background music and play our pause music.
Add a new AudioStreamPlayer node and rename it to “PauseMenuMusic”.
Assign the “8bit-music-for-game-68698.mp3” track to its Stream property.
In our input() function, underneath our ui_pause conditional, we need to play this audio track.
### Player.gd
#older code
#singular input captures
func _input(event):
#pause game
if event.is_action_pressed("ui_pause"):
#show menu
$PauseMenu.visible = true
#pause scene
get_tree().paused = true
#show cursor
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
#play pause menu music
$Music/BackgroundMusic.stop()
$Music/PauseMenuMusic.play()
We also need to stop the pause menu music from playing when the player resumes the game because we need the background music to play again.
### Player.gd
#older code
#resume game
func _on_button_resume_pressed():
#unpause scene
get_tree().paused = false
#hide menu
$PauseMenu.visible = false
#stop pause menu music
$Music/BackgroundMusic.play()
$Music/PauseMenuMusic.stop()
Now if you run your scene and you pause your game, your pause menu music should play. If you resume your game, your background music should play.
Game Over Music
When our player dies, we need to stop our background music and play our death music. In your Player scene, add a new AudioStreamPlayer node and rename it to “GameOverMusic”.
Assign the “kl-peach-game-over-iii-142453.mp3” track to its Stream property.
Let’s play it when the player dies. We also need to stop the background music from playing.
### Player.gd
#older code
#reset our animation variables
func _on_animated_sprite_2d_animation_finished():
if attack_time_left <= 0:
Global.is_attacking = false
set_physics_process(true)
is_hurt = false
if $AnimatedSprite2D.animation == "death":
# pause game
get_tree().paused = true
# show menu
$GameOver/Menu.visible = true
# make modular value visible
$AnimationPlayer.play("ui_visibility")
#hide the player's UI
$UI.visible = false
#get final values
final_score_time_and_rating()
# show player values
$GameOver/Menu/Container/TimeCompleted/Value.text = str(Global.final_time)
$GameOver/Menu/Container/Score/Value.text = str(Global.final_score)
$GameOver/Menu/Container/Ranking/Value.text = str(Global.final_rating)
#show cursor
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
#play game over music
$Music/BackgroundMusic.stop()
$Music/GameOverMusic.play()
We also need to continue the background music when our player presses the restart button on our game over screen.
### Player.gd
#older code
#restarts game
func _on_restart_button_pressed():
#hide menu
$GameOver/Menu.visible = false
# Restart current scene
get_tree().reload_current_scene()
if Global.get_current_level_number() > 1:
#unpause scene
get_tree().paused = false
#start background music
$Music/BackgroundMusic.play()
Now if you run your scene, your music should play when our game is over.
*If your music is too loud/soft for your taste, you can edit its Volume property to be lower or higher in the Inspector panel. You can test this by enabling and disabling the Playing property.
Main Menu Music
We also need music to play in our MainMenu scene. Create a new Node2D node called “Music” and add an AudioStreamPlayer node to it which you should rename to “BackgroundMusic”.
Assign the “8bit-music-for-game-68698.mp3” track to its Stream property.
Let’s play it when the MainMenu node enters the scene.
### MainMenu.gd
extends CanvasLayer
func _ready():
#pause it by default
get_tree().paused = true
#show cursor
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
#play background music
$Music/BackgroundMusic.play()
Now if you run your scene, your background music should play when your MainMenu is open.
Level Up Music
We also need music to play in our LevelDoor scene when the player enters its collision body. Create a new Node2D node called “Music” and add an AudioStreamPlayer node to it which you should rename to “LevelUpMusic”.
Assign the “cool pigeon.wav” track to its Stream property. If you don’t like this audio, you can also use the “kl-peach-game-over-iii-142453.mp3” or “computer(turn on).wav” audio.
Let’s play it when the LevelDoor node collides with our player body.
### LevelDoor.gd
#older code
func _on_body_entered(body):
if body.name == "Player":
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
# pause game
get_tree().paused = true
# show menu
$UI/Menu.visible = true
# make modular value visible
$AnimationPlayer.play("ui_visibility")
#hide the player's UI
body.get_node("UI").visible = false
#get final values
body.final_score_time_and_rating()
# show player values
$UI/Menu/Container/TimeCompleted/Value.text = str(Global.final_time)
$UI/Menu/Container/Score/Value.text = str(Global.final_score)
$UI/Menu/Container/Ranking/Value.text = str(Global.final_rating)
#play music
$Music/LevelUpMusic.play()
Now if you run your scene, your level-up music should play when your player reaches the door.
STEP 2: SFX
Jump SFX
When our player jumps, we want to play a jumping sound effect. In your Player scene, add a new AudioStreamPlayer2D node and rename it to “JumpSFX”.
Assign the “jump.wav” track to its Stream property.
In our input() function, underneath our ui_jump conditional, we need to play this audio track.
### Player.gd
#older code
#singular input captures
func _input(event):
#older code
#on jump
if event.is_action_pressed("ui_jump") and is_on_floor():
velocity.y = jump_height
$AnimatedSprite2D.play("jump")
#sfx
$Music/JumpSFX.play()
You can also play this if your player is climbing ladders.
### Player.gd
#older code
#singular input captures
func _input(event):
#older code
#on climbing ladders
if Global.is_climbing == true:
if Input.is_action_pressed("ui_up"):
$AnimatedSprite2D.play("climb")
gravity = 100
velocity.y = -160
Global.is_jumping = true
$Music/JumpSFX.play()
Now if you run your scene, your sfx should play when your player jumps or climbs.
Damage SFX
When our player gets damaged, we want to play a damaged sound effect. In your Player scene, add a new AudioStreamPlayer2D node and rename it to “DamageSFX”.
Assign the “embarrassed.wav” track to its Stream property.
In our take_damage() function, we need to play this audio track.
### Player.gd
#older code
# takes damage & death
func take_damage():
#deduct and update lives
if lives > 0 and Global.can_hurt == true:
lives = lives - 1
update_lives.emit(lives, max_lives)
#play damage animation
$AnimatedSprite2D.play("damage")
#allows animation to play
set_physics_process(false)
is_hurt = true
#decrease score
decrease_score(10)
$Music/DamageSFX.play()
#death
if lives <= 0:
$AnimatedSprite2D.play("death")
Now if you run your scene, your sfx should play when your player gets hurt.
Pickup SFX
When our player picks up any one of our pickups, we want to play a different sound. In your Player scene, add three new AudioStreamPlayer2D nodes and rename them to “ScoreSFX”, “BoostSFX”, and “HealthSFX”.
Assign the “score.wav” track to the ScoreSFX’s Stream property.
Assign the “boost.wav” track to the BoostSFX’s Stream property.
Assign the “upgrade.wav” track to the HealthSFX’s Stream property.
In our add_pickup() function, we need to play these audio tracks.
### Player.gd
#older code
#adds pickups to our player and updates our lives/attack boosts
func add_pickup(pickup):
#increases life count if we don't have 3 lives already
if pickup == Global.Pickups.HEALTH:
if lives < max_lives:
lives += 1
update_lives.emit(lives, max_lives)
$Music/HealthSFX.play()
#temporarily allows us to destroy boxes/bombs
if pickup == Global.Pickups.ATTACK:
Global.is_attacking = true
$Music/BoostSFX.play()
#increases our player's score
if pickup == Global.Pickups.SCORE:
increase_score(1000)
$Music/ScoreSFX.play()
Now if you run your scene, your sfx should play when your player runs through the pickups.
Attack SFX
When our player presses their ui_attack input, we want an attack SFX to play. In your Player scene, add a new AudioStreamPlayer2D node and rename it to “AttackSFX”.
Assign the “attack variation.wav” track to its Stream property.
In our input() function, underneath our ui_attack conditional, we need to play this audio track.
### Player.gd
#older code
#singular input captures
func _input(event):
#pause game
if event.is_action_pressed("ui_pause"):
#show menu
$PauseMenu.visible = true
#pause scene
get_tree().paused = true
#show cursor
Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
#play pause menu music
$Music/BackgroundMusic.stop()
$Music/PauseMenuMusic.play()
#on attack
if Input.is_action_just_pressed("ui_attack"):
if Global.is_attacking == true:
$AnimatedSprite2D.play("attack")
$Music/AttackSFX.play()
Now if you run your scene, your sfx should play when your player attacks when their attack boost is active.
IncreaseScore SFX
When our increases their score, we want the game to be notified via a soundtrack. In your Player scene, add a new AudioStreamPlayer2D node and rename it to “IncreaseScoreSFX”.
Assign the “Pickup Coin.wav” track to its Stream property.
In our increase_score() function, we need to play this audio track.
### Player.gd
#older code
func increase_score(score_count):
score += score_count
update_score.emit(score)
$Music/IncreaseScoreSFX.play()
Now if you run your scene, your sfx should play when your player ups their score.
Your code should look like this.
I challenge you to go ahead and add more SFX if you’d like. Maybe play a sound if the player presses on a button, or when the Box and Bomb hits the Wall collisions. I’m going to stop here for now, since this is a good base. In the next part, we’ll be adding our particle effects to our game when our game. Now would be a good time to save your project and make a backup of your project so that you can revert to this part if any game-breaking errors occur. Go back and revise what you’ve learned before you continue with the series, and once you’re ready, I’ll see you in the next part!
Next Part to the Tutorial Series
The tutorial series has 24 chapters. I’ll be posting all of the chapters in sectional daily parts over the next couple of weeks. You can find the updated list of the tutorial links for all 24 parts of this series on my GitBook. If you don’t see a link added to a part yet, then that means that it hasn’t been posted yet. Also, if there are any future updates to the series, my GitBook would be the place where you can keep up-to-date with everything!
Support the Series & Gain Early Access!
If you like this series and would like to support me, you could donate any amount to my KoFi shop or you could purchase the offline PDF that has the entire series in one on-the-go booklet!
The booklet gives you lifelong access to the full, offline version of the “Learn Godot 4 by Making a 2D Platformer” PDF booklet. This is a 451-page document that contains all the tutorials of this series in a sequenced format, plus you get dedicated help from me if you ever get stuck or need advice. This means you don’t have to wait for me to release the next part of the tutorial series on Dev.to or Medium. You can just move on and continue the tutorial at your own pace — anytime and anywhere!
This book will be updated continuously to fix newly discovered bugs, or to fix compatibility issues with newer versions of Godot 4.