Advertisement
Game Audio

Adding Platformer Jump Sound Effects in Unity 2D: Rigidbody, Coyote Time, and Audio Source Setup

A jump sound that fires at the wrong moment is worse than no sound. This covers where to call PlayOneShot relative to Rigidbody2D.velocity, how Coyote Time changes the trigger condition, and why Play() produces a stutter on rapid inputs.

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.

Advertisement