Godot: stop_on_slope doesn't seem to work in version 3.2 beta 3

Created on 5 Dec 2019  路  11Comments  路  Source: godotengine/godot

Godot version:
Godot 3.2 Beta 3
OS/device including version:
Windows 10
Issue description:
The function move_and_slde() in Kinematic Body 3D, the stop_on_slope parameter seems to have stopped working after I upgraded from Godot 3.2b2 to 3.2b3. (It only happens in 3.2b3)
Steps to reproduce:
Open the project I gave, press play, go to a slope, if you go up and stop everything should be ok, but if you go down even slightly you will be dragged to the bottom. It happens only in 3.2b3
Minimal reproduction project:
FirstPersonStarter 1.03
Additional:
I don't know if this is a bug but even before, even at a slight velocity, sort of 0.001 you get pulled down instead of stopping on a slope with stop_on_slope parameter true.

archived bug discussion physics

Most helpful comment

As noted by @bruvzg, move_and_slide() and move_and_slide_with_snap were fixed with #33864. The 750f343e4a commit contained in #33864 has not been applied to 3.1.2; so it still behaves in the old, incorrect way.

As mentioned by @oeleo1, the current code is per the specification. The move_and_slide() function:

Returns the linear_velocity vector, rotated and/or scaled if a slide collision occurred.

This ensures that the velocity is modified correctly by the slide and the KinematicBody (with a gravity adjustment) incurs more effort going up slopes than it does going down.

The old, incorrect way was returning a vector perpendicular to the floor_normal vector passed to the move_and_slide() and move_and_slide_with_snap() functions; instead of returning the updated velocity as modified by the slide. Since most people set the floor_normal to be in the negative y direction, the old, incorrect way resulted in the y component of the returned vector always being equal to 0, and the x and z components not being modified proportional to the steepness of the slope.

As described in the link provided by @oeleo1 , if the old way is desired, a RayShape can be added to the KinematicBody pointing down. The collision vector returned by the RayShape is always in the opposite direction of the RayShape and not in the direction of the collision surface normal. With a RayShape pointing down, the vector returned by the move_and_slide() and move_and_slide_with_snap() functions will be perpendicular to the negative RayShape i.e. have a zero y component, and the x and z components will not being modified proportional to the steepness of the slope.

Finally, looking at the example project provided, as far as I can tell, the problem appears to be the player sliding down the slope when the player tries to stop when moving down the slope. The player stops when moving up the slope as expected. Looking at the player controller, the velocity in the y direction is modified to simulate gravity (exponentially increased downwards: velocity.y += -gravity * delta), while the velocity in the x and z directions are accelerated to a target velocity and clamped to 0 when it drops below 0.25:

    if direction.dot(temp_vel) > 0:
        temp_accel = acceleration 
    else:
        temp_accel = deacceleration
    # interpolation and clamping
    temp_vel = temp_vel.linear_interpolate(target, temp_accel * delta)
    if !direction.dot(temp_vel) > 0:
        if temp_vel.x < 0.25 && temp_vel.x > -0.25:
            temp_vel.x = 0
        if temp_vel.z < 0.25 && temp_vel.z > -0.25:
            temp_vel.z = 0

When moving down the slope the residual velocity down the slope is increased through vector addition by the "gravity" in the y direction which is then further increased by the move_and_slide down the slope (when moving up the slope the velocity is decreased). The deceleration in the next iteration is not enough to be caught by the clamp and the player KinematicBody slides down the slope. Similarly, the residual velocity is too great to be caught by the stop_on_slope. To stop it sliding down the slope when stopping after moving down the slope, try increasing the clamp minimum velocity to 1:

    temp_vel = temp_vel.linear_interpolate(target, temp_accel * delta)
    if !direction.dot(temp_vel) > 0:
        if temp_vel.x < 1 && temp_vel.x > -1:
            temp_vel.x = 0
        if temp_vel.z < 1 && temp_vel.z > -1:
            temp_vel.z = 0

All 11 comments

Behaviours of move_and_slide and co. were changed in beta3, see #33864.

My feedback: after some testing I believe the current code is right.

3.2b3 broke my code indeed compared to 3.2b2 as my character started sliding where is used to stop on/snap to the slope. However, I was using the code the wrong way in the first place because it didn't work the way it was supposed to. I used the approach described by @reduz here by adding a ray to the kinematic body, then after some trial and error ended up using this sequence:

const SNAP = 32
const DEG_90 = deg2rad(90)
const FLOOR_NORMAL= Vector2(0, -1)
...
snap = Vector2(0, 0 if jumping else SNAP)
velocity = move_and_slide_with_snap(velocity, FLOOR_NORMAL, snap, true,  1, DEG_90)

The code worked as wanted in 3.2.b2 but doesn't any longer in 3.2.b3.
However this code is not as per the spec of move_and_slide_with_snap() because the FLOOR_NORMAL and the snap parameters are reversed. Since I wrote the code I left it like this since it worked for some reason which is beyond me. Now I reverted the two parameters as per spec:

snap = Vector2(0, 0 if jumping else SNAP)
velocity = move_and_slide_with_snap(velocity, snap, FLOOR_NORMAL, true,  1, DEG_90)

and it works as expected in 3.2.b3, while it doesn't in 3.2b2. I am therefore happily changing the code to match the spec and I hereby report that for me it works.

So whoever complains things don't work as before (like I was about to do :) shall double-check first the parameters of move_and_slide() and move_and_slide_with_snap() to make sure they are per spec.

