DEV Community

Cover image for Let’s Learn Godot 4 by Making an RPG — Part 22: Music & Sound Effects🤠
christine
christine

Posted on • Edited on

Let’s Learn Godot 4 by Making an RPG — Part 22: Music & Sound Effects🤠

In this part we are going to add that final touch of life to our game by adding some music and SFX (sound effects) to our game. We want there to be background music when we are in our pause, death, and main menu screens, as well as in our Main scene. We’ll also add shooting and damage sound effects to our Player and Enemy, as well as dialog music and pickup effects.


WHAT YOU WILL LEARN IN THIS PART:
· How to work with the AudioStreamPlayer node.
· How to work with the AudioStreamPlayer2D node.
· How to play, set, and stop audio streams within your code.
· How to loop audio files in the Import Dock.


In Godot, you have three primary options for playing audio:

  1. 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 play irrespective of the position of the characters or objects in the game, use AudioStreamPlayer.

  2. 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).

  3. 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.

Godot RPG

Godot RPG

MAIN MENU MUSIC

Open up your MainScene scene, and add a new node called AudioStreamPlayer. Rename this node to “BackgroundMusic”.

Godot RPG

Godot RPG

In the Inspector panel, we can assign the audio file that should play for this audio player node. Click on next to your Stream property in your Inspector panel and select “Quick Load”. This will set the AudioStream object to be played.

Godot RPG

For our background music, we want the audio file “We Ride at Dawn.wav” to play. You can find all the music files underneath your Assets > Music directory.

Godot RPG

In the Inspector panel, you can set its playing and auto-playing values. If the playing value is true, the audio is playing or is queued to be played (see play). If autoplay is true, the audio plays when the scene is loaded. I recommend you read the documentation to see what the rest of the properties do, such as bus or mix target.

We want this music to play as soon as the game loads, so we need to enable “autoplay”. If you want to listen to the music, you can enable “playing”, but make sure you disable it afterward!

Godot RPG

If you now run your scene, your music should play by default but only when you are in your Main Menu scene.

PAUSE MENU

In our Player scene, let’s add an AudioStreamPlayer node. Rename it to “PauseMenuMusic”.

Godot RPG

Let’s organize all of our music underneath a Node2D node renamed to “GameMusic”.

Godot RPG

We also want the audio to be “We Ride at Dawn.wav” when our pause menu is open. Do not enable autoplay or playing, as we will set this node to play in our code only when our pause menu is open.

Godot RPG

To play our audio, we simply need to reference our node and then call its .play() method. This method plays the audio in seconds. We will play this when our game is paused after we’ve called our ui_pause input.

    ### Player.gd

    # Audio nodes
    @onready var pause_menu_music = $GameMusic/PauseMenuMusic

    func _input(event):
        # older code
        #show pause menu
        if !pause_screen.visible:
            if event.is_action_pressed("ui_pause"):
                #play music
                pause_menu_music.play()
                #pause game 
                Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
                get_tree().paused = true
                #show pause screen popup
                pause_screen.visible = true
                #stops movement processing 
                set_physics_process(false)
                #set pauses state to be true
                paused = true
                # if the player is dead, go to back to main menu screen
                if health <= 0:
                    get_node("/root/%s" % Global.current_scene_name).queue_free()
                    Global.change_scene("res://Scenes/MainScene.tscn")
                    get_tree().paused = false
                    return
Enter fullscreen mode Exit fullscreen mode

We also need to set our PauseMenuMusic node’s process mode to “When Paused”, because we only want this to process when our game is in the paused state.

Godot RPG

We also need to stop our music from playing when we quit our scene or resume our game — otherwise, it will play over the other audio. We can do this via the stop() method.

    # ---------------- Pause Menu -------------------------------------------
    #resume game
    func _on_resume_pressed():
        #hide pause menu
        pause_screen.visible = false
        #set pauses state to be false
        get_tree().paused = false
        paused = false
        #accept movement and input
        set_process_input(true)
        set_physics_process(true)
        Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
        #stop music
        pause_menu_music.stop()
