3D node looses _on_input_event() focus if it moves too fast

Godot Version

4.6.3

Question

I was trying out the recommendations from here: Best practice: where to handle _unhandled_input() - #2 by artemisia and moved some of my logic directly to the input event handler of the actual node.

One piece of that logic is to click on the node to select it and then have it follow the cursor to move it around. In general that is working but the problem is that when I move the cursor too fast, I am loosing the focus on the node and therefore it stops moving.

Is there a way to prevent this so that the input events are solely handled by that node until the user clicks the button again and it gets released?

Below my current script:

func _on_input_event(camera: Node, event: InputEvent, event_position: Vector3, normal: Vector3, shape_idx: int) -> void:
	if event is InputEventMouseButton and event.is_pressed():
		if event.button_index == MOUSE_BUTTON_LEFT:
			Globals.skipMove = false
	
			if Globals.selectedNode == self:
				self.showCrossMarker(false)
				Globals.selectedNode = null
			else:
				Globals.selectedNode = self
				
	if event is InputEventMouse:
		if !Globals.skipMove:
			self.showCrossMarker(true)
			
			var currentY = self.global_position.y
			var pos = Vector3(event_position)
			
			self.global_position = pos
			self.global_position.y = currentY

I’d recommend checking out a 2D drag-and-drop tutorial. But the simple answer is to have an is_dragging variable, and when it is true, update the location of the object every frame in the _process() function.

The CollisionObject3D.input_event signal only works while event happens inside the object collision shapes, if the mouse exits the object then it won’t be registered.

You can use that signal to know when the object is clicked on but you’ll need to process the dragging in another place like Node._input() for example.

Thanks @dragonforge-dev - I have reshaped the functions and now it works like a charm and also looks way better :slight_smile:

func _on_input_event(camera: Node, event: InputEvent, event_position: Vector3, normal: Vector3, shape_idx: int) -> void:
	if event is InputEventMouseButton:
		if event.is_pressed():
			if event.button_index == MOUSE_BUTTON_LEFT:
				Globals.skipMove = false
				self.isDragging = true
				self.showCrossMarker(true)
		
			if event.button_index == MOUSE_BUTTON_RIGHT:
				var object_of_interest_3D_position : Vector3 = self.global_position
				var HUDposition = get_viewport().get_camera_3d().unproject_position( object_of_interest_3D_position )
				var rect: Rect2i = Rect2i(HUDposition.x, HUDposition.y, 800, 800)
				%PopupMenu.popup(rect)
				
		if event.is_released():
			if event.button_index == MOUSE_BUTTON_LEFT:
				self.isDragging = false
				self.showCrossMarker(false)

and

func _process(delta: float) -> void:	
	# Handle drag movement
	if self.isDragging:
		var camera = get_viewport().get_camera_3d()
		if camera:
			var mousePos = get_viewport().get_mouse_position()
			
			# Create a ray from camera through mouse position
			var rayOrigin = camera.project_ray_origin(mousePos)
			var rayNormal = camera.project_ray_normal(mousePos)
			
			# Calculate intersection with the plane at the node's Y level
			var targetY = self.global_position.y
			var t = (targetY - rayOrigin.y) / rayNormal.y if rayNormal.y != 0 else 0
			var newPosition = rayOrigin + rayNormal * t
			newPosition.y = targetY
			
			var snapper = Vector3(0.5,0,0.5)
			var snappedPos = newPosition.snapped(snapper)
			
			if(!self.global_position.is_equal_approx(snappedPos)):
				self.global_position = snappedPos
				self.global_position.y = targetY