Table of Contents
Introduction
In this post we’re going to continue our series on Godot Fundamentals. We’ll cover adding Audio to our Game to improve the overall feel. We’ll look at The Audio Bus Layout, Adding Audio via Audio Stream Players, and controlling our custom Bus volumes from our UI.
Video
You can watch the video covering this topic, or go at your own pace following this post.
Custom Audio Bus Layout
Open the Audio panel at the bottom of our Editor to look at our AudioServer overview containg our default Master Audio Bus. For our game, we'll be adding a Music and SFX Audio Bus. Click Add Bus and call it Music.
Notice at the bottom of the new Bus, it defaults to tracking back to the Master Bus, this let's us control Bus grouping and will cascade based on this setting.
Next create another Bus called SFX. You'll also note that each Bus has it's own volume slider for controlling the db for that Bus. You can also add effects to a Bus to modify all sounds on that Bus.
Audio Assets
Next let's organize our Audio Assets. You can download the assets from our Github project , or create your own by going to a tool like Chiptone or jsfxr . You can also check out free music on the Free Music Archive as well.
We'll create a new folder in our assets
folder called audio
and inside that folder let's create a sfx
folder and a music
folder. Copy your assets into the proper folders and ensure they are playing correctly inside Godot. For your BG music, you may want to enable looping. Double click to re-import the music file and check the box to enable looping and then re-import. Now the track will continue to repeat itself once it completes.
BG Music
We can add our background music to our game by opening our Game scene and adding a child node of type AudioStreamPlayer
. Drag over the music file from your assets and select the Music Bus in the inspector. If you have a very loud or quiet music track, you may also adjust the volume dB in the inspector. We'll be controlling our audio via the UI later on. For our Background music we also want it to Autoplay, so enable that.
SFX
Open our Weapon scene and add a child node of type AudioStreamPlayer
and in the inspector drag over the sfx file for firing our gun, select the SFX Bus, and adjust the volume to your needs. Open the Weapon's script scenes/tank/weapon/weapon.gd
and CTRL + Drag a reference of the Node into our script and rename it to audio_player. Inside our fire function we'll add a call to the audio_player's play function.
class_name Weapon
...
@onready var audio_player = $AudioStreamPlayer
...
func fire():
...
bullet.global_position = global_position
audio_player.play()
Next open the Crate scene and add the child node of type AudioStreamPlayer
and repeat the process to setup the inspector to your needs, ensuring it's on the SFX Bus and has the audio file defined. Open the Crate's script scenes/world/crate.gd
and CTRL + Drag a reference of the Node into our script and rename it to audio_player.
Note that when we destroy the crate, we call queue_free() which will destroy this object, which prevents the audio from processing properly. To work around this we'll want to await our audio_player's finished signal and then continue destroying the crate from within our destroy function.
class_name Crate
...
@onready var audio_player = $AudioStreamPlayer
...
func destroy():
audio_player.play()
await audio_player.finished
queue_free()
Next for the Pickup Scene, add the child node for AudioStreamPlayer
and set the values in the inspector. Open the script scenes/world/pickup.gd
and CTRL + Drag a refrence of the Node into our script and rename it to audio_player. inside our _on_body_entered function we'll also await the finishing of our audio player before queuing free.
class_name Pickup
@onready var audio_player = $AudioStreamPlayer
func _on_body_entered(body):
if body is Tank:
body.collect(self)
audio_player.play()
await audio_player.finished
queue_free()
Audio Control via UI
Open our UI scenes/ui/ui.tscn
and add a new Control node as a child to the existing Control node and rename it to Menu. We can also give it a Unique Name accessor. CTRL + Drag this into the scenes/ui/ui.gd
script to track a reference to our menu in code.
In the 2D view, let's setup our Menu Layout. We'll ensure our new Control node has anchoring for Full Rect. Add a child node of type ColorRect
to our Menu and set it's color to a dark gray semi transparent color. Add another child to our Menu of type MarginContainer
and set it's Left and Right margin constants to 256. Inside the MarginContainer
add a child VboxContainer
and add a GridBoxContainer
as a child of that. Our GridBoxContainer's alignment should fill/expand horizontally, but centered vertically.
Next we'll be adding a Label
and HSlider
for the SFX and Music setting inside this GridBoxContainer
. We'll call the Music Label MusicLabel and the Slider MusicSlider.
We'll call the SFX Label SFXLabel and the Slider SFXSlider.
Set the Label's text values to "SFX" and "Music" respectively.
Make sure your Sliders' Layout Container Sizing is set to Shrink to Center to best align in the UI. We can also add H Seperation between elements within the GridBoxContainer
and set equal to 16. Our Slider's are also going to be mapping from 0 - 1 and should have their max set to 1 and their step size set to a nice snappy amount such as .05, and the starting value to 1 which is our default audio level.
Next for each slider we'll attach the Range's value_changed signal which passes the new value to the function. We'll connect these the UI's script, which will be called _on_music_slider_value_changed and _on_sfx_slider_value_changed. These funcitons will both call the AudioServer
's set_bus_volume_db and set_bus_mute functions to update the bus volume and mute if we are under a threshold with our new value. These calls require the BUS index which we can also retrieve from our AudioServer
and store in vars on ready. We'll also convert the Slider's value in linear to the db equivilant using the linear_to_db funciton.
class_name UI
...
@onready var SFX_BUS_ID = AudioServer.get_bus_index("SFX")
@onready var MUSIC_BUS_ID = AudioServer.get_bus_index("Music")
@onready var menu = %Menu
...
func _on_music_slider_value_changed(value):
AudioServer.set_bus_volume_db(MUSIC_BUS_ID, linear_to_db(value))
AudioServer.set_bus_mute(MUSIC_BUS_ID, value < .05)
func _on_sfx_slider_value_changed(value):
AudioServer.set_bus_volume_db(SFX_BUS_ID, linear_to_db(value))
AudioServer.set_bus_mute(SFX_BUS_ID, value < .05)
Lastly, we'll add a simple input event to check if we are pressing our ui_cancel
action, we can toggle our Menu to be visible/invisible.
func _input(event):
if event.is_action_pressed("ui_cancel"):
menu.visible = !menu.visible
Recap
There we have a great overview of adding Audio assets to your game in the form of Music and SFX separation using Audio Buses and the ability to control them in the UI using the slider node. Be sure to join the discussions on our Discord community server and stay tuned for more content.
Happy Coding!