Trouble with collision using Area2D and RigidBody2D

Godot Version

4.2

Question

I have been struggling to get something that should be seemingly simple to work. I want the collision activated from my Area2D node (which follows my cursor and activates a hitbox when left mouse button is pressed) to interact with a RigidBody2D node that is scrolling across the screen. When they collide, I simply want to queue_free() the RigidBody2D.

I know that Area2D and RigidBody2D hitboxes cannot naturally interact, so using the hitbox signal on the RigidBody2D wasn’t working. The only solutions I have come across simply suggest they both be physics2d bodies or area2d nodes. However, changing the Area2D node to a physics body ends up pushing the RigidBody2D around with the hitbox and not actually ever triggering the signal function to queue_free().

I also saw a suggestion to use the collision signal on the Area2D node instead, but I am unaware on how I would call a function from a separate node in the Area2D script. I could call it from the main node script, but the RigidBody2D is being instantiated through a script so the signal isn’t present to be connected.

I’ve also tried changing both of them to be Area2D, with the first node being moved across the screen every frame in its own _process function. For some reason this didn’t work either, despite updating the script to use the new _on_area_entered signal and connected it to itself just like you would with the _on_body_entered signal.

I’ve enabled Visible Collision Shapes to ensure the collision hitboxes are actually present, and they are.

I’m very new to godot so I know I’m missing something, it’s just frustrating to me that I got stuck here despite how simple it must be.

edit: having both of them be Area2D does in fact work, it only wasn’t in my testing because the hitbox wasn’t active long enough. I’m unsure how to fix this properly, I’ve tried creating and using await on a timer between enabling and disabling the hitbox, but it breaks.

Given Area2D and RigidBody2D manage collision, have you set up their collision layers properly?

image
.

Area2D should have a Signal under the Node tab

image

Yes, first node thats scrolling uses Layer 1 and Mask 1. Second node uses Layer 1.

When the second node has its hitbox always active, and both are Area2D, this works properly and the first node will queue_free()

You said that RigidBody2D is instantiated through script, so maybe the script could connect to the Area2D’s signal by:

  • Getting a reference to the Area2D
  • Connecting trough something like MyArea2D.my_signal_triggered.connect( receiver_function )
  • Under func receiver_function( my_args ): you should run the code that follows, which, If I’m not mistaken it should be queue_free()

If I am not mistaken “Layer” means the collision group the Node is in.
Without a Mask it would not be able to detect collisions.
I’d separate them into two different groups and make them detect eachother.

Changing the 2nd node’s Layer to 2 and Mask to 1, or even just adding Mask 1 and keeping it on Layer 1, both results in the hitbox no longer resulting in queue_free() on the first node. It seems to need Mask 1 turned off and for them both to be on Layer 1. Mask 1 is only on the first node to prevent multiple spawns from hitting eachother. This is only when they’re both Area2D nodes, otherwise this just doesn’t work period.

I haven’t gotten to your initial solutions, I’m not sure how to reference Area2D. Here’s the scripts so far:

Main Node script that handles spawning, which works as intended:

extends Node2D

@export var amoob_scene: PackedScene

func _on_spawn_timer_timeout():
	var amoob = amoob_scene.instantiate()

	var amoob_spawn_location = get_node("SpawnPath/PathFollow2D")
	amoob_spawn_location.progress_ratio = randf()

	amoob.position = amoob_spawn_location.position

	#Handles Scrolling Physics for RigidBody2D 
	var velocity = Vector2(150.0, 0)
	amoob.linear_velocity = velocity

	add_child(amoob)

amoob scene (first node) which is currently a RigidBody2D with sprite and hurtbox:
image
(I know the icon is for Area2D, this screenshot was taken before I changed it back to RigidBody2D. I’ve been swapping between them to test solutions)

extends RigidBody2D

func _on_body_entered(body):
	queue_free()

signal is appropriately attached
image

now the “attack-crosshair” scene, which handles following the mouse as an Area2D node and activating the hitbox when you click

image

extends Node2D
func _process(delta):
	var global_mouse_pos = get_global_mouse_position()
	position = global_mouse_pos
	if Input.is_action_just_pressed("shoot"):
		$Area2D/CollisionShape2D.disabled = false
		$Area2D/Sprite2D.visible = true
	else:
		$Area2D/CollisionShape2D.disabled = true
		$Area2D/Sprite2D.visible = false

This seemingly works, as when I click the hitbox and texture both appear very quickly. For testing purposes, the clicking conditional check can be removed and simply have the hitbox active at all times.

I don’t know how to go about this exactly. I’ve used .connect before in a tutorial but it fundamentally did not stick how to utilize it properly. In the tutorial I followed it seemed reliant on a variable that was set in the function itself, rather than literally referencing a node that is instantiated and not always present.

I fixed my hitbox issue. Originally tried doing an await like this

	if Input.is_action_just_pressed("shoot"):
		$Area2D/CollisionShape2D.disabled = false
		$Area2D/Sprite2D.visible = true
	else:
		await get_tree().create_timer(0.2).timeout
		$Area2D/CollisionShape2D.disabled = true
		$Area2D/Sprite2D.visible = false

This just didn’t work properly and I couldn’t see why. AnimationPlayer node works better though for timing of collision shape disabled, just keyed them out.

Still don’t know how to identify that a RigidBody2D and Area2D hitbox have collided. I eventually want the enemy to get knocked back by the hitbox slightly, so it would be nice to have the physics options available from a rigidbody. For now they’re both Area2D

As this has not been marked as solved, I’m leaving an answer.

I’ve been tinkering with an empty project and testing your code and how it behaves.
I replicated everything I could, and indeed it behaves weirdly.

I notice many things that don’t work properly, the first one being what is happening inside _process, inside CursorHitbox, as Input.is_action_just_pressed("shoot") triggers on one frame, and given it is inside process it happens so fast it disables quickly.

I’d suggest you moving the input code inside any built in input function you consider best. My suggestion is _unhandled_input(event)

Then maybe you can make it shoot whenever you click, and once you try to shoot, check if there is a body and then properly free this body. I’ve tested this for a bit and worked fine.

Maybe it’s your design choice but I don’t see a point in disabling the collider; specially because disabling and enabling collision at runtime normally leads to weird behaviours, even when called deferred.
In the end I’ve left it enabled and added checks instead for whether it was a valid shot or not.
If there is a design choice, then you’ll have to look for a workaround to the strange behaviours.

I’ve seen in your pictures that RigidBody is connecting to itself, waiting for a body to enter.
I think what you can do is make Area2D detect whether a body entered it and as the signal carries a value (that is, it passes the body which enters as an argument) you can then receive this body and store it somewhere for a reference. With this reference you can then pass the reference around and manipulate nodes freely.

I’m not on my PC until Sunday so I cannot tell you more, so this is all I got.
Hope it helps; report back!

This was it, the logic wasn’t clear to me at the time that (body) stores the information of the other node that touched it. This is why I was so confused on how the script could be made in Area2D. Thanks

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.