Lesson 17

Background Music

Loop a song underneath the whole game, and pause it right where it is instead of starting over.

Loading the game engine. This takes a moment the first time.

New code in this lesson

None of the earlier lessons use these. Each one shows the line where it appears here.

value_changed

The signal a slider sends when it is dragged, along with its new value.

game.gd
volume_slider.value_changed.connect(_on_volume_changed)

stream_paused

Pauses a sound where it is, and picks up from the same spot when set back to false.

game.gd
music_player.stream_paused = false

playing

True while a sound is playing.

game.gd
if not music_player.playing:

stop()

Stops a sound or an animation.

game.gd
music_player.stop()

volume_db

How loud a sound plays, in decibels. 0 is normal and lower numbers are quieter.

game.gd
music_player.volume_db = linear_to_db(value / 100.0)

linear_to_db()

Turns a 0 to 1 slider value into decibels for volume_db, so the slider feels even all the way along.

game.gd
music_player.volume_db = linear_to_db(value / 100.0)

The scenes

■ UI node · ■ 2D node · % unique name · .tscn instanced scene

game.tscn
  • Game Control game.gd
  • VBoxContainer
  • TitleLabel Label
  • StatusLabel Label %
  • PlayButton Button %
  • PauseButton Button %
  • StopButton Button %
  • VolumeLabel Label
  • VolumeSlider HSlider %
  • HomeButton Button %
  • MusicPlayer AudioStreamPlayer %

FileSystem

  • game.tscn
  • game.gd

The code

extends Control

## The music track, loaded from a file in this lesson's folder.
var music_stream: AudioStream = preload("res://lessons/background_music/ambience.mp3")

# "%MusicPlayer" means "the node named MusicPlayer in this scene."
@onready var music_player: AudioStreamPlayer = %MusicPlayer
@onready var play_button: Button = %PlayButton
@onready var pause_button: Button = %PauseButton
@onready var stop_button: Button = %StopButton
@onready var volume_slider: HSlider = %VolumeSlider
@onready var home_button: Button = %HomeButton
@onready var status_label: Label = %StatusLabel

func _ready() -> void:
	# Hand the loaded song to the player node so it has something to play.
	music_player.stream = music_stream
	# Each line below names the function to run when that button is clicked.
	play_button.pressed.connect(_on_play_pressed)
	pause_button.pressed.connect(_on_pause_pressed)
	stop_button.pressed.connect(_on_stop_pressed)
	# "value_changed" is a signal the slider sends every time you drag it.
	volume_slider.value_changed.connect(_on_volume_changed)
	home_button.pressed.connect(_on_home_pressed)

func _on_play_pressed() -> void:
	# Unpause first, in case the song was paused instead of stopped.
	music_player.stream_paused = false
	# Only call "play()" if nothing is playing. Calling it while the song is
	# already going would jump it back to the beginning.
	if not music_player.playing:
		music_player.play()
	status_label.text = "Status: Playing"

func _on_pause_pressed() -> void:
	# "not" flips true to false and false to true, so this one button both
	# pauses and unpauses.
	music_player.stream_paused = not music_player.stream_paused
	if music_player.stream_paused:
		status_label.text = "Status: Paused"
	else:
		status_label.text = "Status: Playing"

func _on_stop_pressed() -> void:
	# "stop()" forgets where the song was. Pressing play starts it over.
	music_player.stop()
	status_label.text = "Status: Stopped"

# "value" is the slider's number, from 0 to 100.
func _on_volume_changed(value: float) -> void:
	# Ears do not hear loudness evenly, so Godot measures volume in decibels.
	# "linear_to_db()" turns the slider's plain number into decibels for us.
	music_player.volume_db = linear_to_db(value / 100.0)

func _on_home_pressed() -> void:
	# Stop the music first, or it keeps playing on the lesson list screen.
	music_player.stop()
	get_tree().change_scene_to_file("res://shared/ui/lesson_select.tscn")