How do I make a RayCast2D cast to the player?

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

Hello,

So i want my enemy’s raycast to cast to my player when my player is in range,
in order to fire an projectile along the direction of the raycast.

what I tried:

onready var rayCast2D = $RayCast2D;
onready var target = get_node("../../Player");

const MAX_DISTANCE : float = 60.0;

func _process(_delta):
	if global_transform.origin.distance_to(target.global_transform.origin) <= MAX_DISTANCE:

		rayCast2D.cast_to = target.global_transform.origin.clamped(MAX_DISTANCE);

if i print both vectors (my players ’ position and the raycats’ get_cast_to()) I get this:

target pos: (53.393688, 27.369949)ray pos(53.393688, 27.369949)
target pos: (53.393688, 27.369949)ray pos(53.393688, 27.369949)
target pos: (53.393688, 27.369949)ray pos(53.393688, 27.369949)
target pos: (53.393688, 27.369949)ray pos(53.393688, 27.369949)
target pos: (53.393688, 27.369949)ray pos(53.393688, 27.369949)[...]
 [output overflow, print less text!]

But ingame the blue arrow indicating my raycast is pointing sometimes at the player, but mostly at who knows what. I guess even a broken clock is correct twice a day.

my raycast scene is as follows:

Node2D {raycast script here}

RayCast2D

and the player scene:

Node2D (world)

YSort

KinematicBody2D (Player)

Node (NPC)

RayCastScene

:bust_in_silhouette: Reply From: timothybrentwood
func _process(_delta):
    var direction_to_player = global_position.direction_to(target.global_position)
    rayCast2D.cast_to = direction_to_player * MAX_DISTANCE
    rayCast2D.force_raycast_update()
    var collision_object = rayCast2D.get_collider()
    if collision_object == target:
        do_something_related_to_target()

really it should be done in _physics_process() similarly but a tad different:

func _physics_process(delta):
    var collision_object = rayCast2D.get_collider()
    if collision_object == target:
        do_something_related_to_target()
    # set up for next frame's collision
    var direction_to_player = global_position.direction_to(target.global_position)
    rayCast2D.cast_to = direction_to_player * MAX_DISTANCE

Storing a “target” variable as you are is very brittle. Most people use an Area2D node for “sight” or detection of other objects then check if get_overlapping_bodies() contains the player or by using one of the signals like body_entered.

Thank you for the answer.
It works perfectly.

In the case of using an Area2D and a signal, that would only detect the player when he
enters the area and not track the player as long as he is in the area or am I wrong?

MadJester | 2021-05-19 13:26

So the main thing to avoid is keeping a constant reference to a potentially temporary object. With the way your code is currently written, it will crash on the next frame if your player object gets queue_free()d because it will try to access the global_position property of a null object - which doesn’t exist.

Basically you want to do one of the following:

  • Put class_name Player in your player’s script then check if body is Player:
  • Add add_to_group("player") in your player’s _ready() function then check if body.is_in_group("player"):
  • Or only have your Area2D able to collide with player objects so whenever you get a body it’s guaranteed to be the player

Then:

func _physics_process(delta):
    for body in my_area2d.get_colliding_bodies():
        if (one of the body checks above):
            check_cast_on_player(body)

func check_cast_on_player(player_object):
    var direction_to_player = global_position.direction_to(player_object.global_position)
    rayCast2D.cast_to = direction_to_player * MAX_DISTANCE
    rayCast2D.force_raycast_update()
    var collision_object = rayCast2D.get_collider()
   # assuming your ray cast can collide with other objects like walls
    if collision_object == target:
        do_something_related_to_player(collision_object)
        # really you would want to emit a signal that the player object is connected to and let the player object do the work

It may seem very similar but what you’re doing is eliminating potential crashes by not keeping a constant reference to an object that isn’t a child.

timothybrentwood | 2021-05-19 13:59

Thank you for your time and the thorough explanation.
I learned much.

MadJester | 2021-05-19 15:12