I’m going to go ahead and assume that your #SECOND CAST
-code and your for-loop are two different attempts at solving the same problem. A for-loop is indeed one way to solve your problem. However, there are a few issues with your for-loop.
Ray information is not fully updated between iterations
from
to
target_direction
(not updated)
As noted in the box above, your target_direction
is not being updated in your for-loop. Because of this, every ray in your for-loop will shoot in the same direction.
Using for-loops incorrectly
I don’t use GDScript myself but from what I can gather on the web, you shouldn’t increment i
yourself – GDScript does that for you. Please print out i
to confirm; I’m unsure of whether I’m correct here. However, I’m pretty sure you don’t need to break
your for-loop. Like most other languages, a for-loop will only do as many loops as you set it to do – unless, of course, you modify i
(which you are doing).
Please consult the GDScript reference and other sources to become more familiar with how it works.
Making a conceptual distinction between your first ray and subsequent rays
In the code you’ve shown in your post, you’re separating your first ray (#FIRST CAST
) from the “bounce”-rays (#SECOND RAY
and so on…). From a code-perspective, this is not a maintainable or reliable approach to implementing a bouncing laser. Unless the first ray of the laser should behave differently, you should only have to write the code for your ray(s) once – otherwise you may run into scenarios in the future where you forgot to change both pieces of “ray code” which may then result in unintended behaviour.
Your set of raycasts are used to achieve a “laser that keeps reflecting/bouncing in 3D”. If they are part of the same thing and behave identically, you shouldn’t make distinctions between them.
TL;DR: distinguishing the first ray from the rest makes sense from a design standpoint, not from an implementation standpoint.
Solution
extends Node3D
@onready var marker3d = $Marker3D
@export var cast_length = 10
@export var max_reflections = 10
const epsilon = 0.01 # collision margin for the bounce rays
func _physics_process(delta):
var space_state = get_world_3d().direct_space_state
# The ray information (initialized for the first ray in the loop)
var dir = marker3d.global_basis * Vector3.FORWARD
var from = marker3d.global_position
var to = marker3d.global_position + dir * cast_length
var result: Dictionary
# NOTE: I changed the range-parameters. 0-indexing is a programming standard
var rays_to_shoot = 1 + max_reflections
for i in range(rays_to_shoot):
# Perform a raycast with the current ray information
var query = PhysicsRayQueryParameters3D.create(from, to)
result = space_state.intersect_ray(query)
if result.size() == 0:
DebugGeo.draw_debug_line(delta, from, to, 0.025, Color.RED)
break
else:
# ===== Debugging Code =====
var hue = 0.25 + fmod(i / 2.0, 1.0)
var color = Color.from_hsv(hue, 1.0, 1.0)
DebugGeo.draw_debug_line(delta, from, result["position"], 0.025, color)
DebugGeo.draw_debug_sphere(delta, result["position"], 4, 4, 0.2, color)
DebugGeo.draw_debug_line(delta, result["position"], result["position"] + result["normal"], 0.025, Color.DODGER_BLUE)
# ==========================
# Update ray info
dir = dir.bounce(result["normal"])
from = result["position"] + result["normal"] * epsilon
to = from + dir * cast_length
If you have any further questions, let me know.
EDIT:
The issue experienced in this post was caused by floating-point precision. For some reason, the issue is more apparent with CollisionShape3D
than with CSGBox3D
. The fix was to add a margin (commonly denoted as epsilon) to the bounce position.
The solution makes use of a utility script to draw the laser for debugging purposes. You can find this script (and the setup for it) here. Alternatively, the OPs line()
function is available in full further down in this thread.