Delay in RigidBody2D Contact Monitor

Godot Version

v4.2.1.stable.official [b09f793f5]

Question

Hi, I’m new in Godot, trying to implement a ball character controller for a platformer via RigidBody2D. I currently have working code, but it introduces a noticable delay between jumps. The timer does not cause the problem, it is there to prevent multiple impulses in consecutive frames. I suspect there is some kind of delay in the contact monitor contact reporting. ChatGPT is not able to help me with this. Will anyone be able to look at it?

extends RigidBody2D

@export var movement_strength := 500
@export var jump_strength := 600

var collisions: Array = []

func _physics_process(delta: float):
	$RayCast2D.rotation = -rotation
        $RayCast2D.force_raycast_update()

      	if $RayCast2D.is_colliding() and get_colliding_bodies().size() == 0:
            # This is the case when the body is colliding, but the contact monitor is not reporting it
            # It breaks when I put breakpoint here
	        pass

	var movement_intent := Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
	
	apply_central_force(Vector2(movement_intent, 0) * movement_strength)
	
	if Input.is_action_pressed("ui_accept") and $JumpTimer.is_stopped():
		for pair in collisions:
			var point = pair[0]
			var object = pair[1]
			
			var jump_direction: Vector2 = (global_position - point).normalized()
			
			var jump_force: Vector2 = jump_direction * jump_strength / collisions.size()
			
			apply_central_impulse(jump_force)
			
			if object is RigidBody2D:
				object.apply_impulse(-jump_force, object.to_local(point))
		
		$JumpTimer.start()

func _integrate_forces(state: PhysicsDirectBodyState2D) -> void:
	var current_collisions: Array = []
	
	for i in state.get_contact_count():
		var collision_point = state.get_contact_collider_position(i)
		
		# store all collisions in the lower quarter of the body
		if collision_point.y - global_position.y > $CollisionShape2D.shape.radius / 4:
			current_collisions.append([collision_point, state.get_contact_collider_object(i)])
	
    # I currently use this hack to forward the contact points to the _physics_process because get_colliding_bodies() only report the bodies
	collisions = current_collisions

I tried to visually profile it with a ray casting and Visible Collision Shapes and there is measurable one frame delay between when the ray detects the collision and when the debug output detects the contact, but I’m not sure if the contacts visualized in the debug output are the same reported by the Contact Monitor.

I cannot use the ray casting itself because this would break when the ball falls over an edge of a box. I guess I could calculate the contact myself with Area2D, but it seems weird to do this when the physics engine should give me this data.

Can anyone direct me in a right way? Is there a better way to detect contact points?

The noticeable delay was indeed caused by the $JumpTimer.start() being called even when the jump does not occur and preventing the next jump starting.

This does not solve the issue of not being able to do ad-hoc contact queries returning a list of pairs of objects and their contact points. I almost got it via this piece of code:

var shape_query_parameters := PhysicsShapeQueryParameters2D.new()

shape_query_parameters.collision_mask = collision_mask
shape_query_parameters.shape = $CollisionShape2D.shape
shape_query_parameters.transform = transform

var space_state := get_world_2d().direct_space_state

var objects    := space_state.intersect_shape(shape_query_parameters)
var collisions := space_state.collide_shape(shape_query_parameters)

But this just gives me two lists - the list of objects and the list of contacts - without a way to connect them to each other other than relying on the order, which is UB.
I’m aware of the get_rest_info() function, but this returns the contact information for one object only.

My current solution exploiting the _integrate_forces() function works for my case, but it feels to me it will break when I run the physics engine in a different thread. Is there a documentation on concurrecy model of the engine subsystems and/or GDScript runtime so I can be sure this does not introduce a race condition?

I finally solved this using ShapeCast2D which i didn’t know existed. It provides me with all methods necessary to do ad-hoc shape queries.