RayCast3D problem

Godot Version

4.6.2

Question

Trying to use RayCast3D for laser weapons - you hold LMB and then the function does “Raycast” stuff in _physics_process, detecting some collisions. But Idk why its not working. All the “Enemies”, “World”, “Objects” are in 1, 2, 3 layers so it can detect them, but still nothing happens (“Debil” doesnt appear in the Output).
Camera and weapon are facing -Z direction.

func _trace_laser(_bullets: int, delta: float) -> void:
	if not camera:
		push_error("Camera not found!")
		return

	var damage_tick_interval = 0.5

	if not laser_raycast:
		laser_raycast = RayCast3D.new()
		laser_raycast.collision_mask = 7
		laser_raycast.collide_with_bodies = true
		laser_raycast.collide_with_areas = false
		laser_raycast.add_exception(player_controller)
		camera.add_child(laser_raycast)
		damage_timer = 0.0

	var from = camera.global_transform.origin
	var to = from + camera.global_transform.basis.z * -current_weapon.laser_range
	laser_raycast.target_position = laser_raycast.to_local(to)
	laser_raycast.enabled = true

	if laser_raycast.is_colliding():
		print("Debil")
		damage_timer += delta
		if damage_timer >= damage_tick_interval:
			attack_type_handler.apply_attack(
				get_weapon_data().attack_types,
				camera,
				laser_raycast.get_collider(),
				laser_raycast.get_collision_point(),
				laser_raycast.get_collision_normal()
			)
			damage_timer = 0.0
	else:
		damage_timer = 0.0

With “Visible Collision Shapes” enabled, I can see that my RayCast3D is properly rotated in the right direction, so thats not the issue.

When Im moving:

Does “Debil” show in the Output?

Jesus, forgot to add… No, ofc its not showing

Need a screenshot of its configuration in the Inspector then.

Im trying to create it “on the fly” in memory. Its not a separate node in my tree… Or did I misunderstand you?

I see that now. Then need to see all the code, not just the one function.

WeaponController (main):

class_name WeaponController extends Node


@export var player_controller: PlayerController
@export var camera: CameraEffects
@export var weapon_model_parent: Node3D
@export var weapon_state_chart: StateChart
@export var base_weapon_projectile: PackedScene
@export var attack_type_handler: AttackTypeHandler
@export var damage_component: DamageComponent
@export var weapon_effects: WeaponEffects

var current_weapon: BaseWeapon
var current_weapon_model: Node3D

#...#

var laser_raycast: RayCast3D = null
var damage_timer: float = 0.0
var is_firing_laser: bool = false

func _physics_process(delta: float) -> void:
	if is_firing_laser and current_weapon is LaserWeapon:
		current_weapon.perform_attack(self, 0, delta)
	elif laser_raycast:
		_stop_laser()

func fire_weapon() -> void:
	if current_weapon is LaserWeapon:
		current_weapon.perform_attack(self, 0, 0.0)
		return

#...#

func _trace_laser(_bullets: int, delta: float) -> void:
	if not camera:
		push_error("Camera not found!")
		return

	var damage_tick_interval = 0.5

	if not laser_raycast:
		laser_raycast = RayCast3D.new()
		laser_raycast.collision_mask = 7
		laser_raycast.collide_with_bodies = true
		laser_raycast.collide_with_areas = false
		laser_raycast.add_exception(player_controller)
		camera.add_child(laser_raycast)
		damage_timer = 0.0

	var from = camera.global_transform.origin
	var to = from + camera.global_transform.basis.z * -current_weapon.laser_range
	laser_raycast.target_position = laser_raycast.to_local(to)
	laser_raycast.enabled = true

	if laser_raycast.is_colliding():
		print("Debil")
		damage_timer += delta
		print(damage_timer)
		if damage_timer >= damage_tick_interval:
			attack_type_handler.apply_attack(
				get_weapon_data().attack_types,
				camera,
				laser_raycast.get_collider(),
				laser_raycast.get_collision_point(),
				laser_raycast.get_collision_normal()
			)
			damage_timer = 0.0
	else:
		damage_timer = 0.0

func _stop_laser() -> void:
	if laser_raycast:
		laser_raycast.enabled = false
		damage_timer = 0.0
		is_firing_laser = false

LaserWeapon resource:

class_name LaserWeapon extends BaseWeapon


@export var laser_range: int
@export_range(0.1, 2.0) var overheat_buildup: float
@export_range(1.0, 10.0) var overheat_threshold: float
@export_range(0.5, 3.0) var cooldown_rate: float


func perform_attack(_controller: WeaponController, _bullets: int, _delta: float) -> void:
	_controller._trace_laser(_bullets, _delta)

FiringState logic from my StateMachine:

func _on_firing_state_physics_processing(_delta: float) -> void:
	if not weapon_controller.has_ammo():
		weapon_controller.weapon_state_chart.send_event("onEmpty")

	if weapon_controller.current_weapon.is_automatic:
		if Input.is_action_pressed("primary_fire"):
			if weapon_controller.can_fire():
				weapon_controller.fire_weapon() #thats why Im added three lines here
		else:
			weapon_controller.weapon_state_chart.send_event("onIdle")
	else:
		weapon_controller.fire_weapon()
		weapon_controller.weapon_state_chart.send_event("onIdle")

Node tree (just for sure):

So you’re sure this matches the boolean values set on the layers of the objects you’re colliding with? It should be colliding with any default object like a wall or floor.

FWIW, I always recommend making the object, getting it to work, then re-creating it in code if you have a reason for that - like a more complex node. TBH there is no benefit I can see for making it in code. Even if you don’t need it all the time, make it a scene, configure it, and instantiate it.

You should use the function set_collision_mask_value(7, true)instead because the actual mask is probably a binary number like

10000010000000000000100000000101

with bits 1, 7 20 etc, you are saying mask =7 which is actually like

11100000 in binary.