Quick answer: Raise max_slides from the default 4 to 6. Set wall_min_slide_angle around 0.3 radians. Disable slide_on_ceiling for most platformers. Apply horizontal velocity only, not a pre-rotated diagonal, and let move_and_slide resolve the slide direction.
Here is how to fix Godot CharacterBody3D wall slide jitter. Your player pushes into a wall and instead of sliding smoothly along it, the character vibrates or stutters. Sometimes it bounces between two nearby walls in a corner. Sometimes the camera follower shows microshake while the body looks stable. Godot’s built-in move_and_slide is well-tuned for simple cases but has three knobs that interact in non-obvious ways when walls are close or angled.
The Symptom
Pressing into a wall with diagonal input makes the character shiver along the contact. Moving into a concave corner produces a visible bounce. Sloped walls alternate between “treated as floor” and “treated as wall” from frame to frame, snapping the player’s position.
Variant: is_on_wall() toggles true/false every other frame during a slide, breaking animation state machines keyed to wall contact.
What Causes This
max_slides too low. Each call to move_and_slide runs a fixed number of collision resolution iterations (max_slides, default 4). In a corner where the resolved direction bounces off multiple surfaces, 4 is not enough to converge. The solver leaves residual motion, which the next frame tries to resolve — producing oscillation.
wall_min_slide_angle misclassification. Surfaces between vertical and the min-slide angle are treated as walls. Below it, they are treated as floors or ceilings. A sloped surface near the threshold flips classification based on minor normal fluctuation.
slide_on_ceiling interactions. With slide_on_ceiling = true, a character grazing a ceiling slides horizontally. With it off, ceiling contact stops vertical motion. Near overhangs the classification can flip between wall and ceiling.
Velocity recomputed after collision. A common bug: after move_and_slide, user code zeroes or overwrites velocity based on input. Next frame pushes the same velocity back into the wall, producing a step pattern.
Physics ticks vs render ticks. Camera following in _process interpolates between physics states. Without interpolation on the body, the camera shows physics-frame jitter amplified.
The Fix
Step 1: Tune slide parameters.
extends CharacterBody3D
func _ready():
max_slides = 6
wall_min_slide_angle = deg_to_rad(15)
slide_on_ceiling = false
floor_max_angle = deg_to_rad(45)
max_slides = 6 gives the solver enough passes to converge in sharp corners without measurable cost. Values above 8 rarely help and add compute.
wall_min_slide_angle around 15 degrees (0.26 rad) means anything within 15 degrees of vertical acts as a wall. Below that, it becomes a floor. Pick a value that matches your level geometry — steep ramps should be “floors,” near-vertical cliffs should be “walls.”
Step 2: Preserve velocity after move_and_slide. Do not overwrite velocity from input every frame. Accumulate input, then call move_and_slide, and let it return the actual resolved velocity:
func _physics_process(delta):
var input_dir = Input.get_vector("left", "right", "fwd", "back")
var target = Vector3(input_dir.x, 0, input_dir.y) * SPEED
velocity.x = move_toward(velocity.x, target.x, ACCEL * delta)
velocity.z = move_toward(velocity.z, target.z, ACCEL * delta)
velocity.y -= GRAVITY * delta
move_and_slide()
After move_and_slide, velocity reflects post-collision motion. Let it ride into the next frame rather than recomputing from raw input.
Step 3: Disable slide_on_ceiling for platformers. Jumping into an overhang with slide-on-ceiling enabled makes the character skim along the ceiling, then drop. With it disabled, the character’s upward velocity clears on contact, which feels natural for Mario-style games.
Step 4: Raise solver_iterations for precision. For physics-heavy scenes, tune the Project Settings > Physics > 3D > Solver Iterations value. Default 16 is fine for most games; increase to 24 if you still see residual jitter in tight spaces.
Debug with get_slide_collision
To inspect what the solver actually touched:
for i in get_slide_collision_count():
var c = get_slide_collision(i)
print("hit ", c.get_collider(), " normal ", c.get_normal())
If the same body appears multiple times per frame with flipping normals, the solver is bouncing. Raise max_slides or simplify the collision geometry.
Corner Case: Two Walls at Acute Angle
In a 45-degree corner, the naive solve picks one wall, slides, hits the other, slides back, repeat. The fix is projected input: detect wall normals and flatten input into the wedge direction rather than letting the solver fight:
if is_on_wall():
var n = get_wall_normal()
velocity = velocity.slide(n)
move_and_slide()
This pre-projects velocity parallel to the dominant wall, reducing solver work and eliminating the bounce pattern.
“Four slides is not enough for a corner. Six is usually plenty.”
Related Issues
For related physics tuning, see Area2D Not Detecting StaticBody2D. For camera jitter that compounds with physics jitter, Viewport Mouse Position Offset shares root-cause territory.
max_slides 6, slide_on_ceiling off, preserve velocity. Smooth slides, no shake.