[Godot] 3D Audio and Spatial Sound: Conveying Distance and Direction with AudioStreamPlayer3D

Created: 2026-02-08Last updated: 2026-07-08

How to implement 3D spatial audio with Godot's AudioStreamPlayer3D. Covers choosing an attenuation model, separating listener from camera with AudioListener3D, the Doppler effect, Area3D reverb zones, distance filtering, and performance optimization, with code and diagrams.

An enemy's footsteps closing in from behind. A distant explosion heard faintly from the left. Reverb kicking in the moment you enter a cave. Immersion in 3D games depends heavily on conveying where a sound comes from and how far away it is .

In Godot, AudioStreamPlayer3D handles this for you. Give a sound source a position and volume plus stereo panning change automatically based on distance and direction from the listener (usually the camera). This article builds from basic setup through choosing attenuation models, separating the listener, the Doppler effect, and per-area reverberation.

A 3D audio image of sound ripples spreading spherically from a speaker on a 3D grid floor and reaching a listener figure some distance away

What You'll Learn

  • AudioStreamPlayer3D basics: volume changes with distance, panning with direction, automatically
  • Choosing an attenuation model by scene scale (Inverse / Square / Log / Disabled)
  • Separating listener from camera with AudioListener3D (third-person, cutscenes)
  • The Doppler effect and Area3D reverb zones
  • Muffling filters based on distance, and performance optimization for many sources

Sponsored

AudioStreamPlayer3D Basics

AudioStreamPlayer3D is an audio playback node that has a position in 3D space . Volume changes automatically with distance from the listener (normally the Camera3D with current = true), and stereo panning changes with direction . Positioning like "an enemy's voice from the back right" or "an explosion directly behind you" comes just from placing the node.

A diagram showing that a nearby source reaches the listener figure loudly from the right while a distant source arrives quietly from the left. Distance sets volume and direction sets panning

These are the main properties controlling how sound reaches the listener.

PropertyDescriptionDefault
unit_sizeDistance at which volume halves. Effectively sets the audible range10.0
max_distanceDistance beyond which the sound is fully silent (0 = unlimited)0
attenuation_modelHow distance attenuation is calculated (covered below)Inverse Distance
panning_strengthStrength of stereo panning1.0
emission_angle_enabledDirectionality (projecting strongly in one direction)false

Here is the basic setup for enemy footsteps.

# Enemy (CharacterBody3D)
#   └── FootstepSound (AudioStreamPlayer3D)
@onready var footstep: AudioStreamPlayer3D = $FootstepSound

func _ready() -> void:
    footstep.stream = preload("res://audio/sfx/footstep.ogg")
    footstep.unit_size = 5.0       # Volume halves at 5m = clearly audible reasonably close
    footstep.max_distance = 30.0   # Silent beyond 30m (cuts wasted calculation)
    footstep.bus = "SFX"           # Route through the SFX bus

func play_footstep() -> void:
    if not footstep.playing:       # Avoid overlapping playback
        footstep.play()

unit_size is the knob that sets the felt "audible range." When you want to project in one direction only, like a security camera alarm or a PA speaker , set emission_angle_enabled = true and narrow the angle with emission_angle_degrees.

Choosing an Attenuation Model

attenuation_model determines what curve volume follows as distance increases . You choose based on whether you want realism or in-game audibility.

A graph comparing four attenuation curves of distance versus volume. Inverse Distance is gentle, Inverse Square is steep, Logarithmic is in between, and Disabled has no attenuation
ModelAttenuationBest-suited scenes
Inverse DistanceInversely proportional to distance (gentle)Wide outdoor fields
Inverse SquareInversely proportional to distance squared (realistic, steep)Realism-focused games
LogarithmicLogarithmic (close to human hearing)Indoors, close-range exchanges
DisabledNo attenuation (constant)Music-like ambience
footstep.attenuation_model = AudioStreamPlayer3D.ATTENUATION_INVERSE_SQUARE_DISTANCE

# Guideline values for unit_size and max_distance (by scene scale)
# Small room     : unit_size=2.0,  max_distance=10.0
# Mid-size indoor: unit_size=5.0,  max_distance=25.0
# Outdoor field  : unit_size=10.0, max_distance=50.0

When in doubt, a solid approach is to start from Inverse Square (realistic) and raise unit_size if things are hard to hear.

Sponsored

Separating the Listener with AudioListener3D

By default, you hear sound from wherever the camera is. But when a third-person camera pulls far back , the camera and player positions diverge and sounds right next to you feel distant, which is jarring.

Placing an AudioListener3D as a child of the player and activating it makes sound heard relative to the player's position no matter where the camera is.

A diagram of a third-person view where the camera is pulled back but the listening point sits at the player's head, so nearby sources are heard from the player's position
PatternListenerUse
Typical gamesCamera3D (default)Camera position as listening position is fine
Third-personAudioListener3DHear sounds near the player even with a pulled-back camera
CutscenesAudioListener3DKeep the mix player-relative while the camera moves
# Player (CharacterBody3D)
#   └── AudioListener3D
@onready var listener: AudioListener3D = $AudioListener3D

func _ready() -> void:
    listener.make_current()  # Activate this listener (takes priority over the camera)

From the moment you call make_current(), the listening point moves here. If you want to hand it back to the camera temporarily for a cutscene, clear_current() releases it.