I confirm that bug, my project also stop to work in 3.2 beta 3 and previously it works in 3.2 beta 2

How does it behave in 3.1.2?

CC @madmiraal

As noted by @bruvzg, move_and_slide() and move_and_slide_with_snap were fixed with #33864. The 750f343e4a commit contained in #33864 has not been applied to 3.1.2; so it still behaves in the old, incorrect way.

As mentioned by @oeleo1, the current code is per the specification. The move_and_slide() function:

Returns the linear_velocity vector, rotated and/or scaled if a slide collision occurred.

This ensures that the velocity is modified correctly by the slide and the KinematicBody (with a gravity adjustment) incurs more effort going up slopes than it does going down.

The old, incorrect way was returning a vector perpendicular to the floor_normal vector passed to the move_and_slide() and move_and_slide_with_snap() functions; instead of returning the updated velocity as modified by the slide. Since most people set the floor_normal to be in the negative y direction, the old, incorrect way resulted in the y component of the returned vector always being equal to 0, and the x and z components not being modified proportional to the steepness of the slope.

As described in the link provided by @oeleo1 , if the old way is desired, a RayShape can be added to the KinematicBody pointing down. The collision vector returned by the RayShape is always in the opposite direction of the RayShape and not in the direction of the collision surface normal. With a RayShape pointing down, the vector returned by the move_and_slide() and move_and_slide_with_snap() functions will be perpendicular to the negative RayShape i.e. have a zero y component, and the x and z components will not being modified proportional to the steepness of the slope.

Finally, looking at the example project provided, as far as I can tell, the problem appears to be the player sliding down the slope when the player tries to stop when moving down the slope. The player stops when moving up the slope as expected. Looking at the player controller, the velocity in the y direction is modified to simulate gravity (exponentially increased downwards: velocity.y += -gravity * delta), while the velocity in the x and z directions are accelerated to a target velocity and clamped to 0 when it drops below 0.25:

    if direction.dot(temp_vel) > 0:
        temp_accel = acceleration 
    else:
        temp_accel = deacceleration
    # interpolation and clamping
    temp_vel = temp_vel.linear_interpolate(target, temp_accel * delta)
    if !direction.dot(temp_vel) > 0:
        if temp_vel.x < 0.25 && temp_vel.x > -0.25:
            temp_vel.x = 0
        if temp_vel.z < 0.25 && temp_vel.z > -0.25:
            temp_vel.z = 0

When moving down the slope the residual velocity down the slope is increased through vector addition by the "gravity" in the y direction which is then further increased by the move_and_slide down the slope (when moving up the slope the velocity is decreased). The deceleration in the next iteration is not enough to be caught by the clamp and the player KinematicBody slides down the slope. Similarly, the residual velocity is too great to be caught by the stop_on_slope. To stop it sliding down the slope when stopping after moving down the slope, try increasing the clamp minimum velocity to 1:

    temp_vel = temp_vel.linear_interpolate(target, temp_accel * delta)
    if !direction.dot(temp_vel) > 0:
        if temp_vel.x < 1 && temp_vel.x > -1:
            temp_vel.x = 0
        if temp_vel.z < 1 && temp_vel.z > -1:
            temp_vel.z = 0

Fixed it by doing what @madmiraal said, + clamping the velocity.y to not go below -0.5 while on a slope. To be smoother while walking I made it so it works only when being on slopes. And I changed move_and_slide to move_and_slide_with_snap

Here is some of the code if anyone has trouble:

# Clamping to not slide down on slopes
# getting floor_angle by converting radians to degrees the collision from the raycast
var floor_angle: float = rad2deg(acos(raycast.get_collision_normal().dot(Vector3(0, 1, 0))))
if floor_angle > 1: # Do only on slopes
    if !direction.dot(velocity) > 0:
        if velocity.x < 1 && velocity.x > -1:
            velocity.x = 0
        if velocity.z < 1 && velocity.z > -1:
            velocity.z = 0
    if grounded && velocity.y < -0.5:
        velocity.y = -0.5

@madmiraal Shouldn't the docs be updated? Because it says

"If stop_on_slope is true, body will not slide on slopes if you include gravity in linear_velocity."

Which is not the case anymore.

@vickylance Good spot. The current documentation for 2D and 3D are different. The 2D version states:

If stop_on_slope is true, body will not slide on slopes when you include gravity in linear_velocity and the body is standing still.

@madmiraal

As described in the link provided by @oeleo1 , if the old way is desired, a RayShape can be added to the KinematicBody pointing down.

If I'm not misunderstanding it, using a ray shape like that means narrowing the feet of the player to a thin collision area that fits through any tiny gap on the ground, making it possible for the player to fall halfway through (until the capsule collides), and means the player will have less ground to walk on near ledges or on small platforms (which will make the sprite's width not be an accurate/reliable visual way for the player to tell where to jump on a ledge), and also means the player can fall halfway down ledges as well (leaving the character hanging on the capsule).

I played around with several setups a while ago for a first person character controller and found that one to be among the least desirable. It worked flawlessly on slopes, that's for sure (and on stairways, iirc), but those problems were glaring red flags. Including for 2D, I presume.

On a related note, if a player can crouch, that setup can affect the steepness of slopes the player can climb while crouching, if the bottom of the capsule shape has to be lowered. And if the player can prone (crawl lied down on the floor), it will probably be even more problematic.

@madmiraal indeed I am! Thanks.

Was this page helpful?
0 / 5 - 0 ratings