How do I check if there is a clear line from A to B with Raycast2D?

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

I have a setup where if the gun is active, update_current_target() is called in physics process.
What it should do is first get all bodies in RangeDetector which is an Area2D and then filter it to get only those in the specified group.
Then it uses AimCheck, which is a Raycast2D, to check if there is a clear line between the gun and the targets.
Then it gets the nearest target and sets it as the gun’s primary target.

I noticed that it doesn’t work as expected with some test objects i set to pass by.
Direction and Speed
The east target touches first and the raycast targets it immediately. But get_collider() first returns [Object:null], then correctly sets it as the target in a split second then suddenly ignores it as though it’s no longer a valid target.
The north target is tracked correctly from when it enters to when it exits. As it nears the bottom edge the south target becomes the nearest but it’s ignored even after the north target leaves the area. Then it tracks the south target for less than a second and then stops.
It ignores the west target as expected since a static body is blocking it.

var current_target: Node

func update_current_target():
	var target_node: Node
	var possible_nodes: = []
	var clear_nodes: = []
	for item in RangeDetector.get_overlapping_bodies():
		if item.is_in_group(target_group):
			possible_nodes.append(item)
	for item in possible_nodes:
		if has_clear_shot(item):
			clear_nodes.append(item)
	if clear_nodes.size() > 0:
		target_node = clear_nodes[0]
		for body in clear_nodes:
			if (body.global_position - self.global_position).length() < (target_node.global_position - self.global_position).length():
				target_node = body
	else:
		target_node = null
	current_target = target_node

func has_clear_shot(target: Node):
	AimCheck.set_cast_to(target.global_position - self.global_position)
	return AimCheck.get_collider() == target

I spent a lot of time and i still don’t know how to fix it. My best guess is that get_collider() returns the wrong node even after setting its cast vector but that doesn’t make sense.

if (body.global_position - self.global_position).length() < (target_node.global_position - self.global_position).length():
                target_node = body

does it enter in this statement?

also (likely it doesnt even matter) but is the raycast set to exclude parent?

Andrea | 2020-06-01 15:50

It’s done in physics process so it’s more like processing a snapshot of all the targets.
Yes the raycast excludes the parent although the parent is a Node2D and raycast only seems to work on bodies and/or areas. And even then the first thing I do is filter objects in the group.

GooglyCoffeeMeat | 2020-06-01 16:01

can you share the project?

Andrea | 2020-06-01 16:19

Dropbox - GodotGun - Simplify your life

GooglyCoffeeMeat | 2020-06-01 16:33

sorry but the project is not complete (it’s even missing the project.godot file) and if use the nodes you provided it returns a bunch of errors because there are missing nodes and script.
If you dont feel like sharing the entire project, try replicate the issue on a smaller but working project, and share the entire folder

Andrea | 2020-06-01 16:48

The paths were pointing to the wrong directory. I’ve fixed all the dependencies and put it in a proper project. I haven’t used the gun elsewhere so it will work the same with just the relevant directory.
Dropbox - GodotGun - Simplify your life

GooglyCoffeeMeat | 2020-06-01 17:02

:bust_in_silhouette: Reply From: Andrea

the clear_shot function you are using to check if the target is clear is not working, because the the physic world state not being updated between one ray cast and the next one (if you notice, the clear_node array is always size 1, because it works only once a frame).
Use AimCheck.force_raycast_update() immediatly after set_cast_to(),

Another solution (less nodes and always updated): you can replace with an instance ray

func clear_shot(target: Node):
	var space_state = get_world_2d().direct_space_state
	var result = space_state.intersect_ray(global_position, target.global_position)
	return result.collider == target

It worked perfectly. Thank you for your help!

GooglyCoffeeMeat | 2020-06-02 11:52