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.
None of the earlier lessons use these. Each one shows the line where it appears here.
The sound an AudioStreamPlayer plays.
pickup_sound.stream = pickup_streamStarts a sound, or an animation by its name, playing.
pickup_sound.play()Godot runs this once for every key press, click or mouse move.
func _input(event: InputEvent) -> void:True when that key is down. On Input it stays true while the key is held; on an event it means this event is the key going down.
if event.is_action_pressed("jump"):A sound built from numbers in code. mix_rate is how many numbers make one second, and data holds them.
func _make_beep(frequency: float, duration: float) -> AudioStreamWAV:Turns a value into a whole number or a decimal. int() chops off anything after the point.
var sample_count: int = int(duration * sample_rate)A list of raw bytes, the smallest numbers a computer stores. resize() sets how many it holds.
var data: PackedByteArray = PackedByteArray()Turns an angle into a smooth wave between -1 and 1. cos() is the same wave, a quarter turn later.
var value: float = sin(t * frequency * TAU) * 0.5One full turn in radians, about 6.28.
var value: float = sin(t * frequency * TAU) * 0.5Makes a new object straight from code, like ColorRect.new(), instead of building it in the editor.
var stream: AudioStreamWAV = AudioStreamWAV.new()■ 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")
@onready var pickup_sound: AudioStreamPlayer = %PickupSound
@onready var jump_sound: AudioStreamPlayer = %JumpSound
@onready var pickup_area: Area2D = %PickupArea
@onready var hud: SoundHUD = %HUD
func _ready() -> void:
# Hand the loaded file to the player node so it has something to play.
pickup_sound.stream = pickup_stream
# The jump sound is built in code instead. There is no file on disk.
jump_sound.stream = _make_beep(440.0, 0.1)
# When something touches PickupArea, run "_on_pickup_collected".
pickup_area.body_entered.connect(_on_pickup_collected)
# "body" is whatever touched the pickup area.
func _on_pickup_collected(body: Node2D) -> void:
# "Player" is the class_name at the top of player.gd.
if body is Player:
pickup_sound.play()
# Erase the pickup so it can only be collected once.
pickup_area.queue_free()
# "hud" was grabbed once at the top, by unique name, so this 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"):
jump_sound.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 streamclass_name SoundHUD
extends CanvasLayer
@onready var message_label: Label = %MessageLabel
@onready var home_button: Button = %HomeButton
## Swaps the line of text along the top. game.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:
message_label.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.
home_button.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 Player
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
@onready var sprite: Sprite2D = $Sprite2D
# "_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:
sprite.flip_h = direction.x < 0.0