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

TudorTudor Posts: 27Member
in 3D

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?

Best Answers

  • TudorTudor Posts: 27
    edited May 19 Accepted Answer

    I finally found the solution:
    I use "move_and_slide_with_snap", thanks for the idea, guys :)

    When I am walking, the snap vector (second parameter of "move_and_slide_with_snap") is set to be the opposite of the floor_normal (the perpendicular vector of the surface the Raycast collides with).

    When I am jumping, the snap vector is set to null vector Vector3(0, 0, 0).

    var floor_normal = $Raycast.get_collision_normal()
    
    if has_contact and Input.is_action_just_pressed("jump "):
            velocity = move_and_slide_with_snap(velocity, Vector3(0, 0, 0), Vector3(0, 1, 0), true)
            velocity.y = jump_height
            has_contact = false
    
    velocity = move_and_slide_with_snap(velocity, -floor_normal, Vector3(0, 1, 0), true)
    

    This works for me. Now, the player stops on the slope definitively, until I move him again.

  • DschoonmakerDschoonmaker Posts: 260
    edited May 19 Accepted Answer

    Finally, a solution! I've also been looking for this for a long time.
    The main part is setting the snap vector to the inverted floor normal, although sometimes snap needs to be disabled(e.g. for jumping, @Tudor said he sets the snap vector to Vector3.ZERO. The same can be done by using move_and_slide() instead).

