Why is CollisionShape2D not updated properly when changing its parent RigidBody2D position in input_event callback

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

Hi,
I am new to Godot so I might be missing something obvious here, but the following behavior seems like a bug to me:
I have a RigidBody2D scene node with a CollisionShape2D subnode. I am using a capsule shape, but this seems to be irrelevant for the problem, I tried it with other shapes, too. The RigidBody2D has a GDScript assigned, which listens to input_event from CollisionShape2D to detect a mouse click inside the collision shape, the connected function looks like this:

func _on_input_event(viewport, event, shape_idx):
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
		print("clicked on TestRigidBody")
		self.position += Vector2(0.0, 50.0)

I then have my main scene derived from Node which instantiates this RigidBody2D scene (named TestRigidBody) and just moves it at a start position like this:

func _ready():
	var screen_size = DisplayServer.window_get_size()
	$TestRigidBody.position = 0.5 * Vector2(screen_size.x, screen_size.y)

The problem now arises when the user clicks on the TestRigidBody: At first everything works fine and the rigid body and its collision shape seem to be moved by Vector2(0.0, 50.0) as intended. I enabled “Debug->Visible Collision Shapes”, which shows the collision shape is moving, too. But when trying to click inside the collision shape again to move the object once more, only a click inside the initial collision shape triggers the _on_input_event function not a click inside the actual visualized collision shape. So essentially it seems that the collision shape of TestRigidBody has not moved at all when clicking on the object, although godot debugger shows the visualized collision shape in the intended place.

Am I right in assuming this is a bug and do you know how to circumvent it?

I tested this in Godot 3.5.2, Godot 4.0.3 and Godot 4.1 dev3 running on an Intel mac. I attached a test project with the above setup for you to see the problem in action:
collision_shape_movement_test

I’d guess you’re getting some unintended influence from the physics system. As noted in the docs, ideally you should not be directly changing the position of a Rigidbody2D as it’s managed by the physics system. Instead, you should be using _integrate_forces().

For instance, this statement from the above link:

Note: You should not change a RigidBody2D’s position or linear_velocity every frame or even very often. If you need to directly affect the body’s state, use _integrate_forces, which allows you to directly access the physics state.

jgodfrey | 2023-06-01 20:51

:bust_in_silhouette: Reply From: putper

A RigidBody is a node that is controlled by physics. As described in Godot’s documentation on RigidBody2D, you can’t directly perform transformations (like setting position), since the physics system is overriding it again.

Move RigidBody2D with Phyiscs

If you want to move a RigidBody, use forces, like with the method: apply_central_impulse(Vector2(0.0, 5.0)).
There are a bunch of methods to apply forces. Check out the documentation.

Teleport RigidBody2D

Godot provides the _integrate_forces() event. When a physics calculation is done, the event is called and you can alter it.

extends RigidBody2D

var should_be_moved = false

func _on_input_event(viewport, event, shape_idx):
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
		can_sleep = false
		should_be_moved = true
		
func _integrate_forces(state):
	if should_be_moved:
		state.transform.origin += Vector2(0.0, 50.0)
		can_sleep = true
		should_be_moved = false

In the above code the:

  • _on_input_event() sets should_be_moved to true when a click happened to communicate a movement is going to happen.

  • can_sleep is set to false. Otherwise when your RigidBody isn’t moving, integrate_forces() isn’t run because no physics calculations are expected.

  • integrate_forces() applies the movement.

Alternative

If you only need image that changes position on click, and you don’t need the fancy physics calculations from a RigidBody2D, look into using a Sprite instead.

Thanks for the detailed answer. Guess I should have read the documentation about RigidBody2D :). To be honest I did not expect the RigidBody2D to be the culprit here and only focused on the CollisionShape2D and its input_event signal. Thanks for pointing me in the right direction.

3DWizard | 2023-06-01 21:54

One more question: You mentioned using TextureButton as an alternative. How about using Area2D instead of RigidBody2D, as it does not implement simulated physics? Do you see any problems with that?

3DWizard | 2023-06-01 23:15

An Area2D is meant for collision-checks, it allows you to catch an event when another Area2D or a RigidBody overlaps it. It doesn’t have support for showing images.

What you are looking for is a Sprite. And a better alternative for your use-case, since TextureButtons are meant for UI.
I will change my original comment to recommend a Sprite instead.

putper | 2023-06-02 08:07

Thanks for the reply. I do need the ability to select the object via mouse input. It is clear to me how using a button would allow doing that, but not how this would work with a Sprite2D node. So far I used CollisionShape2D input_event signal for that, which cannot directly be used on a Sprite2D node, so I would tend to using an Area2D node with AnimatedSprite2D subnode and CollsionShape2D subnode. Or do you have another suggestion for being able to use Sprite2D directly without any Area2D involvement?

3DWizard | 2023-06-02 10:23