• 3D
  • FPS character sliding down slopes slightly when walking

I am using move with snap on my fps controller to allow my character to stick to slopes. The controller can walk up slopes easily. However, when you move on a slope, and then stop moving, the character will slide down the slope a little before stopping. If you jump while on a slope, no sliding occurs, it is only while walking. It will always slide down, no matter which direction you are walking. I am not sure why this is happening, but I think it might be because of my use of linear interpolation to smooth the movement.

Here is my code for the fps controller. Sorry it's a little long, I tried to organize it but I'm not the best at that. =)

extends KinematicBody

export var mouse_sensetivity = .1

export var jump_time = 1.5
var jump_time_counter
export var jump_strength = 6.5

export var walking_speed = 7
export var airborne_speed = 2
export var gravity = 30

var speed = walking_speed
var velocity = Vector3.ZERO
var h_velocity = Vector3.ZERO

var h_acceleration = .2
var air_acceleration = .08
var normal_acceleration = .2

var snap_direction = Vector3.DOWN

onready var head = $Head

func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	jump_time_counter = jump_time
	pass

func _input(event):
	if event is InputEventMouseMotion:
		head.rotation_degrees.x += event.relative.y * mouse_sensetivity
		head.rotation_degrees.x = clamp(head.rotation_degrees.x, -90, 75)
		
		head.rotation_degrees.y -= event.relative.x * mouse_sensetivity
		head.rotation_degrees.y = wrapf(head.rotation_degrees.y, 0, 360)
		
func _physics_process(delta):
	
	handle_movement(delta)
	handle_jumping(delta)
		
	if Input.is_action_just_pressed("ui_cancel"):
		Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
		
	velocity = move_and_slide_with_snap(velocity, snap_direction, Vector3.UP, true)

func handle_jumping(delta):
	var just_landed = is_on_floor() and snap_direction == Vector3.ZERO
	var jumping = Input.is_action_pressed("jump")
	
	if jumping and jump_time_counter > 0:
		velocity.y = jump_strength
		h_acceleration = air_acceleration
		snap_direction = Vector3.ZERO
		speed = airborne_speed
		jump_time_counter -= delta
	elif just_landed:
		h_acceleration = normal_acceleration
		jump_time_counter = jump_time    #reset jump counter so the player can jump again.
		snap_direction = Vector3.DOWN
		speed = walking_speed            #change speed back to full controll.
	if Input.is_action_just_released("jump"):
		jump_time_counter = 0

	
func handle_movement(delta):
	var move_direction = Vector3.ZERO
	move_direction.x = Input.get_action_strength("left") - Input.get_action_strength("right")
	move_direction.z = Input.get_action_strength("forward") - Input.get_action_strength("backward")
	move_direction = move_direction.rotated(Vector3.UP, head.rotation.y).normalized()
	
	velocity.x = move_direction.x 
	velocity.z = move_direction.z 
	velocity.y -= gravity * delta
	
	h_velocity = h_velocity.linear_interpolate(move_direction * speed, h_acceleration)
	velocity.z = h_velocity.z
	velocity.x = h_velocity.x

Thanks!

15 days later

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.

3 months later

Thanks a lot for your help, sorry i didn't see this comment for a long time cause i wasn't on the forumns for a while and it got lost in the sea of notifications =).