Post-7 listed eight possible causes for the directional landing stick, but some of the analysis was wrong. After re-reading the code with fresh eyes, here is what is actually happening &mdash and what to do about it.

What Was Wrong With Post-7

Several suggestions targeted problems that don't exist in this codebase:

What Is Actually Happening

The real issue is simpler than the list in post-7 suggested. It comes down to one interaction:

When the player is moving and lands, the sequence is:

  1. OnCollisionStay2D sets isGrounded = true.
  2. On the next Update(), move is still non-zero (player is still holding the key).
  3. Line 177 executes: rb.linearVelocity = new Vector2(move * speed, rb.linearVelocity.y).
  4. This sets horizontal velocity to full speed while the character is already in contact with the ground.
  5. But — and this is the key — if the landing involved any vertical bounce or micro-oscillation, the rigidbody may have been repositioned by the physics solver in a way that leaves it slightly intersecting the ground collider.
  6. Unity's collision resolution pushes the rigidbody out and nullifies velocity components that would cause further penetration. This nullification can include the horizontal component, especially if the contact normal has any sideways angle.
  7. The character is now grounded with zero (or near-zero) horizontal velocity. The next frame, move is still held, so line 177 runs again — but the same collision state may persist for another frame, and the cycle repeats.

This explains why:

The Fix

The simplest fix is to not overwrite velocity on the first grounded frame after being airborne. This gives the collision solver one frame to settle before accepting new input:

// Add these fields (around line 32):
private bool wasGroundedLastFrame;

// Replace lines 172-185 with:
if (!isWallJumping)
{
    if (move != 0f)
    {
        rb.linearVelocity = new Vector2(move * speed, rb.linearVelocity.y);
    }
    else
    {
        float newVelX = Mathf.MoveTowards(rb.linearVelocity.x, 0f, 10f * Time.deltaTime);
        rb.linearVelocity = new Vector2(newVelX, rb.linearVelocity.y);
    }
}

// Add this at the end of Update(), after line 307 (before the closing brace):
wasGroundedLastFrame = isGrounded;

Actually — that's still not right. The problem isn't the first grounded frame. The problem is that isGrounded can flicker between true and false when the rigidbody is micro-bouncing on the surface. Each flicker gives the physics solver a chance to nullify velocity.

The real fix is to clamp the horizontal velocity instead of overwriting it, and let the movement code only add to existing velocity rather than replacing it entirely:

// Replace lines 172-185:
if (!isWallJumping)
{
    if (move != 0f)
    {
        // Only set speed if within range, don't overwrite existing momentum
        if (Mathf.Abs(rb.linearVelocity.x) < speed)
        {
            rb.linearVelocity = new Vector2(move * speed, rb.linearVelocity.y);
        }
        // If already faster than max speed, leave it (don't fight the physics solver)
    }
    else
    {
        float newVelX = Mathf.MoveTowards(rb.linearVelocity.x, 0f, 10f * Time.deltaTime);
        rb.linearVelocity = new Vector2(newVelX, rb.linearVelocity.y);
    }
}

But honestly, the cleanest fix is even simpler. Remove the friction patch entirely and go back to the original instant-velocity behavior, but add one guard: skip velocity overwrite when isGrounded just flipped true:

// Fields (around line 32):
private bool wasGroundedLastFrame;

// Lines 172-185:
if (!isWallJumping)
{
    if (move != 0f)
    {
        rb.linearVelocity = new Vector2(move * speed, rb.linearVelocity.y);
    }
    else
    {
        rb.linearVelocity = new Vector2(0f, rb.linearVelocity.y);
    }
}

// End of Update():
if (isGrounded && !wasGroundedLastFrame)
{
    rb.linearVelocity = new Vector2(rb.linearVelocity.x * 0.8f, rb.linearVelocity.y);
}
wasGroundedLastFrame = isGrounded;

This applies a small velocity dampener on the exact frame landing is detected, which absorbs the micro-bounce without killing horizontal momentum. The 0.8f multiplier reduces velocity by 20% on the landing frame — enough to prevent the physics solver from nullifying it, but not enough to feel like a hard stop.

Why Post-7 Was Wrong

The earlier post treated each suggestion as equally plausible without verifying which ones actually applied to the code as it exists. It mixed up symptoms (friction slowing the player) with causes (collision state nullifying velocity). It also suggested editor-level fixes for problems that are entirely in the script — the character collider size and physics material have nothing to do with velocity overwrite logic that runs every frame regardless.

The actual fix is in the script. The issue is that line 177's unconditional velocity overwrite fights the physics solver on landing frames. The dampener at the end of Update() absorbs the landing energy before the solver can react.