Bouncing raycast causes raycast and Line2D to disappear

Godot Version

4.6.3

Question

Hello. I have a raycast and line2d that enable and turn visible (respectively) when the player activates them by entering an Area2D. I am trying to make this raycast and line2d bounce off of a wall but cannot figure it out for the life of me. I believe I have the basic formulation of the bouncing raycast correct (maybe not), but for some reason when trying to actually implement a new global position my raycast and Line2d no longer enable and turn visible when entering the Area2d.

Here is my code for the raycast bouncing:

func _physics_process(_float) -> void:
    if ray_cast.is_colliding and ray_cast.enabled == true:
        var n = ray_cast.get_collision_normal()
		var c = ray_cast.get_collision_point()
		var r = ray_cast.global_transform.origin
		var incoming_direction = c - r
		var outgoing_direction = incoming_direction.bounce(n.normalized())
		
		ray_cast.global_position = c + outgoing_direction

The raycast and beam work fine when I comment out the last line of code. How can I get this bouncing to work?

What exactly are you trying to accomplish with this?

the idea is that the player can activate a positionable light beam and then aim the beam to mirrors to reflect the light. I’ll add in the specific collider stuff needed for the mirror at a later time since I need to get the basics of the raycast bouncing down first.

here’s a clip of what’s working so far (turning on the beam and positioning both work), but as you can see it isn’t bouncing off walls like I want it too

Well you can’t do this with a single raycast. You’ll need as many raycasts as there are bounces.

If I’m understanding your goals correctly, the broad strokes of what you want to do are as follows:

If ray hits an object that causes deflection:

  • Get the position of the collision point and normal get_collision_point(), get_collision_normal()
  • Calculate the reflection
  • Set the position of the Raycast2D to the collision point
  • Set the target position of the Raycast2D to the reflected point
  • Recalculate the ray so it will update this frame force_raycast_update()

You probably want to do this in a while true: loop that breaks once there are no more collisions with reflecting objects (or some large amount of iterations to avoid infinite loops in case you end up with two reflectors perfectly reflecting a beam between themselves)

In each iteration of the loop, store the hit position in an array, and once you’ve got all your reflection positions, set your Line2D.points to the array of hit positions.

This response helped me with a very similar problem in the past.

Here are the relevant snippets from my implementation of this idea. for context:

  • I have a hitscan (Raycast) gun with the bullet’s path animated with a shader along a Line2D
  • I also set a maximum distance on how far a bullet can travel.
class_name HitscanComponent
extends RayCast2D
# [...]
var raycast_hit: Node2D
var tracer: Line2D
var tracer_scene: PackedScene
var hit_positions: Array[Vector2]
var deflector_exceptions: Array[Area2D]
var max_distance: float
var remaining_distance: float
# [...]
func _ready() -> void:
    hit_positions.append(global_position)
	process_shot()
    destroy()
# [...]
func process_shot():
    force_raycast_update()
    while true:
        # Clear any exceptions for DeflectorComponents so they can deflect again
		for exception in deflector_exceptions:
			remove_exception(exception)
		deflector_exceptions.clear()

        if is_colliding():
			hit_positions.append(get_collision_point())

        raycast_hit = get_collider()
		if not raycast_hit:
			return

		if raycast_hit is Deflector:
			deflect(raycast_hit)
# [...]
func deflect(collider: Object) -> void:
	remaining_distance = max_distance - get_distance_travelled(hit_positions)
	add_exception(collider)
	deflector_exceptions.append(collider)

	# Credit: https://forum.godotengine.org/t/rotating-raycast2d-accordingly-to-vectors-reflection/13589/3
	var collision_point = get_collision_point()
	var collision_normal = get_collision_normal()
	var forward = collision_point - global_position
	var reflection = -forward.reflect(collision_normal)
	global_position = collision_point
	target_position = reflection.normalized() * remaining_distance
# [...]
func destroy():
	tracer = tracer_scene.instantiate()
	get_tree().root.add_child(tracer)
	tracer.points = hit_positions +  [target_position + global_position]
	queue_free()
#[...]
func get_distance_travelled(points: Array[Vector2]) -> float:
	# Get the total distance between all hit positions so we know how far this ray has been cast so far.
	# Used to determine the distance the ray should reflect when colliding with a Deflector
	var total_distance: float = 0
	var p1: Vector2
	var p2: Vector2

	for point in range(points.size() -1):
		p1 = points[point]
		p2 = points[point + 1]
		total_distance += p1.distance_to(p2)
		
	return total_distance