I was struggling with this last weekend. Here is what I ended up doing to solve it.
The issue is that when the kinematic body falls and collides with the ground, you want the ground to push back up. But move_and_slide returns the velocity when sliding along the surface. So, I use the downward velocity before move_and_slide as the magnitude of the correction needed, and remove the amount of x and z after move_and_slide based on the downward velocity.
First off, be sure to apply gravity all the time (I had a "solution" that reduced the downward velocity to -0.01 when on the ground). I am using v 3.3.4.
Also, I was loath to use ray casts due to their cost. I'd rather save it for something more difficult than standing on the ground. I also decided that move_and_slide_with_snap didn't fix the slope sliding issue.
I have two other features in the code, 1. I want the player to slide when the surface is too steep. 2. I want to manage responses based on the collision layer of what I collided with (I have terrain on 1, players on 2, and enemies on 3).
First off, I run my checks in
func _process(delta):
It will probably work in the physics_process, but I haven't tested it.
I'm skipping the whole part where I take in user input and determine the desired velocity.
var new_velocity = move_and_slide(velocity, Vector3.UP, false, 4)
Hopefully move_and_slide is already familiar to you, you put in the current velocity, the up vector, stop on slope, number of collisions. However, we don't directly apply the output to velocity, because we want to manipulate it, before applying it back to the player
# Check collisions
var floor_max_angle = 45
var floor_check = false
var floor_angle = 0.0
var slides = get_slide_count()
if(slides):
for i in slides:
var touched = get_slide_collision(i)
var n = touched.normal
if touched.collider.get_collision_layer_bit(0) == true: # terrain
floor_angle = rad2deg(acos(n.dot(Vector3(0, 1, 0))))
floor_check = true
if floor_angle < floor_max_angle:
new_velocity.x += (velocity.y * n.x)
new_velocity.z += (velocity.y * n.z)
velocity = new_velocity
is_on_floor = floor_check
So, if the player collides with the terrain and the angle of that terrain is less than 45 degrees, it removes the x and z amounts caused by the original velocity downward. Only after the collision processing is the new velocity applied.
As an added feature, I have the player hold a variable (is_on_floor) that I calculate based on collisions. That means that I now every frame if the player has sufficient downward force to push them into the ground.
While I was frustrated initially about how difficult it was to implement "simple movement," I don't see the current process as a "bug," instead the documentation is poor in how to implement it for a variety of purposes.
Hope it helps.