Playback Control#
This chapter explains how to play sounds and how to control them after they start playing.
When you are getting started, prefer playback nodes. Use code-driven playback only when you need finer control, such as creating a Sound yourself, getting a Channel, or manually setting loop points or 3D properties.
Choose a Playback Method First#
Need |
Recommended approach |
|---|---|
Background music, long audio, or a fixed player in a scene |
|
Sound effects in a 2D scene |
|
Sound sources in a 3D world |
|
The same short sound effect is triggered frequently |
Preload the Audio Asset or reuse a Sound |
Fine-grained control is needed after playback starts |
Get the Channel |
If you are not sure, start with nodes. Nodes cover most needs for playback, stopping, volume, pitch, and bus routing.
Play with Nodes#
The most common approach is to set the node’s stream and then call play().
extends Node
@onready var music := $FmodAudioStreamPlayer
func _ready():
var stream := FmodAudioStream.new()
stream.file_path = "res://music/bgm.mp3"
music.stream = stream
music.bus = "Music"
func play_music():
music.play()
func stop_music():
music.stop()
Here, stream is a Godot-FmodPlayer audio resource. When it is handed to FMOD for playback, a Sound is created under the hood, and each playback instance corresponds to a Channel.
Common Node Properties#
Properties |
Purpose |
|---|---|
|
The audio resource to play. |
|
Volume in decibels; |
|
Pitch and speed multiplier; |
|
Automatically play when the scene starts. |
|
Which Bus to output to. |
|
Whether it is currently playing. |
Fade In and Fade Out#
Because playback nodes are Godot nodes, you can use Tween directly:
func fade_music(target_db: float, duration: float):
var tween := create_tween()
tween.tween_property(music, "volume_db", target_db, duration)
Play a Short Sound Effect Once#
For one-shot sound effects such as pressing a button, firing a weapon, or picking up an item, you can place a player node and call play() repeatedly:
extends Node
@onready var hit := $HitPlayer
func _ready():
var stream := FmodAudioStream.new()
stream.file_path = "res://sfx/hit.wav"
hit.stream = stream
hit.bus = "SFX"
func play_hit():
hit.play()
If the same sound may overlap many times, such as several bullets fired in one second, consider code-driven playback or an object pool so one node does not have to handle every overlapping instance.
Play from Code#
Code-driven playback is closer to how FMOD works:
Choose which Bus / ChannelGroup to output to.
Call
play_sound()to get the Channel for this playback instance.
func play_sound(path: String, bus_name: String = "SFX") -> FmodChannel:
var system := FmodServer.get_main_system()
var sound := system.create_sound_from_file(path)
var bus := system.get_channel_group_by_name(bus_name)
var channel := system.play_sound(sound, bus, false)
return channel
The returned channel represents only this one playback instance. If the same sound is played three times, there will be three different Channel objects.
Create Paused, Then Configure#
Sometimes you need to set volume, pitch, or 3D position before the sound starts. Create it paused, configure it, and then resume playback:
func play_configured(path: String) -> FmodChannel:
var system := FmodServer.get_main_system()
var sound := system.create_sound_from_file(path)
var sfx_bus := system.get_channel_group_by_name("SFX")
var channel := system.play_sound(sound, sfx_bus, true)
channel.volume_db = -6.0
channel.pitch = 1.2
channel.set_paused(false)
return channel
Control One Playback Instance#
A Channel is one currently playing sound. It can be paused, stopped, have its volume and pitch adjusted, seek to a position, and receive 3D properties.
Pause, Resume, and Stop#
func pause_channel(channel: FmodChannel, paused: bool):
if channel != null:
channel.set_paused(paused)
func stop_channel(channel: FmodChannel):
if channel != null:
channel.stop()
func is_still_playing(channel: FmodChannel) -> bool:
return channel != null and channel.is_playing()
Volume, Pitch, and Pan#
func adjust_channel(channel: FmodChannel): channel.volume_db = -10.0 channel.pitch = 1.0 # -1.0 in left , 0.0 in , 1.0 in right . channel.set_pan(0.25)
pitch affects both pitch and playback speed. 0.5 sounds lower and slower, while 2.0 sounds higher and faster.
Playback Position#
Position is usually controlled in milliseconds. This is useful for music seeking, resuming from a specific time, or building a simple preview tool.
func jump_to_30_seconds(channel: FmodChannel):
channel.set_position(30000, FmodChannel.TIMEUNIT_MS)
func print_position(channel: FmodChannel):
var position_ms := channel.get_position(FmodChannel.TIMEUNIT_MS)
print("position: ", position_ms, " ms")
Looping#
func loop_forever(channel: FmodChannel):
channel.loop_count = -1
func loop_three_times(channel: FmodChannel):
channel.loop_count = 3
func disable_loop(channel: FmodChannel):
channel.loop_count = 0
Whether looping works also depends on the playback mode used when the sound was created. Regular node playback is usually better for simple loops; control Channel and Sound directly only when you need precise loop points.
2D and 3D Playback#
2D sounds do not follow world positions, so they are suitable for UI, music, and narration. 3D sounds calculate distance, direction, and attenuation from the Listener and the sound source position.
Prefer the 3D Playback Node#
In a 3D scene, the simplest approach is to use FmodAudioStreamPlayer3D. It synchronizes the node position to FMOD.
extends Node3D
@onready var emitter := $FmodAudioStreamPlayer3D
func play_at_current_position():
emitter.bus = "SFX"
emitter.play()
If a sound should follow an enemy, vehicle, door, mechanism, or environment object, make the player a child of that node.
Set 3D Position from Code#
You only need to set 3D properties manually when playing directly from code:
func play_3d_sound(path: String, position: Vector3):
var system := FmodServer.get_main_system()
var sound := system.create_sound_from_file(path, FmodSystem.MODE_3D)
var bus := system.get_channel_group_by_name("SFX")
var channel := system.play_sound(sound, bus, true)
channel.set_3d_attributes(position, Vector3.ZERO)
channel.set_3d_min_max_distance(1.0, 30.0)
channel.set_paused(false)
return channel
MODE_3D is important here. If a sound was not created in 3D mode, setting a position will not make it behave as a 3D sound source.
Listener#
The Listener represents where the player hears from. It usually follows the player character or the main camera.
extends CharacterBody3D
func _physics_process(_delta):
var system := FmodServer.get_main_system()
system.set_3d_listener_attributes(
0,
global_position,
velocity,
-global_transform.basis.z,
global_transform.basis.y
)
If a 3D sound has the wrong direction or distance, check the listener position and orientation first.
FAQ#
The Sound Does Not Play#
Check these in order:
The Sound Only Plays Once#
If the same node is still playing, calling play() again may restart that playback instead of creating a new overlapping sound. For overlapping playback, create multiple players, use an object pool, or call play_sound() directly to obtain multiple Channel objects.
Stutter When Playing Many Sounds#
For short sound effects, prefer Sample mode or preloading. For long music, prefer Stream mode. See Stream and Sample for the difference.
When many sounds play at once, also pay attention to Virtual Channel. FMOD may virtualize inaudible or low-priority sounds to reduce mixing cost.
Recommendations#
Start with playback nodes. Do not operate on lower-level objects right away.
Get a Channel when you need to control this specific playback instance.
Route long music to the
Musicbus and short sound effects to theSFXbus.Prefer FmodAudioStreamPlayer3D for 3D sounds.
When playing from code, create the sound paused if you need to configure parameters first, then resume playback.