Enter fullscreen mode Exit fullscreen mode

Now if you run your scene, and you pause/unpause, your pause music should play correctly.

BACKGROUND MUSIC

We also need music to play during the game loop. We will attach this node to our Player scene because we want this sound to follow them around. Let’s add another AudioStreamPlayer node to our Player scene and call it “BackgroundMusic”.

We want this audio track to be “We Don’t Need Railroads.wav”. Also, enable autoplay because we want this audio track to play by default.

Godot RPG

Godot RPG

We need to stop this music from playing when our pause menu is open, so update your ui_pause input to stop the background music audio track.

    ### Player.gd

    # Audio nodes
    @onready var pause_menu_music = $GameMusic/PauseMenuMusic
    @onready var background_music = $GameMusic/BackgroundMusic

    func _input(event):
        # older code
        #show pause menu
        if !pause_screen.visible:
            if event.is_action_pressed("ui_pause"):
                #pause game 
                Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
                get_tree().paused = true
                #show pause screen popup
                pause_screen.visible = true
                #stops movement processing 
                set_physics_process(false)
                #set pauses state to be true
                paused = true
                #play music
                background_music.stop()
                pause_menu_music.play()
                # if the player is dead, go to back to main menu screen
                if health <= 0:
                    get_node("/root/%s" % Global.current_scene_name).queue_free()
                    Global.change_scene("res://Scenes/MainScene.tscn")
                    get_tree().paused = false
                    return
Enter fullscreen mode Exit fullscreen mode

Our background music should also play after we’ve pressed our confirm and resume buttons.


    ### Player.gd

    # close popup
    func _on_confirm_pressed():
        level_popup.visible = false
        get_tree().paused = false
        Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
        background_music.play()

    # ---------------- Pause Menu -------------------------------------------
    #resume game
    func _on_resume_pressed():
        #hide pause menu
        pause_screen.visible = false
        #set pauses state to be false
        get_tree().paused = false
        paused = false
        #accept movement and input
        set_process_input(true)
        set_physics_process(true)
        Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
        #stop music
        pause_menu_music.stop()
        background_music.play()
Enter fullscreen mode Exit fullscreen mode

If our game is over, we will play another sound — so we also need to stop our background music to play in our death conditional.

    ### Player.gd

    # ------------------- Damage & Death ------------------------------
    #does damage to our player
    func hit(damage):
        health -= damage    
        health_updated.emit(health, max_health)
        if health > 0:
            #damage
            animation_player.play("damage")
            health_updated.emit(health, max_health)
        else:
            #stop background music
            background_music.stop()
            #death
            set_process(false)
            get_tree().paused = true
            paused = true
            animation_player.play("game_over")
Enter fullscreen mode Exit fullscreen mode

The same goes for our update_xp function.

    ### Player.gd

    # ----------------- Level & XP ------------------------------
    #updates player xp
    func update_xp(value):
        xp += value
        #check if player leveled up after reaching xp requirements
        if xp >= xp_requirements:
            #stop background music
            background_music.stop()
Enter fullscreen mode Exit fullscreen mode

If you run your scene, your music should play when you’re in the game.

GAME OVER MUSIC

When our player dies, we also want to play our GameOverMusic. For this, we need to add another AudioStreamPlayer node to our Player scene.

We want this audio track to be “Too Late To Save The Town.wav”. Also, change its processing mode to “When Paused”.

Godot RPG

Update your hit() function to play the audio after the background music has been stopped.

    ### Player.gd

    # Audio nodes
    @onready var pause_menu_music = $GameMusic/PauseMenuMusic
    @onready var background_music = $GameMusic/BackgroundMusic
    @onready var game_over_music = $GameMusic/GameOverMusic

    # ------------------- Damage & Death ------------------------------
    #does damage to our player
    func hit(damage):
        health -= damage    
        health_updated.emit(health, max_health)
        if health > 0:
            #damage
            animation_player.play("damage")
            health_updated.emit(health, max_health)
        else:
            #death
            set_process(false)
            get_tree().paused = true
            paused = true
            animation_player.play("game_over")
            #stop background music
            background_music.stop()
            game_over_music.play()
