How to make a KinematicBody2d, in a 2d platformer, bounce of the floor only when falling

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

I want the Player (KinematicBody2d) to represent a moving ball, so when it reaches the floor it should bounce a couple of times and then stop. I have tried implementing this in many ways, but a problem always arises. For example, with the collision check from move_and_collide, the problem is that when the character is allready on the floor, it keeps detecting collisions, even if i use not self.is_on_floor and not self.is_on_wall in the if conditional. I also tried turning off gravity when player is on the floor. this is the relevant part of the code:

const FLOOR_NORMAL = Vector2.UP
var velocity := Vector2.ZERO
export var acceleration := 100.00
export var friction := 40.00
export var max_speed := 100.00
export var gravity := 200.00
export var max_fall_speed := 1500.00
export var bounce_speed := 100


func _physics_process(delta: float) -> void:
    var collision = move_and_collide(velocity * delta)
    var direction := get_direction()
    velocity = calculate_move_velocity(velocity, direction, acceleration)
    if collision and not self.is_on_floor() and not self.is_on_wall():
		  velocity.y= - bounce_speed
    clamp_velocity()
    velocity = move_and_slide(velocity, FLOOR_NORMAL)


func get_direction() -> float: 
    return Input.get_action_strength("move_right") - 
    Input.get_action_strength("move_left")

func calculate_move_velocity(
	     velocity: Vector2,
	     direction: float,
	     acceleration: float
  	     ) -> Vector2:
    var out := velocity
    out.x += acceleration * direction * get_physics_process_delta_time()
    if not self.is_on_floor():
   	     out.y += gravity * get_physics_process_delta_time()
    if direction == 0:
	     out.x = move_toward(out.x, 0, friction * get_physics_process_delta_time())
    return out

func clamp_velocity () -> void:
    velocity.y = clamp(velocity.y, -max_fall_speed, max_fall_speed)
    velocity.x = clamp(velocity.x, -max_speed,  max_speed)

With this version of the code the Player bounces only once and then, almost randomnly, bounces out of nowhere when moving, and then most of the times when it collides with a wall. The Player Scene is just a KinematicBody2d with a CollsionShape2d and an AnimatedSprite as children. The shape of the Player (and of the collsision shape) is a perfect circle, and it is colliding with square MapTiles.

The bouncing only once is not for now, part of the problem. Thanks in advance

:bust_in_silhouette: Reply From: Legorel

From the doc :

bool is_on_floor() const

Returns true if the body is on the floor. Only updates when calling move_and_slide() or move_and_slide_with_snap().


bool is_on_wall() const

Returns true if the body is on a wall. Only updates when calling move_and_slide() or move_and_slide_with_snap().

In your code you are using move_and_collide, so is_on_floor and is_on_wall always returns the same value.
There might also be more problems, if there is a collision with the floor or a wall you want to bounce of, then is_on_floor and is_on_wall will make the condition false.
If the ball is on the side of a wall and should bounce of the ground, because it is on a wall, it will not bounce.
I also see you were using a var bounce_speed for the speed of the bounce, if you want you can try to use get_travel and get_remainder of KinematicCollision2D (your collision var) :

Vector2 travel [default: Vector2( 0, 0 )]get_travel() getter

The distance the moving object traveled before collision.


Vector2 remainder [default: Vector2( 0, 0 )]get_remainder() getter

The moving object’s remaining movement vector.

Thanks for the awnser. Maybe there is something that i dont understand, but at the end of the _physics_process, I set velocity = move_and_slide(velocity, FLOOR_NORMAL), so in every frame, the .is_on_floor/wall does get updated. The fact that sometimes I am getting undesired bounces, and sometimes not, suggests that they are in fact not always returning the same value

I agree that more problems, like the one you mention, will arrise, but i cant think of another way to implement the bounce.

replacing the var bouncing_speed with the reminder of the collision will come in handy, in making the bounce more realistic

Pomelo | 2021-05-22 20:11

Has you said i didn’t see the move_and_slide so is_on_floor and the other are correct

So now i am thinking of creating two variables : was_on_ground and was_on_wall
You update them at the end of _physics_process, so they are always “at the previous frame”

Your ball isn’t bouncing off walls so i don’t see the need of is_on_wall and was_on_wall right now.

This is how it should look like :

var was_on_floor := false
# if needed for bouncing of walls or ceilings
#var was_on_wall := false
#var was_on_ceiling

func _physics_process(delta: float) -> void:
    var collision = move_and_collide(velocity * delta)
    var direction := get_direction()
    velocity = calculate_move_velocity(velocity, direction, acceleration)
    if collision and not was_on_floor:
          velocity.y= - bounce_speed
    clamp_velocity()
    velocity = move_and_slide(velocity, FLOOR_NORMAL)

    # maybe more code in _physics_process

    was_on_floor = is_on_floor()
    # if needed
    #was_on_wall = is_on_wall()
    #was_on_ceiling = is_on_ceiling()

Be aware that the ball will stop bouncing only if it has touched the floor/wall 2 frame in a row, if that’s not what you want you need to force stop the bounce in some way.

Legorel | 2021-05-22 21:53

I tried it, It bounces once, but then after i start walking it starts to bounce again “randomly”. I like your idea, if i am already on the floor, then was_on_floor should stay true and the bounce shouldnt trigger, but it does. Maybe it has to do with the fact that my CollisionShape2d is a circle, and the is_on_floor has dificulty telling when the Player is actually on floor

Pomelo | 2021-05-23 01:09