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.
■ UI node · ■ 2D node · % unique name · .tscn instanced scene
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 streamextends CanvasLayer
## Swaps the line of text along the top. sound_effects.gd calls this instead of
## reaching down into this scene and setting the Label itself, so moving the
## Label around in here cannot break the level.
func show_message(text: String) -> void:
%MessageLabel.text = text
func _ready() -> void:
# "pressed" is a signal Godot builds into every Button. We put the name
# "_on_home_pressed" inside ".connect()". That is the function Godot
# runs when HomeButton is clicked.
%HomeButton.pressed.connect(_on_home_pressed)
# Loads the lesson list screen, which replaces this whole scene.
func _on_home_pressed() -> void:
get_tree().change_scene_to_file("res://shared/ui/lesson_select.tscn")class_name SfxPlayer
extends CharacterBody2D
## How fast the player moves. "@export" puts "speed" at the top of the
## Inspector panel, so you can change it there instead of in this file.
@export var speed: float = 200.0
# "_physics_process()" runs about 60 times a second. Godot calls it for you.
func _physics_process(_delta: float) -> void:
# "Input.get_vector(...)" checks all four keys at once and hands back
# an arrow pointing where to go. The four names in quotes are ours.
# See them under Project > Project Settings > Input Map.
var direction: Vector2 = Input.get_vector(
"move_left", "move_right", "move_up", "move_down"
)
# "direction" is only WHICH WAY (its length is always 1).
# "direction * speed" stretches it, so "velocity" is also HOW FAST.
velocity = direction * speed
# Nothing moves until "move_and_slide()" runs.
# "Slide" means we slide along walls instead of sticking to them.
move_and_slide()
# The warrior picture only faces right. Walking left mirrors it.
if direction.x != 0.0:
$Sprite2D.flip_h = direction.x < 0.0