Enter fullscreen mode Exit fullscreen mode

If you run your scene, your music should play when your player dies.

DIALOG MUSIC

We’ll have to set our dialog music in two places, which are in our Player and DialogPopup scripts. If our player interacts with the NPC, the dialog music should play — and if the player is done interacting and our popup closes, our background music should play.

Add a new AnimationPlayer node with the audio track “Whiskey Barn Dance (loop).wav”. Set its processing mode to “When Paused”. You can also use the “Imposter Syndrome (wav)” audio for this.

Godot RPG

We’re playing the audio in the Player script because if we had to do it in our DialogPopup’s open() function the music would restart each time the popup changes! Play and stop the Dialog Music as follows:


    ### Player.gd

    # Audio nodes
    @onready var pause_menu_music = $GameMusic/PauseMenuMusic
    @onready var background_music = $GameMusic/BackgroundMusic
    @onready var game_over_music = $GameMusic/GameOverMusic
    @onready var dialog_music = $GameMusic/DialogMusic

    # ------------------- Damage & Death ------------------------------
    #does damage to our player
    func _input(event):
        # older code
        #interact with world         
        elif event.is_action_pressed("ui_interact"):
            var target = ray_cast.get_collider()
            if target != null:
                if target.is_in_group("NPC"):
                    # Talk to NPC
                    target.dialog()
                    # Music
                    background_music.stop()
                    dialog_music.play()
                    return
Enter fullscreen mode Exit fullscreen mode
    ### DialogPopup.gd

    extends CanvasLayer

    # Node refs
    @onready var animation_player = $"../../AnimationPlayer"
    @onready var player = $"../.."
    @onready var background_music = $"../../GameMusic/BackgroundMusic"
    @onready var dialog_music = $"../../GameMusic/DialogMusic"

    #closes the dialog  
    func close():
        get_tree().paused = false
        self.visible = false
        player.set_physics_process(true)
        Input.set_mouse_mode(Input.MOUSE_MODE_HIDDEN)
        # Music
        dialog_music.stop()
        background_music.play()
Enter fullscreen mode Exit fullscreen mode

If you now run your scene, your dialog music should play when your player interacts with the NPC.

LEVEL UP MUSIC

When our player level’s up, we want to play a little victory tune. We will still use the AnimationPlayer node for this because we want the audio noise to come from a central point — we don’t want it to pan between our left and right ear.

Add a new node and call it “LevelUpMusic”. We want the audio to be “Retro PowerUP StereoUP 05.wav”.

Godot RPG

Now, in our update_xp() function, we want to play our LevelUpMusic.

    ### Player.gd

    # Audio nodes
    @onready var pause_menu_music = $GameMusic/PauseMenuMusic
    @onready var background_music = $GameMusic/BackgroundMusic
    @onready var game_over_music = $GameMusic/GameOverMusic
    @onready var dialog_music = $GameMusic/DialogMusic
    @onready var level_up_music = $GameMusic/LevelUpMusic

    # ----------------- Level & XP ------------------------------
    #updates player xp
    func update_xp(value):
        xp += value
        #check if player leveled up after reaching xp requirements
        if xp >= xp_requirements:
            #stop background music
            background_music.stop()
            level_up_music.play()
Enter fullscreen mode Exit fullscreen mode

If you run your scene, your LevelUpMusic music should play when your player levels up after completing quests and shooting enemies.

PICKUPS SOUND EFFECT

If our player runs over some ammo or a drink, or even our quest items, we want to play a pickup sound effect. For this, we will use an AudioStreamPlayer2D node, since we want some audio attenuation for our sound effects. It gives the sound a bit more of a realistic feel, as it plays more like environmental background noise.

You can attach this node to something like a fire scene, which, depending on how far/close you are from the fire, the sound volume will differ. Since we’ll be adding this sound to our Player, the sound won’t attenuate much since our camera always focuses on our player.

Add a new AudioStreamPlayer2D node and call it “PickupsMusic”. Set its audio file to “stamfull.wav”.

Godot RPG

In our code, we need to play this audio when our player runs over a pickup. We can do this in our add_pickup() function.


    ### Player.gd

    # Audio nodes
    @onready var pause_menu_music = $GameMusic/PauseMenuMusic
    @onready var background_music = $GameMusic/BackgroundMusic
    @onready var game_over_music = $GameMusic/GameOverMusic
    @onready var dialog_music = $GameMusic/DialogMusic
    @onready var level_up_music = $GameMusic/LevelUpMusic
    @onready var pickups_sfx = $GameMusic/PickupsMusic

    # ---------------------- Consumables ------------------------------------------
    # Add the pickup to our GUI-based inventory
    func add_pickup(item):
        if item == Global.Pickups.AMMO: 
            ammo_pickup = ammo_pickup + 3 # + 3 bullets
            ammo_pickups_updated.emit(ammo_pickup)
            print("ammo val:" + str(ammo_pickup))
        if item == Global.Pickups.HEALTH:
            health_pickup = health_pickup + 1 # + 1 health drink
            health_pickups_updated.emit(health_pickup)
            print("health val:" + str(health_pickup))
        if item == Global.Pickups.STAMINA:
            stamina_pickup = stamina_pickup + 1 # + 1 stamina drink
            stamina_pickups_updated.emit(stamina_pickup)
            print("stamina val:" + str(stamina_pickup))
        # SFX
        pickups_sfx.play()
        update_xp(5)
Enter fullscreen mode Exit fullscreen mode

Now if you run over your pickups items, the audio should play!

CONSUMING SOUND EFFECT

We want a sound effect to play each time our player consumes a health or stamina drink by pressing “1” or “2”.

Add another AudioStreamPlayer2D node and call it “ConsumableMusic”. Set its audio file to “stam1.wav”.

Godot RPG

In our input code, let’s update our ui_consume_health and ui_consume_stamina inputs to play our consumable sound effects. You can also make both of these different audio streams by loading a new stream resource into your ConsumableMusic node before playing it.

    ### Player.gd

    # Audio nodes
    @onready var pause_menu_music = $GameMusic/PauseMenuMusic
    @onready var background_music = $GameMusic/BackgroundMusic
    @onready var game_over_music = $GameMusic/GameOverMusic
    @onready var dialog_music = $GameMusic/DialogMusic
    @onready var level_up_music = $GameMusic/LevelUpMusic
    @onready var pickups_sfx = $GameMusic/PickupsMusic
    @onready var consume_sfx = $GameMusic/ConsumableMusic

    func _input(event):
        # older code
        #using health consumables
        elif event.is_action_pressed("ui_consume_health"):
            if health > 0 && health_pickup > 0:
                health_pickup = health_pickup - 1
                health = min(health + 50, max_health)
                health_updated.emit(health, max_health)
                health_pickups_updated.emit(health_pickup) 
                # SFX
                consume_sfx.play()
        #using stamina consumables      
        elif event.is_action_pressed("ui_consume_stamina"):
            if stamina > 0 && stamina_pickup > 0:
                stamina_pickup = stamina_pickup - 1
                stamina = min(stamina + 50, max_stamina)
                stamina_updated.emit(stamina, max_stamina)      
                stamina_pickups_updated.emit(stamina_pickup)
                # SFX
                consume_sfx.stream = load("res://Assets/FX/Music/Free Retro SFX by @inertsongs/SFX/stam0.wav")
                consume_sfx.play()
Enter fullscreen mode Exit fullscreen mode

Now if you run over your pickups items, and consume them, the audio should play!

BULLET IMPACT SOUND EFFECT

When a bullet hits our Player or Enemy, we want the bullet impact sound to play. We will add this audio in our Enemy and Player scenes.

Let’s play the “Retro Impact LoFi 09.wav” sound using the AudioStreamPlayer2D node which we’ll rename as “BulletImpactMusic”. Add this node to both your Enemy and Player scenes.

*Not SprintingMusic, but BulletImpactMusic

Godot RPG

Godot RPG

