Quick answer: Set floor_snap_length to 4–8 pixels (or large enough to cover one frame of movement), make sure floor_stop_on_slope is set appropriately, and verify up_direction = Vector2.UP. Without snap length, gravity pulls the body off every downward slope.

Here is how to fix Godot CharacterBody2D floor snap not working. You build a platformer, put in a few slopes, and the character bounces down them like a ball. On a shallow downhill, the character alternates between “on floor” and “in air” every frame, which ruins both the animation and any “on-ground” logic like footstep sounds or jump cooldowns. In Godot 4, the solution is one property you have probably never touched: floor_snap_length.

The Symptom

Moving down a slope, the CharacterBody2D leaves and re-touches the ground each frame. is_on_floor() toggles true/false repeatedly. Animation state machine flickers between run and fall. Jumping from a slope produces a weaker jump because the body was not on the ground at the frame the jump button was processed. Sliding or jumping off the edge of a platform looks choppy as the body traces a stairstep pattern instead of a smooth arc.

In 3D, the equivalent problem with CharacterBody3D produces the same flicker. The fix is the same, just with a 3D up direction.

What Causes This

Gravity outpaces slope geometry. Each _physics_process frame, your code adds gravity to velocity.y, then calls move_and_slide(). On a downward slope, the body wants to move forward and down, but the geometry only descends gradually. The result is that the body ends the frame slightly above the slope surface — in the air — until the next frame when gravity pulls it back to contact.

Snap length is zero by default. floor_snap_length tells CharacterBody2D to cast a ray downward after each move and reposition the body to the floor if a surface is found within that distance. With the default of 0, there is no ray, no reposition, and the body leaves the ground. Set it to a small positive value (4–8 pixels) and snap pulls the body back to the slope each frame.

floor_max_angle excludes steep slopes. The default 45 degrees is too shallow for many platformers. A 50-degree slope counts as a wall, not a floor. is_on_floor() returns false on contact, and snap does not apply. Raise floor_max_angle to 60–75 degrees if your game has steeper walkable surfaces.

up_direction not set. If you rotate your world (top-down to side-scroller transitions, gravity flips), you may have set up_direction to something non-default. If it is Vector2.ZERO, move_and_slide treats the body as not having a defined floor at all, and snap has no direction to work in.

Jumping re-engages snap. If you manually set velocity.y to a negative value (upward) for a jump, snap correctly disables itself — good. But if your code sets velocity.y = jump_power while floor_snap_length is also being set each frame, the ordering matters. Set snap properties once in _ready, not every frame.

The Fix

Step 1: Set snap length in the inspector. Select your CharacterBody2D. In the Inspector under “Floor,” set Floor Snap Length to 4 (pixels). Also verify:

After setting these, run your game and walk across every slope type. The bounce should be gone.

Step 2: Verify in code. A minimal CharacterBody2D controller:

extends CharacterBody2D

const SPEED = 200.0
const JUMP_VELOCITY = -350.0
const GRAVITY = 980.0

func _ready():
    floor_snap_length = 4.0
    floor_max_angle = deg_to_rad(60)
    up_direction = Vector2.UP

func _physics_process(delta):
    if not is_on_floor():
        velocity.y += GRAVITY * delta

    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = JUMP_VELOCITY

    var direction = Input.get_axis("move_left", "move_right")
    velocity.x = direction * SPEED

    move_and_slide()

Setting floor_snap_length and up_direction in _ready is idiomatic. Setting them in _physics_process is wasteful but not wrong — they are just properties.

Step 3: Tune snap length to movement speed. If your character moves 4 pixels per physics frame, snap length of 4 is minimal. If it moves 10 pixels (fast run), snap of 10–12 is needed. Undersized snap produces occasional bounces at high speed; oversized snap can pull the body down onto ceilings of pits you intended to walk off.

The rule of thumb: floor_snap_length > max_horizontal_speed * physics_fps, with some margin. For a 200 px/s character at 60 physics FPS, 3.3 px/frame + margin = 5.

Step 4: Handle moving platforms. If your character rides moving platforms, set platform_on_leave to PLATFORM_ON_LEAVE_ADD_VELOCITY so the character inherits platform velocity when jumping off. Otherwise the character appears to lag behind the platform’s motion at the moment of jump.

Preventing Slope Slide When Idle

Snap works at movement, but when the character is idle on a slope, it may gradually slide down because gravity still applies. Enable Floor Stop On Slope to freeze the character on slopes when not moving. For slopes steeper than floor_max_angle, the character is considered on a wall and will slide anyway — by design.

Debugging Snap Behavior

Add a visual debug to confirm what the body is doing each frame:

func _physics_process(delta):
    # ... movement code ...
    move_and_slide()
    $StateLabel.text = "floor=%s, vel=%s" % [is_on_floor(), velocity]

If the label alternates true / false every frame while walking down a slope, snap is not engaging. Double-check the inspector values and that nothing in your code overrides them after _ready.

“Floor snap is not a nice-to-have for platformers. It is the difference between smooth movement and a character that jitters down every hill.”

Related Issues

For navigation-related fixes, see Godot Navigation Region Not Connecting. For 3D character controllers, the same principles apply to CharacterBody3D with up_direction = Vector3.UP.

Snap 4, max angle 60, stop on slope on. Three switches, one smooth platformer.