Pulling my hair out trying to figure out why my player is clipping through the ground with an Area2D-oriented collision system

Godot Version

4.2.1

Question

Video

I’m not using move_and_slide / collide, and I’ll post my code below. Let me explain what is happening first.

The player spawns in the air and falls to the ground with a “feet” area2d as its child that acts as the collision. The “feet” initially lands snugly inside the ground area, but if I jump and land again, the feet are a few pixels below the ground, and then the player falls straight through the floor on the second jump.

The structure of the player is:

  • Node2D (Parent Node) → Player script attached
  • Area2D “Feet” → Feet script attached, child of the Node2D
    • CollisionShape2D of the area above.

And now the code for the player and feet:

PLAYER

extends Node2D

static var gravity_resistance : float = 0.0

var player_gravity : float 
var move = Enums.move_state
^ From an autoload that holds a bunch of enums I didn't include.
var min_jump_time : float = 0.1
var max_jump_time : float = 0.4

@onready var MOVE_STATE = move.fall

func _physics_process(delta):
	apply_gravity(delta)
	move_state()

func apply_gravity(delta) -> void:
	player_gravity = (Room.room_gravity - gravity_resistance) * delta
    #Room gravity is 600
	position.y += player_gravity #This is a value of 10 while falling (with delta, 600 otherwise), -10 while jumping, and 0 when on the ground.

func move_state() -> void:
	var pos = Vector2(position)
        match MOVE_STATE:
		    move.fall: 
			    search_transition("jump", pos)
            move.jump:
                jump()

func search_transition(parameter : String, pos) -> void:
	var y_axis = Input.get_axis("down","up")
	
	if parameter == "jump": 
		if Input.is_action_just_pressed("space") && y_axis != -1:
			MOVE_STATE = move.jump

func jump() -> void:
	gravity_resistance = Room.room_gravity * 2 #player_gravity becomes -600
	await get_tree().create_timer(min_jump_time).timeout
	if !Input.is_action_pressed("space"):
		gravity_resistance = 0
		MOVE_STATE = move.fall
	else:
		await get_tree().create_timer(max_jump_time).timeout
		gravity_resistance = 0
		MOVE_STATE = move.fall

FEET

extends Area2D

@onready var player = get_parent()

func _on_area_entered(area):
	player.gravity_resistance = Room.room_gravity #600 - 600, AKA 0

func _on_area_exited(_area):
	player.gravity_resistance = Room.room_gravity

this forum uses Markdown and extended syntax Markdown as formatting as far as i know

if u want to include piece of code as in your example:

var player_gravity : float
var move = Enums.move_state
var min_jump_time : float = 0.1
var max_jump_time : float = 0.4

you need to put “```GDScript” on new line → than piece of your code on new line → than (on new line) “```” to close code block

1 Like

when you’re posting problems be sure to include all of code (or better → simplified version) its hard to make out what not included parts do

for example line:

player.gravity_resistance = Room.room_gravity #600 - 600, AKA 0

comment doesn’t make any sense because you aren’t modifying room_gravity in any of your examples

problem:

problem seems to be that you are modifying gravity inside of signal callback
signals are not dependent on engine physics tick (they are asynchronous)

1 Like

Room_gravity is a constant 600. apply_gravity()'s in the player script
player_gravity = (Room.room_gravity - gravity_resistance) * delta
is what I was referencing for #600 - 600, AKA 0, where room_gravity is the first 600 and the player’s said
resistance to gravity the latter, making the player’s change in y position when applied 0:
position.y += player_gravity
I’m essentially trying to use gravity_resistance as the only variable that can change the player’s y position.

I’m not sure what signal callback would be, area_entered in the feet? Or when the static var gravity_resistance is changed? I’m not using actual signals so that’s confusing me. What would be my best alternative, to change gravity_resistance inside of physics_process after area_entered emits a signal?

_on_area_entered and _on_area_exited in FEET script are signal callbacks: Using signals — Godot Engine (stable) documentation in English

It’s a classic example of “fast bullets, thin walls” gamedev problem, except it your case it’s a feet Area2D instead of a bullet.

Physics is games are simulated in a discrete way. That means that a movement of a physics body is not continuous like in real life, it is simulated only in discrete points of space, 60 times per second. The state between these points is not evaluated. Strictly speaking a body just teleports short distances 60 times per second creating an illusion of movement.

What happens with “too fast bullets, too thin walls” is that at a frame 1 the bullet is right in front of a wall, but doesn’t touch it so no collision is reported. On the next frame the bullet travels a great distance because of its great velocity, so on frame 2 the bullet is already behind the wall. No collision is reported, because there was no frame where the wall and the bullet clearly intersect.

It boils down to: if a collision body moves with a velocity which length is greater than a thickness of colliders, it may go through said colliders without ever reporting a collision.

TL;DR: your feet area is too small for the velocity the player is moving when falling. And your floor is too thin. In cases like these people use raycasts with length set to the length of the velocity vector, not areas. Raycasts can report a collision at any point along the line of the raycast, not only on the very tip.

1 Like