After we impact with our node, let’s play this sound.

    ### Enemy.gd

    # Audio nodes
    @onready var bullet_sfx = $GameMusic/BulletImpactMusic

    #will damage the enemy when they get hit
    func hit(damage):
        health -= damage
        if health > 0:
            #damage
            animation_player.play("damage")
            # SFX
            bullet_sfx.play()
Enter fullscreen mode Exit fullscreen mode
    ### Player.gd

    # Audio nodes
    @onready var pause_menu_music = $GameMusic/PauseMenuMusic
    @onready var background_music = $GameMusic/BackgroundMusic
    @onready var game_over_music = $GameMusic/GameOverMusic
    @onready var dialog_music = $GameMusic/DialogMusic
    @onready var level_up_music = $GameMusic/LevelUpMusic
    @onready var pickups_sfx = $GameMusic/PickupsMusic
    @onready var consume_sfx = $GameMusic/ConsumableMusic
    @onready var bullet_sfx = $GameMusic/BulletImpactMusic

    # ------------------- Damage & Death ------------------------------
    #does damage to our player
    func hit(damage):
        health -= damage    
        health_updated.emit(health, max_health)
        if health > 0:
            #damage
            animation_player.play("damage")
            health_updated.emit(health, max_health)
            # SFX
            bullet_sfx.play()
Enter fullscreen mode Exit fullscreen mode

Now if we run our scene and we hit the enemy or the enemy hits us with a bullet, our sound effect should play.

SHOOTING SOUND EFFECT

When we shoot our weapons, we also want our guns to play a shooting sound effect. We’ll do this for both our Enemy and our Player.

Let’s play the “Retro Weapon Gun LoFi 03.wav” sound using the AudioStreamPlayer2D node which we’ll rename as “ShootingMusic”. Add this node to both your Enemy and Player scenes.

Godot RPG

Let’s play this sound effect in our ui_attack input.

    ### Player.gd

    # Audio nodes
    @onready var pause_menu_music = $GameMusic/PauseMenuMusic
    @onready var background_music = $GameMusic/BackgroundMusic
    @onready var game_over_music = $GameMusic/GameOverMusic
    @onready var dialog_music = $GameMusic/DialogMusic
    @onready var level_up_music = $GameMusic/LevelUpMusic
    @onready var pickups_sfx = $GameMusic/PickupsMusic
    @onready var consume_sfx = $GameMusic/ConsumableMusic
    @onready var bullet_sfx = $GameMusic/BulletImpactMusic
    @onready var shooting_sfx = $GameMusic/ShootingMusic 

    func _input(event):
        #input event for our attacking, i.e. our shooting
        if event.is_action_pressed("ui_attack"):
            #checks the current time as the amount of time
            var now = Time.get_ticks_msec()
            #check if player can shoot if the reload time has passed and we have ammo
            if now >= bullet_fired_time and ammo_pickup > 0:
                #SFX
                shooting_sfx.play()
                #shooting anim
                is_attacking = true
                var animation  = "attack_" + returned_direction(new_direction)
                animation_sprite.play(animation)
                #bullet fired time to current time
                bullet_fired_time = now + bullet_reload_time
                #reduce and signal ammo change
                ammo_pickup = ammo_pickup - 1
                ammo_pickups_updated.emit(ammo_pickup)
Enter fullscreen mode Exit fullscreen mode

For our enemy, we’ll need to update its process() function to also have a reload time so that it doesn’t loop the audio excessively together. We did this in the Player script.

    ### Enemy.gd

    # Audio nodes
    @onready var bullet_sfx = $GameMusic/BulletImpactMusic
    @onready var shooting_sfx = $GameMusic/ShootingMusic 

    #------------------------------------ Damage & Health ---------------------------------
    func _process(delta):
        #regenerates our enemy's health
        health = min(health + health_regen * delta, max_health)
        #checks the current time as the amount of time passed 
        var now = Time.get_ticks_msec()
        #check if enemy can shoot
        if now >= bullet_fired_time:
            # What's the target?
            var target = $RayCast2D.get_collider()
            if target != null:
                if target.name == "Player" and player.health > 0: 
                    # SFX
                    shooting_sfx.play()
                    #shooting anim
                    is_attacking = true
                    var animation  = "attack_" + returned_direction(new_direction)
                    animation_sprite.play(animation)
                    #reload time to bullet fired time
                    bullet_fired_time = now + bullet_reload_time
