Player-character occasionally bounces down from one way collision platforms when trying to jump onto them

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

Hey all, I’m working on a platformer right now and I’ve come across a bug that’s left me stumped. I’m using platforms with one way collision, so the player can jump through tiles and land on top of them. Most of the time this works as expected, but one in every 20 jumps onto a higher platform and you get bounced down right when the bottom of your character meets the top of the platform. It’s difficult to describe, so I’ve included a video demonstrating the bug, as well as showing some normally behaving jumps:

I’ve noticed the bug only seems to happen when you are moving either left or right while jumping. Another thing I’ve noticed is that the player seems to move up about a pixel whenever you walk left or right, and drop back down a pixel when you stop moving (perhaps this is related to the bug?)

I’ve included the player’s physics_process code below. Does anyone know what might be causing this? If any other information can help, please let me know.

func _physics_process(delta):

if alive:
	var walk = 0
	if can_move: #need to test if 
		if Input.is_action_pressed("ui_left"):	
			walk = -WALK_FORCE
			if (!facing_left):
				facing_left = true 
				$AnimatedSprite.flip_h = true
				$Sword.position.x = -SWORD_DISTANCE
		elif Input.is_action_pressed("ui_right"):
			walk = WALK_FORCE
			if (facing_left):
				facing_left = false
				$AnimatedSprite.flip_h = false
				$Sword.position.x = SWORD_DISTANCE
		if can_jump and Input.is_action_just_pressed("ui_jump"):
			velocity.y = -JUMP_FORCE
			can_jump = false
			jumping = true
			my_gravity = gravity * JUMP_GRAVITY_MODIFIER
		elif jumping:
			if check_grounded() or !Input.is_action_pressed("ui_jump"):
				jumping = false
				my_gravity = gravity
	if (walk == 0): #decelerate the player if they're not walking
		velocity.x = move_toward(velocity.x, 0, STOP_FORCE * delta)
		velocity.x += walk * delta #accelerate the player in their desired direction
	#bring the player towards max speed
	if velocity.x > MAX_WALK_SPEED:
		velocity.x = move_toward(velocity.x, MAX_WALK_SPEED, DECELERATE_FORCE * delta)
	elif velocity.x < -MAX_WALK_SPEED:
		velocity.x = move_toward(velocity.x, -MAX_WALK_SPEED, DECELERATE_FORCE* delta) 
	#velocity.x = (velocity.x, -WALK_MAX_SPEED, WALK_MAX_SPEED) #prevent player from exceeding max_walk_speed

	velocity = move_and_slide(velocity, Vector2(0, -1))
	was_grounded = is_grounded
	is_grounded = check_grounded()
	if is_grounded != was_grounded:
		emit_signal("grounded_updated", is_grounded)

	if !was_grounded and is_grounded: #just landed, so play landing sound
		playing_walking_sound = false
	elif is_on_floor() and walk != 0:
		#play the initial step sound
		if !is_walking:
			is_walking = true
	elif is_walking:
		is_walking = false
	if (is_on_floor() and !can_jump):
		can_jump = true
	elif (!is_on_floor() and $JumpTimer.is_stopped()) and can_jump:
:bust_in_silhouette: Reply From: max.marauder

Looks like it is a bug of the physics engine: sometimes move_and_slide falsely detects floor when jumping on a one-way collision platform from below with non-zero horizontal speed.
In this case it returns velocity with y == 0, which is expected behavior in case of correct floor detection, but in our case it cuts the jump before a body is above the platform.
Unfortunately I wasn’t able to fix the root cause of the problem, however I found a workaround that at least fixes the symptoms:

var alteredVelocity = move_and_slide(velocity, Vector2(0, -1), true)
if (alteredVelocity.y == 0) && (velocity.y < 0) && is_on_floor():
	alteredVelocity.y = velocity.y
velocity = alteredVelocity

As you can see, I check for a combination of 3 parameters, which most likely means that the body was moving up (jumping), and then is_on_floor() detection happened and alteredVelocity.y returned by move_and_slide has become 0.
In such case I set alteredVelocity.y back to the original velocity.y which allows the current jump to go on.

I’ll appreciate if someone suggests more elegant solution, which would eliminate the false floor detection in the first place.