Sometimes collision detection of Area2D doesn't work

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

Hello,

As I told in the title, there is something strange.
I made simple 2d shooting game.
Player, missile and enemy is Area2D node which has its children node of CollisionObject2D for detecting collision.
I want to missile and enemy to be disappeared when both hit.
The code for this is like this.

func _on_missile_area_entered(area):
	if area.get_name() == "Mob":
		area.queue_free()
		queue_free()

I uploaded the full source to github.

The game is made using Godot 3.
please take time to see what’s the problem.

Thank you.

:bust_in_silhouette: Reply From: p7f

Sometimes godot changes name property to avoid duplication. So when you ask for name, instead of “Mob” you get “@Mob@6” or whatever. What i do in this situations is to define a variable in mob script like im_mob and then ask if the objects has it. Add to your Mob script this

var im_mob = true

and in missile script change this:

func _on_missile_area_entered(area):
    if area.get_name() == "Mob":
        area.queue_free()
        queue_free()

with this:

func _on_missile_area_entered(area):
    if "im_mob" in area:
        area.queue_free()
        queue_free()

Tested and works in my pc.

EDIT: Avoid tunneling effect when Area2D is too fast (see comments below)

You can solve this by raycasting (you can do it with RayCast2D node) previously to the movement, and if raycast returns a collision, move to that collision point instead.

func _process(delta):
    var aux_pos = vel * delta * speed
    $RayCast2D.cast_to = aux_pos
    $RayCast2D.force_raycast_update()
    if $RayCast2D.is_colliding():
        global_position = $RayCast2D.get_collision_point()
    else:
        position += aux_pos 

you can also add mobs to group and ask if it belongs to that group, which could be more elegant. but this works

p7f | 2019-01-03 17:58

Thank you very much.
It’s a month ago that I started to learn Godot 3.
It’s very interesting but this problem was frustrating.
You gave me strength to continue learning.

One more question:
If the missile is as fast as it can not be drawn in the screen, is the collision checked?
For example, when time is 0.0sec, the missile is at position (100,100) and time 0.01(actually this is the time when the next frame is drawn), the position of the missile (150,100). And the mob is at position (125, 100). What do you think?

I tested with this code where the speed is 10 times. I see the missile pass through the mob and the mob is still there. How can I solve this problem? I want to missile hit the mob regardless of the speed of missile. Is there any solution other than slowing down the speed?

			# launch missile
		if event.index == 1:
			var amissile = Missile.instance()
			amissile.position = $Player.position
			amissile.vel = pos_delta[1].normalized()
			amissile.speed = pos_delta[1].length() * 20 # 2->20
			add_child(amissile)
			$AudioStreamPlayer2D.playing = true

I became to know that the problem can be solved by increasing the physics fps value in the setting. I changed the value from 60 to 120. After that, the missile shows more frequently and therefore the missile hit the mobs. Is this approach good or not in the aspect of CPU or GPU load? Normally up to what value do you use?

eunwoo123 | 2019-01-04 13:47

It really depends how you implemented movement. If you update the position directly, as we usually do with Area2D, you may miss that collision. That effect is known as “tunneling effect”. You can solve this by raycasting (you can do it with RayCast2D node) previously to the movement, and if raycast returns a collision, move to that collision point instead.

If you are using KinematicBodies, or RigidBodies, as movemen is implemented in different way, i think this won’t happen (raycast is done by the engine in this cases i think). But the best way to know is to try.

In fact, in your project i tried with missile speed=3000 and that causes tunneling effect.

p7f | 2019-01-04 14:01

I modified your missile script a little to do raycast to avoid tunneling and it works for a speed of 3000:

func _process(delta):
    var aux_pos = vel * delta * speed
    $RayCast2D.cast_to = aux_pos
    $RayCast2D.force_raycast_update()
    if $RayCast2D.is_colliding():
	    global_position = $RayCast2D.get_collision_point()
    else:
	    position += aux_pos 

Previously, you must add a RayCast2D node as child of missile, enable it from inspector, and put collision layer to for example bit 2. Then, add that layer also to your enemies (so they are the only ones intercepted by raycast).

The code makes the Raycast to cast to the final position, and if it detects a collision in the way, moves the Area to that point instead of to the final point.

p7f | 2019-01-04 14:14

Hi, i would not change the physics fps only for this. Use Raycast as i told you in other comments. It works perfectly in my computer with your project.

p7f | 2019-01-04 14:31

Thank you a lot.
It was precious advice. The application of Ray cast, collision layer/mask etc. on real situation was really nice. All the curiosity and problem was solved.

eunwoo123 | 2019-01-04 15:24

You are welcome! Would you then select my answer so others see the problem is solved? I’ll add the raycast thing to the answer also.

p7f | 2019-01-04 15:26