Using is_on_ceiling() to detect if CharacterBody2D is colliding with a ceiling doesn't work correctly

Godot Version

4.2.2

Question

Hello, I’m new to Godot and i’m trying to make the player (CharacterBody2D) squeeze when i press a certain key. and unsqueeze if there’s no ceiling over it. i made the squeezing part work but it seems to not care about ceilings and vibrate up and down when it tries to unsqueeze under a ceiling
This the code im using right now

if Input.is_action_pressed("squish") and $CollisionShape2D.scale.y > 0.25:
		$CollisionShape2D.scale.y -= 0.015625
		$CollisionShape2D.position.y += 1
		$Sprite2D.position.y += 1 			
		$Sprite2D.scale.y -= 0.015625
	elif $CollisionShape2D.scale.y < 1 and not is_on_ceiling():
		print(is_on_ceiling())
		$CollisionShape2D.scale.y += 0.015625
		$CollisionShape2D.position.y -= 1
		$Sprite2D.position.y -= 1
		$Sprite2D.scale.y += 0.015625

Thanks in advance!

I’m not sure if that will be the cause of problem but Godot documentation explict says to not change CollisionShape2D scale and that can result in unexpected behavior

From Godot physics introdution documentation:

Important

Be careful to never scale your collision shapes in the editor. The “Scale” property in the Inspector should remain (1, 1). When changing the size of the collision shape, you should always use the size handles, not the Node2D scale handles. Scaling a shape can result in unexpected collision behavior.

So i recommend you start from there

I changed my code to use shape size handles. but it still has the same results.
Here’s the new code

if Input.is_action_pressed("squish") and $CollisionShape2D.shape.size.y > 32:
		$CollisionShape2D.shape.size.y -= 1
		$CollisionShape2D.position.y += 1
		$Sprite2D.position.y += 1
		$Sprite2D.scale.y -= 0.0078125
elif $CollisionShape2D.shape.size.y < COLLISION_SHAPE_Y and not is_on_ceiling():
		$CollisionShape2D.shape.size.y += 1
		$CollisionShape2D.position.y -= 1
		$Sprite2D.position.y -= 1
		$Sprite2D.scale.y += 0.0078125

Can you share the project for me take a look?

sure thing. i cant seem to be able to send it here as a zip. where can i send it to you?

Upload on github or drive

here it is.

I think i found what is happening, when you make the collision shape bigger inside the gasp is_on_ceiling will be true only when the shape reach the size of the gasp, the problem is the shape will have a collision conflict with the floor/ceiling because will invade their shapes, so the physics engine will try push the character shape away to not let him insde the floor/ceiling, that fight is the flicking that you see the character going up and down, and to make worse, while the physics engine fight to not let the player shape enter in the ceiling shape, will make is_on_ceiling be false and the size will keep increase making the conflict even worse.

The solution is use another way to check if player reach the size, in this case an Area2D node:

Create an Area2D node with a CollisionShape2D as child and put a RectagleShape2D as shape like the image bellow:

In your code make this changes:

	elif Input.is_action_just_pressed("jump"):
		velocity.y = JUMP_VELOCITY
	
	# Keep the area in the correct position
	$Area2D.global_position.y = $CollisionShape2D.global_position.y + ($CollisionShape2D.shape.size.y / -2) - 1
	
	# Squeeze code
	if Input.is_action_pressed("squish") and $CollisionShape2D.shape.size.y > 32:
		$CollisionShape2D.shape.size.y -= 1
		$CollisionShape2D.position.y += 1
		$Sprite2D.position.y += 1
		$Sprite2D.scale.y -= 0.0078125
	
	# Check if the shape y size is small than the final size and check if something is inside the area shape
	elif $CollisionShape2D.shape.size.y < COLLISION_SHAPE_Y and $Area2D.get_overlapping_bodies().size() == 0:
		$CollisionShape2D.shape.size.y += 1
		$CollisionShape2D.position.y -= 1
		$Sprite2D.position.y -= 1
		$Sprite2D.scale.y += 0.0078125
	
	# Get the input direction and handle the movement/deceleration.
	var direction = Input.get_axis("move_left", "move_right")

With that your game will work as expected:

unknown_2024.04.28-17.26

2 Likes

this worked for me but i had to make it

$Area2D.global_position.y = $CollisionShape2D.global_position.y + ($CollisionShape2D.shape.size.y / -2) - 1 + COLLISION_SHAPE_Y

Thanks! This was very helpful. I will make sure to credit you in the game if it ever gets completed.

1 Like

But this introduced a new issue. the player can’t unqueeze while mid air. using the Area2D’s body_entered signal i can see that the Area2D is colliding with the CharacterBody2D (the player)
I tried to make it higher but it still somehow collides with the player. could you help?

On the platforms in the collsion tab, turn on the layer 2 and in the Area2D turn off layer 1 and mask 1 and turn on mask 2. This way the area will only detect the platforms, also make sure your Area2D have at least 3 pixels size in the y axis, i’ll send the project with the corrections if you want to check more precisily

1 Like

thanks this worked for me.
for some reason i had issues where i couldnt jump while unsqueezing when i copied over your player scene to my project it worked
also:

$Area2D.global_position.y = $CollisionShape2D.global_position.y + ($CollisionShape2D.shape.size.y / -2) - 1

i had to use again.
i guess i missed up my player position or something.
Thank you very much! without your help i couldnt have solved this issue. :+1: