extends Camera3D ## Owns dart input. The camera does the raycast because the raycast IS a ## camera operation — projecting a screen point into the world. ## ## The dart exposes begin_drag / update_drag / release_drag and never looks ## for the camera itself. This script decides who to call them on. const RAY_LENGTH := 1000.0 var _held_dart: Dart = null func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseButton: var mb := event as InputEventMouseButton if mb.button_index != MOUSE_BUTTON_LEFT: return if mb.pressed: _try_grab(mb.position) else: _release() elif event is InputEventMouseMotion and _held_dart != null: _held_dart.update_drag(self, (event as InputEventMouseMotion).position) func _try_grab(screen_pos: Vector2) -> void: var hit := _raycast(screen_pos) if hit.is_empty(): return var body = hit.get("collider") if body is Dart: _held_dart = body as Dart # NOTE: two args, not three. The old start_drag(camera, mouse_pos, point) # passed mouse_pos but never used it — begin_drag only needs the hit point. _held_dart.begin_drag(self, hit["position"]) func _release() -> void: if _held_dart == null: return _held_dart.release_drag() _held_dart = null func _raycast(screen_pos: Vector2) -> Dictionary: var params := PhysicsRayQueryParameters3D.create( project_ray_origin(screen_pos), project_ray_origin(screen_pos) + project_ray_normal(screen_pos) * RAY_LENGTH ) if _held_dart != null: params.exclude = [_held_dart.get_rid()] return get_world_3d().direct_space_state.intersect_ray(params)