Enter fullscreen mode Exit fullscreen mode

Now if you run your scene, your player’s shooting sound should play when you press CTRL, and the enemy’s shooting sound should play when they attack you.

ENEMY DEATH SOUND EFFECT

Finally, we also want to play a sound effect when our enemy dies.

Add a new AudioPlayer2D node to your EnemySpawner scene and call it “Death Music”. The audio file should be “dmg0.wav”.

Godot RPG

We will play this sound effect when our spawner decreases our enemy count by 1.

    ###EnemySpawner.gd

    # Audio nodes
    @onready var death_sfx = $GameMusic/DeathMusic

    # Remove enemy
    func _on_enemy_death():
        enemy_count = enemy_count - 1
        death_sfx.play()
Enter fullscreen mode Exit fullscreen mode

If you run your scene and kill the enemies, the sound effect should play.

PLAYING DIFFERENT MUSIC IN OUR MAIN SCENES

In our Main_2 scene, we want to play our “Imposter Syndrome (wav)” audio track instead of the Background music we assigned to our Player. To do this, we can simply re-assign our stream resource and then play the node again.

Before we do this, we have to re-import our “Imposter Syndrome (wav)” file to be a looped audio file. If we don’t do this, it plays and then stops when it finishes. To re-import it, click on it and open your Import Dock. Set its import mode to “Forward”, and then re-import it.

You’ll notice that we haven’t imported any of our other audio files with looping enabled, and that is because the default import settings for WAV files in Godot 4 are set to recognize and utilize looping, while MP3 settings are not. Thus, if we were using the audio files with a .mp3 extension, we would’ve imported them to enable looping, but since we were using .wav audio files, the Godot engine automatically recognized which audios should loop.

Godot RPG

    ### Main_2.gd

    extends Node2D

    @onready var background_music = $Player/GameMusic/BackgroundMusic

    #connect signal to function 
    func _ready():
        background_music.stream = load("res://Assets/FX/Music/Free Retro SFX by @inertsongs/Imposter Syndrome (theme).wav")
        background_music.play()

    # Change scene
    func _on_trigger_area_body_entered(body):
        if body.is_in_group("player"):
            Global.change_scene("res://Scenes/Main.tscn")
            Global.scene_changed.connect(_on_scene_changed)

    #only after scene has been changed, do we free our resource     
    func _on_scene_changed():
        queue_free()
Enter fullscreen mode Exit fullscreen mode

Now if you run back and forth between your different scenes, your music should change.

And there you have it. You now have a full game with music, a GUI, enemies, an NPC, and a basic quest! If this is the end of the road for you, and you are ready to move on to the next project, then I’ll see you in the next part when I show you how test, debug, and export your project. Remember to save and make a backup, and I’ll see you in the next part.

The final source code for this part should look like this.

Buy Me a Coffee at ko-fi.com

FULL TUTORIAL

Godot RPG

The tutorial series has 23 chapters. I’ll be releasing all of the chapters in sectional daily parts over the next couple of weeks.

If you like this series or want to skip the wait and access the offline, full version of the tutorial series, you can support me by buying the offline booklet for just $4 on Ko-fi!😊

You can find the updated list of the tutorial links for all 23 parts in this series here.

Top comments (3)

Collapse
 
iamcymentho profile image
Odumosu Matthew

Really great piece. It was really insightful . I would like to know how you made the screenshots by the way ,if you dont mind ?

Collapse
 
christinec_dev profile image
christine

Thank you! I use the Windows snipping tool for screenshots. You can use it by pressing Windows Key + Shift + S

Collapse
 
iamcymentho profile image
Odumosu Matthew

Oh great..Thank you. You've been so helpful