Overview
Tested with: Godot 4.3+
Spatial audio in 3D games is a crucial element for player immersion. Footsteps approaching from behind, distant explosions fading into the background -- these distance and direction-based sound changes are easy to implement in Godot using AudioStreamPlayer3D.
This article covers everything you need to know: from basic AudioStreamPlayer3D setup and attenuation model selection to AudioListener3D usage, Doppler effect implementation, and area-based reverb zones.
AudioStreamPlayer3D Basics
The first step toward 3D spatial audio is understanding the AudioStreamPlayer3D node. Let's start by looking at what this node does for you.
AudioStreamPlayer3D is an audio playback node with positional information in 3D space. Volume and panning automatically change based on the distance and direction to the listener (typically Camera3D).
Key Properties
Let's start by understanding the core properties. These settings control how far sound travels and how it attenuates over distance.
| Property | Description | Default |
|---|---|---|
max_distance | Maximum distance at which sound is audible | 0 (unlimited) |
unit_size | Distance at which volume is halved | 10.0 |
attenuation_model | How volume decreases with distance | InverseDistance |
max_polyphony | Maximum simultaneous playback count | 1 |
panning_strength | Stereo panning intensity | 1.0 |
emission_angle_enabled | Enable directional sound | false |
emission_angle_degrees | Angular range of sound emission | 45.0 |
Basic Setup
Here's an example of adding footstep sounds to an enemy character, showing the typical setup workflow.
# Scene tree example
# Enemy (CharacterBody3D)
# └── FootstepSound (AudioStreamPlayer3D)
# Configure enemy footsteps
@onready var footstep = $FootstepSound
func _ready():
footstep.stream = preload("res://audio/sfx/footstep.ogg")
footstep.max_distance = 30.0 # Audible up to 30m away
footstep.unit_size = 5.0 # Volume halves at 5m
footstep.bus = "SFX"
func play_footstep():
if not footstep.playing:
footstep.play()
Directional Sound
Think of a security camera alarm or a PA speaker -- sometimes you want sound to travel in a specific direction only. Set emission_angle_enabled = true and configure emission_angle_degrees for the angular range. Use emission_angle_filter_attenuation_db to specify how much the sound attenuates outside that range.
Choosing an Attenuation Model
Now that you understand the basics of AudioStreamPlayer3D, let's pick an attenuation model that defines how your sounds "feel" over distance. You can select how volume decreases with distance. The optimal model depends on whether you prioritize realistic physics or gameplay feel.
| Model | Attenuation Behavior | Best For |
|---|---|---|
| Inverse Distance | Inversely proportional to distance (gradual) | Large outdoor fields |
| Inverse Square Distance | Inversely proportional to distance squared (realistic) | Realistic games in general |
| Logarithmic | Logarithmic decay (closer to human hearing) | Indoor, close-range interactions |
| Disabled | No distance-based attenuation | Ambient sounds, background audio |
Configuration
The following code sets the attenuation model and distance parameters. Check the comments for guidelines on different scene scales, and adjust to fit your game.
# Set the attenuation model
footstep.attenuation_model = AudioStreamPlayer3D.ATTENUATION_INVERSE_SQUARE_DISTANCE
# Guidelines for unit_size and max_distance
# Small room: unit_size=2.0, max_distance=10.0
# Medium indoor: unit_size=5.0, max_distance=25.0
# Outdoor field: unit_size=10.0, max_distance=50.0
tips:
unit_sizeis the distance at which volume is halved. A larger value means the sound carries farther; a smaller value means it's only audible up close.
Using AudioListener3D
With your sound sources configured, the next question is: "where does the player hear from?" In third-person games where the camera orbits at a distance, audio centered on the camera position can feel off.
By default, the Camera3D with current = true acts as the listener. When you need to separate the camera from the listener, use AudioListener3D.
When to Use Camera3D vs. AudioListener3D
| Scenario | Listener | Use Case |
|---|---|---|
| Standard game | Camera3D (default) | Camera position = listening position is fine |
| TPS (third person) | AudioListener3D | Keep audio near the player even when the camera is pulled back |
| Cutscenes | AudioListener3D | Maintain player-position audio while the camera moves |
| VR | AudioListener3D | Sync with head tracking |
Implementation Example
Simply place an AudioListener3D as a child of your player node and activate it. Audio will then be based on the player's position regardless of where the camera is.
# Add AudioListener3D as a child of Player (CharacterBody3D)
@onready var listener = $AudioListener3D
func _ready():
listener.make_current() # Activate this listener
Doppler Effect
Now let's dive into a more advanced audio technique. Enable the Doppler effect to shift the pitch of moving sound sources -- perfect for racing games where cars zoom past, or any scenario with fast-moving objects. This adds a layer of realism that players notice immediately.
Setup Steps
# 1. Enable Doppler tracking on AudioStreamPlayer3D
$AudioStreamPlayer3D.doppler_tracking = AudioStreamPlayer3D.DOPPLER_TRACKING_PHYSICS_STEP
# Tracking mode options
# DOPPLER_TRACKING_DISABLED ... Disabled (default)
# DOPPLER_TRACKING_IDLE_STEP ... Syncs with _process()
# DOPPLER_TRACKING_PHYSICS_STEP ... Syncs with _physics_process() (recommended)
Enable Doppler Tracking in Project Settings under Audio > General and adjust the Default Doppler Factor to control the effect intensity (default: 1.0).
tips: Using
PHYSICS_STEPsyncs with the physics engine, providing stable Doppler effects even for fast-moving objects.
Area Reverb Zones
With sources and listeners set up, let's take it a step further by adding per-space acoustics. Imagine stepping into a cave in an RPG and instantly hearing that deep echo -- Area3D makes this possible by applying reverb only when the player enters a specific zone.
Setup
Set the reverb range with a CollisionShape3D on the Area3D, and specify a dedicated bus in the Reverb Bus property. The following code configures a cave reverb zone from script.
func setup_reverb_zone():
var area = $ReverbZone as Area3D
area.audio_bus_override = true
area.audio_bus_name = &"CaveReverb"
area.reverb_bus_enabled = true
area.reverb_bus_name = &"CaveReverb"
area.reverb_bus_amount = 0.6
area.reverb_bus_uniformity = 0.8
Add an AudioEffectReverb (room_size: 0.85, damping: 0.3, wet: 0.4) to the CaveReverb bus. When the player enters the Area3D, audio output within the zone automatically routes to this bus.
Dynamic Control and Performance
At this point, your core 3D audio setup is complete. To wrap things up, let's look at filter techniques for more natural-sounding audio and performance strategies you'll need in real-world projects.
Distance-Based Filter Control
Distant sounds lose their high frequencies as they travel through air and obstacles, making them sound muffled. Recreating this with a low-pass filter makes distance feel much more convincing. Attach the following script to an AudioStreamPlayer3D.
extends AudioStreamPlayer3D
@export var listener_node: Node3D
@export var far_distance: float = 30.0
func _process(_delta):
if not listener_node:
return
var distance = global_position.distance_to(listener_node.global_position)
var ratio = clampf(distance / far_distance, 0.0, 1.0)
# Muffle distant sounds
attenuation_filter_cutoff_hz = lerpf(20500.0, 2000.0, ratio)
attenuation_filter_db = lerpf(0.0, -10.0, ratio)
Performance Optimization
In open-world scenes with many sound sources, CPU load can quickly become an issue. Use these optimization techniques:
| Strategy | Description |
|---|---|
| Set max_distance | Avoid 0 (unlimited); restrict to the needed range |
| Limit max_polyphony | Keep simultaneous playback count to 1-4 |
| Manage active node count | Aim for 20-30 or fewer simultaneous audio nodes |
| LOD-style management | Stop distant audio sources with playing = false |
This script auto-stops sound sources based on their distance from the listener, cutting wasted processing on off-screen audio.
# Auto-stop distant audio sources to reduce CPU load
func _process(_delta):
var camera = get_viewport().get_camera_3d()
if not camera:
return
var distance = global_position.distance_to(camera.global_position)
if distance > max_distance * 1.2 and playing:
stop()
elif distance <= max_distance and not playing:
play()
Summary
- AudioStreamPlayer3D automatically adjusts volume and panning based on distance and direction to the listener
- Choose an attenuation model based on your scene's scale and set
unit_sizeandmax_distanceappropriately - AudioListener3D lets you separate the camera and listener positions, useful for TPS and cutscenes
- The Doppler effect is most stable with
PHYSICS_STEPtracking synced to the physics engine - Area3D reverb zones let you create different audio environments per spatial area
- For performance, setting max_distance and limiting simultaneous playback are critical