A jump sound in Godot 4 is easy to wire up and easy to get
subtly wrong. The obvious implementation — one
AudioStreamPlayer with the jump clip assigned,
calling play() inside the jump condition — works
in a prototype and fails under three common conditions:
rapid successive jumps, Coyote Time, and pitch variation that
is too wide or applied at the wrong layer.
The sections below work through the three decisions that actually change the result: which playback method survives overlapping jumps, which pitch-randomisation approach produces variation without warping the sound, and how Coyote Time and jump buffering move the moment the sound needs to fire. The code is minimal and focuses on the audio call, not a full character controller.
A Single AudioStreamPlayer Cuts Itself Off on Rapid Jumps. max_polyphony Fixes That.
The default AudioStreamPlayer in Godot 4 has a
max_polyphony of 1. That means
it can play one instance of its stream at a time, and when
play() is called again while the first instance is
still running, the new call cuts off the previous one. A Godot
audio reference states the behaviour directly: “A single
player cuts itself off when retriggered”
.
For a platformer jump, this produces an audible stutter when
the player taps jump twice in quick succession. The first jump
sound is cut mid-tail, and the second begins immediately,
which reads as one broken sound rather than two jumps. The
same reference gives the fix: for overlapping SFX, either
raise max_polyphony or spawn a throwaway player
that frees itself when finished
. Godot’s own
documentation confirms that max_polyphony controls
how many sounds the node can play at the same time, and that
when the limit is exceeded, the oldest sound is cut
.
A practical setting for a jump is max_polyphony = 3.
That allows up to three overlapping jump sounds, which covers
rapid inputs without allowing an unbounded stack. Setting it
to a very high number is rarely necessary and can mask a
different problem: if the jump is firing far more often than
the player is pressing the button, the issue is the trigger
condition, not the polyphony limit.
@onready var jump_player: AudioStreamPlayer = $JumpPlayer
func _ready() -> void:
jump_player.max_polyphony = 3
The alternative is a small pool of players, which is more
code but gives finer control over which instance is reused.
For a single jump sound, max_polyphony is the
simpler correct answer. The
sound variation system guide
covers pooling at a larger scale; the jump-specific point is
that one player with a polyphony limit is sufficient until the
game has many simultaneous sound sources.
Pitch Randomisation: randf_range on pitch_scale vs AudioStreamRandomPitch
Godot 4 offers two ways to vary a jump sound’s pitch, and they
are not equivalent. The first is to set
pitch_scale on the player before calling
play(), using a random range. A common pattern
from Godot audio guidance is randf_range(0.95,
1.05), described as “slight variation avoids fatigue”
. Another example uses
randf_range(0.9, 1.1) for a jump
.
The second is AudioStreamRandomPitch, an
AudioStream resource that wraps another stream and
randomly varies the pitch on each start
. Its
random_pitch property has a default of
1.1, and the pitch is chosen between
1.0 / random_pitch and random_pitch
. A value of 1.0 means no variation; a value of 2.0 means the
pitch varies between half and double
.
| Approach | Where the variation lives | When it is the right choice |
|---|---|---|
pitch_scale = randf_range(...) |
In code, on the player | When you want the variation to be controllable per call, or to combine it with other logic |
AudioStreamRandomPitch |
In the resource, applied automatically on each play | When the variation is a fixed property of the asset and you do not want to remember to set it in code |
The practical difference is where the randomisation is
applied. pitch_scale multiplies the playback rate
and also changes the duration, because it is a playback-speed
change. AudioStreamRandomPitch is a stream-level
wrapper, and its effect is the same kind of pitch shift. Both
will shorten the sound as they raise the pitch, which matters
for a jump because a shorter tail can be an advantage — it
prevents overlap with the landing — but also because a very
wide range can make the jump feel inconsistent.
The safe range for a jump is narrow. randf_range(0.95,
1.05) is roughly ±5%, or about ±0.85 semitones, which is
below the threshold where the pitch change becomes the
dominant percept. randf_range(0.9, 1.1) is
roughly ±1.7 semitones, which is still usable for a jump but
starts to be noticeable as a different sound rather than a
variation. A random_pitch of 1.1 on
AudioStreamRandomPitch gives the same
±1.7-semitone range. Values above that tend to make the jump
read as unstable rather than varied.
Coyote Time and Jump Buffering Move the Execution Frame. The Sound Has to Move With It.
Coyote Time allows the player to jump for a short window after leaving a ledge. Jump buffering allows a jump press slightly before landing to be remembered and executed on contact. Both are standard platformer forgiveness mechanics, and both change the moment the jump executes relative to the moment the player presses the button.
Common values from Godot 4 platformer implementations are Coyote Time of 0.15 seconds and jump buffering of 0.10 seconds . A frame-perfect implementation may express these as a number of frames instead of seconds, but the principle is the same: the input is remembered for a short window, and the jump executes on the first frame where the condition is satisfied.
@export var coyote_time: float = 0.15
@export var jump_buffer: float = 0.10
var _coyote_timer: float = 0.0
var _jump_buffer_timer: float = 0.0
func _physics_process(delta: float) -> void:
_coyote_timer -= delta
_jump_buffer_timer -= delta
if is_on_floor():
_coyote_timer = coyote_time
if Input.is_action_just_pressed("jump"):
_jump_buffer_timer = jump_buffer
if _jump_buffer_timer > 0.0 and _coyote_timer > 0.0:
_perform_jump()
func _perform_jump() -> void:
velocity.y = -400.0
_coyote_timer = 0.0
_jump_buffer_timer = 0.0
jump_player.pitch_scale = randf_range(0.95, 1.05)
jump_player.play()
The critical placement is the sound call. If the sound is played when the input is pressed rather than when the jump executes, a buffered jump will play its sound before the character leaves the ground. The player hears a jump, then sees the character still falling for a few frames before the actual jump. That is a different disconnect from a late sound, and it is caused by the same separation between input time and execution time.
The sound should be called inside _perform_jump(),
after the velocity is set and the timers are cleared. At that
point the jump is committed, and the sound is the confirmation
of the committed action, not the input that may or may not
become a jump.
The Unity 2D jump setup guide
covers the same execution-versus-input distinction in a
different engine; the Godot version adds that
_physics_process() is the correct loop for both
the timer and the sound, because the jump execution is a
physics decision.
A Working Godot 4 Setup, in One View
The table below is the audio-relevant configuration, not a full controller. Each row is a decision that changes whether the jump sound fires correctly.
| Decision | Setting | Why |
|---|---|---|
| Player node | AudioStreamPlayer (non-positional) |
The jump sound belongs to the player character, not to a world position; positional audio would pan it with the camera |
| Polyphony | max_polyphony = 3 |
Prevents rapid jumps from cutting off each other; three is enough for a platformer jump |
| Pitch variation | pitch_scale = randf_range(0.95, 1.05), or AudioStreamRandomPitch with random_pitch = 1.1 |
Breaks exact repetition without making the jump read as a different sound |
| Sound call | Inside _perform_jump(), after velocity is set |
Ensures the sound fires on execution, not on input, so buffered jumps do not play early |
| Coyote Time | 0.15 s; trigger condition includes _coyote_timer > 0 |
A Coyote Time jump that succeeds silently is a trigger bug, not a physics bug |
| Jump buffer | 0.10 s; sound fires when the buffer executes |
A buffered jump that plays its sound on press will sound early relative to the character leaving the ground |
A jump sound that fires once per committed jump, with enough polyphony to overlap, a narrow pitch variation, and the call placed inside the execution branch, has no structural reason to feel disconnected. The remaining question — whether the jump clip itself reads as a jump rather than as a blip or a power-up — is a sound-design decision, and the four-parameter jump diagnosis covers the cases where the timing is correct but the sound is still weak.