Adding a jump sound in Unity 2D looks like a one-line problem:
call AudioSource.PlayOneShot() when the player
presses the jump button. In practice, that line produces a
sound that fires when the character is already in the air, or
fires twice, or cuts itself off when the player taps jump
repeatedly. The cause is almost always the relationship
between three systems that are not synchronized by default:
the input check, the Rigidbody2D velocity
change, and the audio source that plays the clip.
The sections below work through the setup in the order the
calls actually execute: where the jump is detected, when the
velocity is set, what Coyote Time changes about the trigger
condition, and which AudioSource method matches
the frequency of a platformer jump. The code examples are
deliberately minimal; they show the placement of the audio
call relative to the physics call, not a full character
controller.
The Audio Call Has to Sit Between the Input Check and the Velocity Change
A jump sound that plays late is usually a jump sound that was
called after the physics step. In a typical 2D controller,
the input is read in Update() or in an
InputAction callback, and the velocity is set in
FixedUpdate() or immediately in the same
callback. The order of those calls determines whether the
sound arrives with the visual or after it.
The pattern that produces a synchronized sound is: check the
jump condition, play the sound, then set the velocity. The
sound is the confirmation of the input; the velocity change
is the physical result. If the sound is played after the
velocity is set, it still works in most cases, because both
happen in the same frame. The audible failure appears when the
sound is placed in a separate method that runs later, or when
it is triggered by a velocity condition
(rb.velocity.y > 0) rather than by the input
itself.
if (jumpAction.WasPressedThisFrame() && isGrounded)
{
jumpSound.PlayOneShot(jumpClip);
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
isGrounded = false;
}
A velocity check seems like a reasonable trigger — “play the jump sound when the character starts moving upward” — but it introduces a frame of delay and fires again if the character is pushed upward by anything else. The input check is the event; the velocity is the consequence. Triggering on the consequence is what makes a jump sound feel disconnected.
Use PlayOneShot, Not Play, Because a Platformer Jump Fires Before the Previous One Ends
The second setup decision is which AudioSource
method to call. Unity’s AudioSource has two
playback methods that look interchangeable and are not.
Play() plays the currently assigned
AudioClip on the source and, if the source is
already playing, restarts or overlaps depending on the
source’s configuration. PlayOneShot() plays a
clip as a one-off instance and does not interrupt anything
else on the same source.
For a jump that fires repeatedly, Play() produces
a specific artifact. If the player taps jump twice in quick
succession, the second Play() call can cut off
the first jump sound before it finishes, which reads as a
stutter rather than two jumps. A comparison of the two methods
on rapid input describes the result directly:
PlayOneShot lets the sounds overlap, while
Play “cuts off the previous sound and restarts,
producing a choppy result”.
PlayOneShot() is the correct choice for a jump
because a platformer jump is a short, frequent, one-shot
event. It does not need to loop, it does not need to be
stopped by code, and it should be allowed to overlap with the
next jump if the player is moving quickly. The same logic
applies to footsteps, UI clicks, and most combat feedback;
Play() is for music and sustained ambience,
where interrupting the previous instance is the desired
behavior.
A practical detail: PlayOneShot() accepts a
volume scale as a second argument, which is useful for
variation. A jump system that plays the same clip with a
volumeScale between 0.9 and 1.0 and a small
pitch offset will sound less repetitive than one that plays
the identical sample every time. The
sound variation system guide
covers the broader architecture; the Unity-specific point is
that PlayOneShot(clip, volumeScale) is the
cheapest place to start.
Coyote Time Changes the Trigger Condition, Not the Sound
Coyote Time is a forgiveness mechanic: it allows the player to jump for a short window after leaving a ledge, even though the character is no longer grounded. The window is usually small — implementations commonly use 0.1 to 0.2 seconds, and some go as high as 0.3 seconds.
The audio consequence is not that the sound should change. It
is that the condition that triggers the sound
changes. Without Coyote Time, the jump condition is
isGrounded. With Coyote Time, the condition
becomes isGrounded || coyoteCounter > 0. If
the sound is still tied to isGrounded alone, a
jump that succeeds through Coyote Time will be silent — the
character leaves the ground, the player hears nothing, and
the jump feels broken even though the physics worked.
[SerializeField, Range(0f, 0.3f)] private float coyoteTime = 0.2f;
private float coyoteCounter;
void Update()
{
if (isGrounded) coyoteCounter = coyoteTime;
else coyoteCounter -= Time.deltaTime;
if (jumpAction.WasPressedThisFrame() && coyoteCounter > 0f)
{
jumpSound.PlayOneShot(jumpClip);
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
coyoteCounter = 0f;
}
}
The same logic applies to jump buffering, which is the mirror image of Coyote Time: if the player presses jump slightly before landing, the input is remembered and the jump executes on contact. A jump buffer of roughly 0.1 seconds is a common starting value. If the sound is tied to the frame the button is pressed rather than the frame the jump executes, a buffered jump will play its sound early — before the character leaves the ground — which is a different kind of disconnect. The sound should play when the jump executes, not when the input is recorded.
The jump and landing guide covers the relationship between the two events; the Unity setup point is that Coyote Time and jump buffering both move the moment of execution away from the moment of input, and the audio call has to move with it.
Ground Detection Is a Separate Problem, and It Affects When the Sound Can Fire
The ground check determines whether a jump is allowed, and
therefore whether the jump sound fires. A common
implementation uses Physics2D.OverlapCircle at a
point below the character, with a small radius and a layer
mask that includes only the ground.
[SerializeField] private Transform groundCheck;
[SerializeField] private float groundCheckRadius = 0.2f;
[SerializeField] private LayerMask groundLayer;
bool isGrounded;
void FixedUpdate()
{
isGrounded = Physics2D.OverlapCircle(
groundCheck.position,
groundCheckRadius,
groundLayer
);
}
The radius is a tuning parameter, not a fixed value. A radius
that is too small will report false on uneven
ground and produce a silent jump; a radius that is too large
will report true while the character is already
falling, which allows a mid-air jump and plays the sound at
the wrong time. The 0.1–0.2 range is a
reasonable starting point for a character of typical
platformer scale, but it should be verified against the
actual collider size rather than copied from a tutorial.
A second ground-check issue is the update loop. If
isGrounded is set in FixedUpdate()
and the jump input is read in Update(), the
input can arrive on a frame where isGrounded is
stale. The result is an occasional missed jump and, with it,
a missed jump sound. The fix is to read the ground state and
the input in the same loop, or to use a coyote window that is
long enough to cover the gap between physics steps.
A Minimal Setup That Fires the Sound Exactly Once per Jump
The components below are the minimum needed to place the sound correctly. They are not a full controller; they isolate the audio-relevant decisions.
| Component | Setting | Why it matters for the sound |
|---|---|---|
AudioSource |
playOnAwake off; loop off |
The jump clip should only play when the code calls it, not when the scene loads |
AudioSource method |
PlayOneShot(jumpClip, 1f) |
Allows rapid jumps to overlap without cutting off the previous sound |
| Jump condition | coyoteCounter > 0f, not isGrounded alone |
A Coyote Time jump that succeeds silently is a sound that failed to trigger, not a physics failure |
| Call order | Sound before rb.velocity |
Keeps the confirmation and the motion in the same frame without a visible delay |
| Ground check radius | 0.1–0.2 units, matched to the collider |
Too small misses uneven ground; too large allows mid-air jumps that play the sound at the wrong time |
The table is a checklist for the audio placement, not a
complete character setup. A jump sound that fires once per
jump, at the moment the jump executes, with a
PlayOneShot call, is a jump sound that has no
structural reason to feel disconnected. The remaining work —
choosing the clip, matching it to the character’s weight,
adding variation — is the sound design, and the
four-parameter jump diagnosis
covers the cases where the timing is correct but the sound
still feels weak.