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

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

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.

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

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.

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?

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.

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

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.

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.

2 months later

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

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

a year later

For those of you still working on this issue, here is my solution. I am using v 3.3.4

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

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

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

I found just setting velocity.y to zero when is_on_floor() was true fixed it.