Answers

  • SaitodepaulaSaitodepaula Posts: 49Member

    Maybe it is related:

  • cyberealitycybereality Posts: 927Moderator

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

  • SIsilicon28SIsilicon28 Posts: 749Moderator

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

  • DschoonmakerDschoonmaker Posts: 260Member

    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....

  • joel127joel127 Posts: 18Member
    edited April 30

    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.

  • DschoonmakerDschoonmaker Posts: 260Member

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

  • joel127joel127 Posts: 18Member

    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.

  • SIsilicon28SIsilicon28 Posts: 749Moderator

    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. :)

  • TudorTudor Posts: 27Member

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

  • TudorTudor Posts: 27Member

    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.
  • TudorTudor Posts: 27Member

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

  • joel127joel127 Posts: 18Member

    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.

  • TudorTudor Posts: 27Member
    edited April 28

    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.

  • joel127joel127 Posts: 18Member
    edited April 28

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

  • joel127joel127 Posts: 18Member
    edited April 28

    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)
    
  • TudorTudor Posts: 27Member

    @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.

  • joel127joel127 Posts: 18Member
    edited April 30

    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.

  • joel127joel127 Posts: 18Member

    The "First Person Starter" template also don't slide on slopes. It's using "move_and_slide_with_snap"

    Project available here:
    https://github.com/Whimfoome/godot-FirstPersonStarter

  • DschoonmakerDschoonmaker Posts: 260Member

    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.

  • TudorTudor Posts: 27Member

    @joel127 , I have tried both your solutions, but unfortunately, none of them worked for me.

    About first solution:

    • The climb up the hill is very slow and difficult for the player (he even tries to change the direction to the right or to the left). I suppose here is a problem with the gravity: it is too powerful.
    • When the player stops on the hill, he continues to slide down slowly.

    About the second solution:

    • The movement of the player is very unstable (he shakes up and down continuously).
    • The player accelerates very slowly.
    • Changing the direction of moving is very difficult: it takes a while until the kinematic body starts to move on a new direction.
    • The player climbs the hill faster that in the previous solution, but when you stop on the hill, he slides down quickly, like on a sledge. :DD

    How well do these solutions work for you?

  • TudorTudor Posts: 27Member
    edited May 19 Accepted Answer

    I finally found the solution:
    I use "move_and_slide_with_snap", thanks for the idea, guys :)

    When I am walking, the snap vector (second parameter of "move_and_slide_with_snap") is set to be the opposite of the floor_normal (the perpendicular vector of the surface the Raycast collides with).

    When I am jumping, the snap vector is set to null vector Vector3(0, 0, 0).

    var floor_normal = $Raycast.get_collision_normal()
    
    if has_contact and Input.is_action_just_pressed("jump "):
            velocity = move_and_slide_with_snap(velocity, Vector3(0, 0, 0), Vector3(0, 1, 0), true)
            velocity.y = jump_height
            has_contact = false
    
    velocity = move_and_slide_with_snap(velocity, -floor_normal, Vector3(0, 1, 0), true)
    

    This works for me. Now, the player stops on the slope definitively, until I move him again.

  • DschoonmakerDschoonmaker Posts: 260Member
    edited May 19 Accepted Answer

    Finally, a solution! I've also been looking for this for a long time.
    The main part is setting the snap vector to the inverted floor normal, although sometimes snap needs to be disabled(e.g. for jumping, @Tudor said he sets the snap vector to Vector3.ZERO. The same can be done by using move_and_slide() instead).

  • DschoonmakerDschoonmaker Posts: 260Member

    For many games, the player should slide down slopes, as long as they exceed the maximum slope angle. For that, my code to get the slope angle is useful:

    var floor_angle = acos(floor_normal.dot(Vector3.UP))
    

    And usually rad2deg() would be used to convert it to degrees.

    if(rad2deg(floor_angle)>maxSlopeAngle):
        #player is sliding down a steep slope
    

    Then, in move_and_slide_with_snap(), use Vector3.DOWN as the snap vector. That will make the player slide down as usual. For my game, I also apply increased downwards velocity so that he slides down quickly. This could also be multiplied by the floor angle, so that slopes slightly too steep only make him slide a little; that's just not what I did. It may also be necessary to decrease player speed, so that the player can't move up a steep slope.

  • DschoonmakerDschoonmaker Posts: 260Member
    edited May 23
    move_and_slide_with_snap(velocity, -floor_normal, Vector3(0, 1, 0), true)
    

    This randomly stopped working. I think the raycast is returning incorrect normals(I tested with a cube mesh, and made it point at the floor normal), but I don't know why that would be. The raycast definitely points down, and my code is exactly the same as before. What could possibly be causing it?

  • TwistedTwiglegTwistedTwigleg Posts: 2,873Admin

    It might be scaled collision shapes. Collision shapes have to have a uniform scale. This means that scales have to all have the same number (like (1.5, 1.5, 1.5)), making scales like (1.5, 1, 1.5) unstable. With non-uniform scales, unfortunately the collision shapes return really strange normal vectors when used with a raycast.

  • DschoonmakerDschoonmaker Posts: 260Member

    @TwistedTwigleg , which collision shape would that be? I checked the player, raycast, and terrain collision shapes, and each was scaled to (1,1,1)

  • TwistedTwiglegTwistedTwigleg Posts: 2,873Admin
    edited May 23

    I dunno, it was just something that I encountered when I was working with raycasts. Like you, I suddenly had strange results despite nothing with the code (or most of the project) had changed. After debugging, and I think I also opened a GitHub issue on the matter, I realized it was the scale of the collision nodes and their shapes. Using uniform scales fixed the issue, so I thought that could have been the case here.

    Edit: Are any of the collision nodes themselves scaled? Like the terrain, player, etc? I believe they will also need to use uniform scales for the normal vectors from the raycast to be reported correctly.

  • DschoonmakerDschoonmaker Posts: 260Member
    edited May 24

    Good advice, but I don't think that's the case here. The terrain was scaled up, but it was scaled by (2.5,2.5,2.5). It was scaled that way when it was working also, so that can't be the problem.

    Edit: I found a previous build also had sliding problems, apparently I had this problem and didn't realize(I was dealing with flat surfaces). It may be the code, but I don't think there's anything I can do about it. I may need to code the character script from the start again.

  • GumboMastoonGumboMastoon Posts: 1Member

    ...or just wait for 4.0 and really bug Reduz and co to make a strong case for an overhaul of the entire system. It shouldn't be this hard!

  • CalinouCalinou Posts: 452Admin Godot Developer
    edited July 14

    @GumboMastoon said:
    ...or just wait for 4.0 and really bug Reduz and co to make a strong case for an overhaul of the entire system. It shouldn't be this hard!

    reduz already plans to work on physics for 4.0. Also, please don't bump topics without contributing significant new information.

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file