The Doppler Effect

A car passing by, an ambulance going past. A moving source rises in pitch as it approaches and drops as it recedes. This Doppler effect is enabled with a single property.

A diagram of a car passing to the right in front of the listener. On the approaching side sound waves compress into a higher pitch, and on the receding side they spread into a lower pitch
# Enable Doppler tracking on both the source and the listening Camera3D
$AudioStreamPlayer3D.doppler_tracking = AudioStreamPlayer3D.DOPPLER_TRACKING_PHYSICS_STEP

# Tracking modes
# DOPPLER_TRACKING_DISABLED     ... off (default)
# DOPPLER_TRACKING_IDLE_STEP    ... synced to _process()
# DOPPLER_TRACKING_PHYSICS_STEP ... synced to _physics_process() (stable even at high speed)

Doppler is calculated from the source's velocity , so for fast-moving objects, PHYSICS_STEP syncing with physics is more stable (see also _process vs _physics_process). It's best reserved for racing games, projectiles, and vehicles. Leaving it on everywhere adds calculation.

Sponsored

Area3D Reverb Zones

"Sound echoes the instant you enter a cave." Per-space reverberation can be automated with Area3D's reverb zone feature. In the AudioBus effects article we added and removed reverb manually; this instead switches to a dedicated bus automatically on entering the area .

A diagram where sound reverberates at length inside a dashed Area3D zone (a cave) and sounds clean outside. Inside the zone, reverb switches on automatically

Define the volume on an Area3D with a CollisionShape3D and specify a reverb bus.

func setup_reverb_zone() -> void:
    var area := $ReverbZone as Area3D
    area.reverb_bus_enabled = true          # Enable reverb for this area
    area.reverb_bus_name = &"CaveReverb"    # Destination reverb bus
    area.reverb_bus_amount = 0.6            # How much is sent to the reverb bus
    area.reverb_bus_uniformity = 0.8        # How uniformly it applies within the area

Add an AudioEffectReverb (for example room_size: 0.85 / wet: 0.4) to the destination CaveReverb bus ahead of time. When the player enters the volume, sounds inside are automatically sent to that bus and pick up reverberation. Leaving the area reverts it automatically, so you write no entry/exit code at all.

Distance Muffling and Performance

For extra realism, cutting the highs off distant sounds to muffle them heightens the sense of distance. Near sounds stay clear while far ones go murky, which you build with the low-pass filter property.

A contrast diagram where a nearby source has a waveform with clear high frequencies and a distant source has a rounded, muffled waveform with the highs cut. Applying a low-pass by distance conveys distance
extends AudioStreamPlayer3D

@export var listener_node: Node3D
@export var far_distance: float = 30.0

func _process(_delta: float) -> void:
    if not listener_node:
        return
    var dist := global_position.distance_to(listener_node.global_position)
    var ratio := clampf(dist / far_distance, 0.0, 1.0)
    # The farther away, the lower the high-frequency ceiling (20500Hz to 2000Hz) = muffled
    attenuation_filter_cutoff_hz = lerpf(20500.0, 2000.0, ratio)

Performance Optimization

In something like an open world with a huge number of sound sources, CPU cost becomes a problem. These three measures do the most work.

MeasureDescription
Always set max_distanceAvoid 0 (unlimited) and only calculate the range you need
Limit simultaneous playbackKeep max_polyphony at 1 to 4, and simultaneously playing nodes at roughly 20 to 30 or fewer
Stop distant sourcesOnce far enough from the listener, stop with playing = false (LOD-style culling)
# Stop sources far from the listener to save CPU
func _process(_delta: float) -> void:
    var camera := get_viewport().get_camera_3d()
    if not camera:
        return
    var dist := global_position.distance_to(camera.global_position)
    if dist > max_distance * 1.2 and playing:
        stop()
    elif dist <= max_distance and not playing:
        play()

Managing large numbers of sources in an object pooling style is also worth considering once load grows.

Sponsored

Bonus: Good to Know for Later

  • Adjust unit_size first : when something is quieter or louder than expected, raising or lowering unit_size is the quickest fix, before touching the attenuation model.
  • For 2D games, use AudioStreamPlayer2D : top-down 2D can convey distance and left-right position too. The 3D thinking transfers almost directly.
  • Give each location its own ambience : for waterfalls, campfires, and crowds, place a source with Disabled attenuation and a short max_distance to get ambience that fades in as you approach.
  • Track switching belongs to adaptive music : changing music by area or combat situation is adaptive music territory.

Summary

  • AudioStreamPlayer3D changes volume with distance and panning with direction automatically. unit_size sets the felt audible range
  • Attenuation models are chosen by scene scale (Inverse Square when in doubt). Always set max_distance to cut wasted calculation
  • AudioListener3D separates the listening point from the camera (useful in third-person and cutscenes)
  • Doppler means setting doppler_tracking to PHYSICS_STEP, and Area3D automates per-space reverberation with reverb zones
  • Optimization rests on three pillars: max_distance, simultaneous playback limits, and stopping distant sources

Start by adding footsteps to an enemy with AudioStreamPlayer3D, then play with unit_size and max_distance to feel the "audible as you approach" effect. Add listener separation and area reverb on top and your world starts sounding genuinely three-dimensional.

Further Reading