Rigidbody2d still colliding even after moved away

Godot Version

v4.3.stable.official [77dcf97d8]

Question

I have a gun node2d that instantiates a projectile node2d that contains a rigidbody3d. When the projectile fires it as a TimeToLive, and when it expires, the projectile removes itself from the current tree and puts itself in a “magazine” array in the gun that spawned it. Then when the gun fires again, if it has a projectile in the magazine, instead of instantiating it, it re adds it to the current tree and positions it to be fired again.

The problem comes when the projectile collides with a staticbody2d. It removes itself from the tree and adds itself to the magazine just fine. But when I try to add it back to the scene tree and move it to the firing position, it is still colliding with the same staticbody2d.

Here is what I think is the relevant code in the gun

func _shoot():
	_curr_reload = 0
	var current_projectile
	if magazine.size() > 0:
		print ("Magazine")
		current_projectile = magazine.pop_back()
	else:
		print ("New")
		current_projectile = projectile.instantiate()
	current_projectile.chamber(shooter.global_position, self)
	current_projectile.shoot(Vector2.RIGHT.rotated(rotation), player.linear_velocity)
	get_tree().get_current_scene().get_node("Projectiles").add_child(current_projectile)

And here is the projectile object

func chamber (start_pos, gun):
	$RigidBody2D.global_position = start_pos
	_parent_gun = gun

func shoot( direction, inherited_inertia):
	_life = 0
	$RigidBody2D.linear_velocity = direction * projectile_speed
	_in_flight = true
	
func _physics_process(delta: float) -> void:
	print (rigid_body_2d.get_colliding_bodies(), _in_flight)
	if _in_flight :
		if _life < ttl:
			_life += delta
		if _life >= ttl:
			_recycle()
		if rigid_body_2d.get_colliding_bodies().size() > 0:
			hit (rigid_body_2d.get_colliding_bodies()[0])

func hit (node : Node):
	_recycle()
	if node.is_in_group("TakesDamage"):
		node.take_damage(projectile_damage)

func _recycle():
	_in_flight = false
	rigid_body_2d.global_position = Vector2.ZERO
	print (rigid_body_2d.get_colliding_bodies(), _in_flight)
	get_tree().get_current_scene().get_node("Projectiles").remove_child(self)
	_parent_gun.magazine.append(self)

The order of operations is the gun gets the shoot input, tries to pull the projectile from the magazine, and if it doesn’t exist instantiates it. Then it passes the projectile the starting position and direction. The projectile sets those values and then sets the _in_flight flag to true so the _physics_process will start testing if the rigidbody2d is colliding. Once it hits a node, it passes that node the damage if it can “take damage”, then zeros out its position then removes itself from the current tree and puts itself in the gun’s magazine.

When I run the game and fire the first bullet I get the output []true every frame until it hits an staticbody2d. Then I get [StaticBody2D:<StaticBody2D#33353106822>]true which makes sense, that is the object colliding with and the projectile is still in flight. But then I get [StaticBody2D:<StaticBody2D#33353106822>]false from the _recycle() function. Even though I have zeroed out the global position, moving the projectile away from the staticbody2d, it is still detecting the collision.

Is there some sort of cache in the rigidbody2d that I need to clear of colliding bodies to get it to stop detecting the previous collisions?

I believe your error is caused by the way you alter your RigidBody2Ds position. As a general rule, you should not modify the state of a physics object outside of _physics_process() and _integrate_forces() – with the latter being preferred for cases such as yours.

To solve your issue, prompt your projectile node to reposition itself through _integrate_forces(). As an example:

var queue_chamber = false
var queue_shot = false
var queue_recycle = false

func chamber(start_pos, gun):
	queue_chamber = true

func shoot(direction, inherited_inertia):
	queue_shot = true

func _recycle():
	queue_recycle = true;

func _integrate_forces(state):
	if queue_chamber:
		queue_chamber = false
		state.transform.origin = start_pos
		_parent_gun = gun

	if queue_shot:
		queue_shot = false
		_life = 0
		state.linear_velocity = direction * projectile_speed
		_in_flight = true

	if queue_recycle:
		queue_recycle = false
		_in_flight = false
		state.transform.origin = Vector2.ZERO
		print (rigid_body_2d.get_colliding_bodies(), _in_flight)
		get_tree().get_current_scene().get_node("Projectiles").remove_child(self)
		_parent_gun.magazine.append(self)
	
# Note the different way of setting the position: state.transform.origin

If you want to set the global_position based on a set of parameters, you might need to make the approach more abstract; substituting only the position-code with a “position queue”. Although, with the code that I have presented, you can be confident that your current code is executed in the same order.

Let me know if using _integrate_forces() fixed your issue.


Relevant class references: