• 3D
  • The player (kinematic body) continues to slide down the slope after I have stopped moving it

Hello! My player, which is a kinematic body, continues to slide down the slope after I have stopped moving it. For fixing this, I applied two tactics:

  1. Its movement is set in such a way, that when the player is on the ground (doesn't jump), gravity is applied only when the floor angle is greater than 45 degrees. Here is an image with the code:

For this, I have followed the instructions from the following tutorial:

This tactic slows down the sliding considerably, but doesn't stop.

  1. Also, I have set parameter "stop_on_slope" of the function "move_and_slide" to be true.

This tactic stops the sliding after 5 to 10 seconds. But this is not the desirable result.

I have heard that in the version 3.2 of Godot, the parameter "stop_on_slope" no longer works, but what workarounds are there for this?

16 days later

In one of my prototypes, I just stop applying gravity if "is_on_floor()" is true, and that seems to work.

You could also try using a ray shape for the bottom bit of the player's collision. This prevents the player from sliding period.

This isn't perfect, but the best solution I've found:

var floorAngle = acos(floorNormal.dot(Vector3.UP))
move_and_slide(velocity,Vector3.UP,true,4,clamp(floorAngle,0,maxSlopeAngle),false)

For some reason, setting the maximum slope angle to the angle of the slope the player is standing on helps. I used this for now, but I don't think it's good enough. Maybe there's some way to tweak it to work better, maybe limiting the vertical velocity with raycasts....

I had the same issue and managed to solve it using a workaround. EDIT I'm now using a different, better solution (in my opinion), see further down

The problem is the maxSlopeAngle does not work when using velocity interpolation to slow the movement gradually. Why? Because the slide only stops if the horizontal velocity is smaller than 0.01 (looked into the C++ code). So when reducing the velocity gradually, the gravity will always create a downward movement and make a horizontal movement greater than 0.01. If you set your horizontal velocity manually to 0, it will work (but then you lose the movement smoothing).

To fix that, I split the horizontal and vertical movement:

    	var is_on_floor #my own is_on_floor variable, since is_on_floor() won't work
    	
    	#horizontal direction at max speed, 0 if stopping
    	var target_velocity = input_direction * target_speed
    	
    	#interpolating horizontal velocity
    	velocity.y = 0.0
    	velocity = velocity.linear_interpolate(target_velocity , acceleration * delta)

    	velocity = move_and_slide(velocity, Vector3.UP, true, 4, deg2rad(max_slope_angle))
    	
    	#apply gravity only, hack to fix issue with move and slide (sliding down slopes)
    	var result = move_and_collide(Vector3(0.0, gravity * delta, 0.0), true, true, true)
    	if result != null:
    		global_transform.origin.y += result.travel.y
    		is_on_floor = true
			fall_speed = 0.0
    	#no collision detected, freefall
    	else:
    		is_on_floor = false
			fall_speed += gravity * delta
			global_transform.origin.y += fall_speed

Obviously the problem is I call two times the physics engine instead of once, but it seems to work, so that's good enough for now, while waiting for a fix.

So you only apply gravity when the player is falling? Does your fix work with snapping?

Yes, gravity is applied when falling (result = null means there is nothing under the player). For snapping, I don't know, I never used move_and_slide_with_snap, not sure what it does exactly.

The function moves the body, but snaps it to the closest floor. That helps preventing the character from bouncing down a hill for example, they would stay glued to it. It's best to disable snapping when already in the air though, so setting it's snap property to a zero length vector when in the air should do. :)

The video ”An example of why Godot is (currently) bad at 3D” only demonstrates the problem, but, unfortunately, it doesn't provide a solution.

Yes, I also apply gravity only in the following cases: "is_on_floor" is true, but floor_angle > MAX_FLOOR_ANGLE. "is_on_floor" is false.

Yes, I use a Raycast. In the image that I posted above, "Tail" is the Raycast.

I also tried to deactivate gravity when not moving first, but I had issue with monsters sometimes flying when attacking me.

My new solution with always applying gravity works, but now monsters slide when the slope is high, when they shouldn't. So I'm still trying to find a better solution.

Regarding this solution:

var floorAngle = acos(floorNormal.dot(Vector3.UP))
move_and_slide(velocity,Vector3.UP,true,4,clamp(floorAngle,0,maxSlopeAngle),false)

I use the following code, which resembles the code above, but with fewer parameters used at "move_and_slide:

var floor_angle = rad2deg(acos(floor_normal.dot(Vector3(0, 1, 0))))
if floor_angle > MAX_SLOPE_ANGLE:
			velocity.y += gravity * delta
velocity = move_and_slide(velocity, Vector3(0, 1, 0), true)

I tried both solutions, but a did't notice any difference in Player's behaviour. Basically, they act the same way: they allow the kinematic body to slide with very low speed for a few seconds on the slope, after which they stop it.

Interesting, I will try this solution, might work better.

I found a new way to apply gravity and stop sliding, by manipulating the gravity vector. If you set the gravity towards the floor normal, the movement is stopped. So I can make my creatures move and stop on slopes by interpolating this gravity vector.

Here is the code:

	if is_on_floor():
		#horizontal direction at max speed, 0 if stopping
		var target_velocity = input_direction * max_speed
		#add gravity towards the floor
		target_velocity += -get_floor_normal() * gravity
		#move the current velocity towards this target velocity
		velocity = velocity.linear_interpolate(target_velocity, acceleration * delta)
	else:
		#falling, accelerate down
		velocity.y += gravity * delta

	move_and_slide(velocity, Vector3.UP)

@joel127 , regarding your solution with splitting the horizontal and vertical movement, can you explain me, please, the following part of your code?

var hvel = velocity
hvel.y = 0.0
hvel = hvel.linear_interpolate(vel_max_speed, acceleration * delta)

Is "val_max_speed" a Vector3() variable? And is it like a "target" variable? In my controller:

var target = direction * speed

where "direction" is a Vector3() and "speed" is a float constant.

I am asking because in my code, MAX_SPEED is float constant, and in the "linear_interpolate()" function, it cannot be put a float variable as the first parameter. The first parameter can be only a Vector3() variable, a Vector2() or a Color.

yes, you are guessing right, sorry my code is poorly explained (code updated).

vel_max_speed is the velocity vector we are trying to reach. It is the input direction in the world multiplied by the max speed allowed. When stopping, this vector will be equal to Vector3(0,0,0).

This code is working well, but the KinematicBody is still sliding down when the slope is too high, I don't really understand why. So now I am using my second solution that seems to work very well in all situations.

5 days later

The relevant part of his code:

# clamping (to stop on slopes)
if direction.dot(velocity) == 0:
	var _vel_clamp := 0.25
	if velocity.x < _vel_clamp and velocity.x > -_vel_clamp:
		velocity.x = 0
	if velocity.z < _vel_clamp and velocity.z > -_vel_clamp:
		velocity.z = 0

# Move
velocity.y = move_and_slide_with_snap(velocity, _snap, FLOOR_NORMAL, 
		true, 4, deg2rad(floor_max_angle)).y

FLOOR_NORMAL is a constant Vector3.UP.