Object continues to slide along ground

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By acorn

Hello,

I am attempting to create an object that will bounce off walls, but slide to a stop on the ground. It almost works, but when it is on the ground it just continues to slide along the ground until i t hits a wall, where it then changes direction. I am fairly new to Godot, so apologies if I am missing something obvious!

Here’s my GDScript for the object (I am using a KinematicBody2D because I don’t want the physics engine to process it):

extends KinematicBody2D

var velocity = Vector2();
var weight = Vector2(0, 900);
var forces = Vector2(-900, 0);
const FLOOR_NORMAL = Vector2(0, -1)
const SLOPE_SLIDE_STOP = 25.0

func _ready():
	pass

func _physics_process(delta):
	if(!is_on_floor()):
		velocity += (delta * (weight + forces)); 


	velocity.x = lerp(velocity.x, 0, 0.1);

	velocity = move_and_slide(velocity, FLOOR_NORMAL)
	
	
	if(is_on_wall()):
		forces.x = -1 * forces.x ;
	pass
:bust_in_silhouette: Reply From: literalcitrus

Firstly, you don’t need semicolons in GDScript. I’d recommend removing them.

The main issue I think is causing your kinematic body to keep sliding is how physics bodies react when colliding with objects. When you call move_and_slide() on a kinematic body, and it moves into a collision, the is_on_floor() and is_on_wall() flags are set accordingly. However, when you make the next move_and_slide() call, the body “pops” itself off any existing collisions it had before moving again, and if you don’t apply any velocity to make it collide again is_on_floor() and other related flags will all be false. It’s also important to remember that move_and_slide() returns the resulting velocity that was just made (which is why you replace your velocity value with it).

If we step through what your code is doing when it first hits the ground, move_and_slide() is returning a velocity value with x and y set to the values of the motion just completed (minus the velocity lost due to collision), and is_on_floor() returning true. On the next frame, you call move_and_slide() again but because is_on_floor() is true, you haven’t changed the velocity.y at all (it’s still set to some value). move_and_slide() will return some x value again, but y will be set to zero because you are already “on the floor” so the collision would prevent any movement in the y direction. Now on the third frame, is_on_floor() is still true because we moved into the floor, so no change is made to velocity.y, and it’s currently set to zero. That means when move_and_slide() is called, the body isn’t moving into the floor anymore, which means no collision is detected, which means that is_on_floor() is set to false.

It’s this change of the is_on_floor() flag that makes your code think that it’s currently in the air, which means that the velocity += (delta * (weight + forces)) block is getting called and increasing your x velocity faster than the lerp can remove it. If you looked at the value of is_on_floor() you would see that it’s constantly jumping between true and false on every frame while you’re on the floor.

The easiest solution to this is to always be applying gravity to your kinematic body, even when it’s on the floor. This means that you will always have some velocity to push yourself into the collision and ensure that is_on_floor() is true when it needs to be. In some cases, mainly if your gravity value is very low, you may need to apply some artificial gravity to make sure that the collision is registered in a single frame. Something like velocity.y = 10 usually does the trick.

This kind of collision stuff becomes a lot more finicky when dealing with wall collisions but with what you’re doing, a simple “one-off” collision with walls works fine.

TL,DR: Do this:

func _physics_process(delta):
    if(!is_on_floor()):
        velocity += delta * forces
    velocity += delta * weight
    velocity.x = lerp(velocity.x, 0, 0.1)

    velocity = move_and_slide(velocity, FLOOR_NORMAL)

    if(is_on_wall()):
        forces.x = -1 * forces.x

Edit: Answer has been changed because it was mostly wrong originally

This means that if you call is_on_floor() or is_on_wall() before doing
a move_and_x(), it will always return false - the collision is only
registered when the body is moved.

“always” is a strong word… I added a print(is_on_floor()) after !is_on_floor() and sometimes print True.

Emerson MX | 2018-01-31 16:23

In what context is it returning true? Is it consistently when something happens or is it just random?

Edit: You are right, it’s not always the case. I will update my answer.

literalcitrus | 2018-01-31 19:26

I see, that makes sense! Thank you for the very detailed and informative answer !

acorn | 2018-02-01 02:34