All lessons
Lesson 16

Sound Effects

Two ways to make a noise: load a .wav file, or build a beep in code with no file at all.

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

This lesson shows TWO ways to get a sound: 1. Load it from a file: pickup.wav, sitting in this lesson's folder. 2. Build it in code: no file at all. See _make_beep() at the bottom.

The scenes

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

sound_effects.tscn
  • SoundEffects Node2D sound_effects.gd
  • SfxPlayer CharacterBody2D player.tscn
  • PickupArea Area2D %
  • ColorRect
  • CollisionShape2D
  • PickupSound AudioStreamPlayer %
  • JumpSound AudioStreamPlayer %
  • HUD CanvasLayer % hud.tscn
hud.tscn
  • HUD CanvasLayer hud.gd
  • MarginContainer
  • MessageLabel Label %
  • HomeButton Button %
player.tscn
  • SfxPlayer CharacterBody2D player.gd
  • Sprite2D
  • CollisionShape2D
extends Node2D

## The pickup sound, loaded from a file in this lesson's folder.
var pickup_stream: AudioStream = preload("res://lessons/sound_effects/pickup.wav")

func _ready() -> void:
	# Hand the loaded file to the player node so it has something to play.
	%PickupSound.stream = pickup_stream
	# The jump sound is built in code instead. There is no file on disk.
	%JumpSound.stream = _make_beep(440.0, 0.1)
	# When something touches PickupArea, run "_on_pickup_collected".
	%PickupArea.body_entered.connect(_on_pickup_collected)

# "body" is whatever touched the pickup area.
func _on_pickup_collected(body: Node2D) -> void:
	# "SfxPlayer" is the class_name at the top of player.gd.
	if body is SfxPlayer:
		%PickupSound.play()
		# Erase the pickup so it can only be collected once.
		%PickupArea.queue_free()
		# "%HUD" finds the HUD by its unique name, so it keeps working even if
		# the HUD is moved or tucked inside a group later.
		%HUD.show_message("Sound played!")

# "_input()" runs whenever the player presses a key. Godot calls it for you.
func _input(event: InputEvent) -> void:
	if event.is_action_pressed("jump"):
		%JumpSound.play()

## Builds a short beep from scratch, with no sound file.
## "frequency" is the pitch: a bigger number makes a higher beep.
## "duration" is how many seconds the beep lasts.
func _make_beep(frequency: float, duration: float) -> AudioStreamWAV:
	# A sound is really just a long list of numbers describing how a speaker
	# should wiggle. "sample_rate" is how many numbers make one second.
	var sample_rate: int = 22050
	var sample_count: int = int(duration * sample_rate)
	var data: PackedByteArray = PackedByteArray()
	# Each number needs 2 bytes of room, so we ask for double the space.
	data.resize(sample_count * 2)
	for i in sample_count:
		# "t" is how far into the beep we are, measured in seconds.
		var t: float = float(i) / sample_rate
		# "sin()" draws a smooth up-and-down wave. That is exactly what a
		# pure musical tone looks like. The 0.5 makes it half as loud.
		var value: float = sin(t * frequency * TAU) * 0.5
		# Turn the wave (-1.0 to 1.0) into a whole number the speaker uses.
		var sample: int = int(value * 32767)
		# That number is too big to fit in one byte, so we split it into
		# two halves and store them side by side.
		data[i * 2] = sample & 0xFF
		data[i * 2 + 1] = (sample >> 8) & 0xFF
	# Wrap the finished list of numbers up as a sound Godot can play.
	var stream: AudioStreamWAV = AudioStreamWAV.new()
	stream.format = AudioStreamWAV.FORMAT_16_BITS
	stream.mix_rate = sample_rate
	stream.data = data
	return stream