Moving/Picking up interactable handle with object

Godot Version

4.4.1 with XR Tools

Question

I am working on a spanner/bolt type affair and im trying to figure out how to essentially move the bolt when the spanner is near and the player moves their controller

Ive tried a few things but the simplest to me seemed like using an interactable hinge and then looking at calling “get_parent().pick_up” directly when the spanner object enters an area child of the hinge but it looks as if i need Function_Pickup for this which itself requries the controller nodes and its all very much from a player hands perspective

What would be the recommended approach to manipulating objects with player held objects in such a fashion? I had a look at the sniper rifle scene as its doing something kind of similar with the bolt but again its player hands directly - i need a held object to then interact with the hinge itself (if thats even a viable route forwards)

So if I understand correctly, in this scenario the spanner is the object you are holding in the players hand, and when the head of the spanner comes near the bolt, you want the turning of the spanner to manipulate the bolt.

What I would do in such a situation is to put an area node alongside the bolt that is set to detect the spanner. Keep in mind that you can set the physics layer of the spanner when picked up to something specific for the spanner, so you can just detect the spanner with that area.

Then while the spanner is within that area, you calculate by how much the spanner was turned since last frame, and rotate the bolt accordingly.

Thank you so much! I apologize for wasting your time with stupid questions, I just wanted to ensure i wasn’t overlooking any pre-existing nodes/scenes in XR Tools in my implementation; I do however suspect this isn’t leveraging your incredible work in XR tools - but i am probably barking up the wrong tree here…

In my example, ive placed a “nut & bolt” at the center of an instance of “smooth_wheel.tscn” - added an interactable handle over the “nut & bolt” and set the hinge limits to 5x360 degrees - i can “unscrew” the bolt with my hand and watch it spin as i move, no problemo! Now i just need to move this capability from the hand itself to the end of the spanner

I think that what im trying to do is treat certain objects as adjunct “hands” - something like:

A). my hand is now being treated as if it is at the the end of the object being held (logically but not visually)
OR
B) the object (whist being held) is actually a “3rd” hand (but because your hand holding the object is indisposed, you will still experience things as if you only have x2 hands)

Ultimately goal is to expand the scope/capability of many of the nodes you’ve already kindly provided such as interactable_hinges, joysticks, sliders etc. by enabling them to interact with both hands and specific held objects

I would very much appreciate your musings/insights on the matter but i appreciate you must be a very busy man and i apologize for my stream-of-consciousness ramblings! In any case, thank you for all the work and effort you’ve shared <3

Problem solved, sharing the below script for any use it may be for anyone else (though its not been well tested!):

Create a scene like:
Node3D (named “ToolGrabber”)
- Area3D (named “GrabArea”)
- Collision3D

set GrabArea collision layer to 19 (this enables it to grab interactable handles) and save this scene as “ToolGrabber.tscn” - you can then instance it as a child of “pickable” and whilst holding the tool you can move it into range of an interactable handle and hold trigger to grab via the tool


@tool
class_name XRToolsToolGrabber
extends Node3D


## Tool Grabber Component
##
## Add this as a child of any XRToolsPickable to enable it to grab
## interactable handles while being held by the player.
## 
## Requires an Area3D child named "GrabArea" with a CollisionShape3D


## Distance at which to grab
@export var grab_distance : float = 0.05: set = _set_grab_distance

## Collision mask for handles
@export_flags_3d_physics var grab_mask : int = 0b0000_0000_0100_0000_0000_0000_0000_0000: set = _set_grab_mask


## Currently grabbed handle
var grabbed_handle : XRToolsInteractableHandle = null

## Parent pickable
var pickable : XRToolsPickable

## Required by pickable.gd grab system - false since tool grabs at contact
var picked_up_ranged : bool = false

# Store initial transform relative to handle
var _grab_transform : Transform3D

# References to child nodes
@onready var _grab_area : Area3D = $GrabArea
@onready var _grab_collision : CollisionShape3D = $GrabArea/CollisionShape3D


func _ready() -> void:
	# Update collision settings
	if _grab_collision:
		_grab_collision.shape.radius = grab_distance
	if _grab_area:
		_grab_area.collision_layer = 0
		_grab_area.collision_mask = grab_mask
	
	if Engine.is_editor_hint():
		return
	
	# Get parent pickable
	pickable = get_parent() as XRToolsPickable
	if not pickable:
		push_error("ToolGrabber must be child of XRToolsPickable")
		return
	
	# Connect to parent signals
	pickable.picked_up.connect(_on_picked_up)
	pickable.dropped.connect(_on_dropped)
	
	# Start disabled
	set_physics_process(false)


func _physics_process(_delta: float) -> void:
	# Check if pickable and handle are valid
	if not is_instance_valid(pickable) or not is_instance_valid(grabbed_handle):
		set_physics_process(false)
		return
	
	# Only process when parent is held and handle is grabbed
	if not pickable.is_picked_up() or not grabbed_handle.is_picked_up():
		set_physics_process(false)
		return
	
	# Update handle position to follow tool grabber
	grabbed_handle.global_transform = global_transform * _grab_transform


func _on_picked_up(_pickable: XRToolsPickable) -> void:
	if not is_instance_valid(pickable):
		return
		
	# Listen for controller input
	var controller := pickable.get_picked_up_by_controller()
	if controller and not controller.button_pressed.is_connected(_on_button_pressed):
		controller.button_pressed.connect(_on_button_pressed)
		controller.button_released.connect(_on_button_released)


func _on_dropped(_pickable: XRToolsPickable) -> void:
	# Release any grabbed handle
	if grabbed_handle:
		grabbed_handle.let_go(self, Vector3.ZERO, Vector3.ZERO)
		grabbed_handle = null
	
	if not is_instance_valid(pickable):
		return
	
	# Disconnect controller signals
	var controller := pickable.get_picked_up_by_controller()
	if controller:
		if controller.button_pressed.is_connected(_on_button_pressed):
			controller.button_pressed.disconnect(_on_button_pressed)
		if controller.button_released.is_connected(_on_button_released):
			controller.button_released.disconnect(_on_button_released)
	
	set_physics_process(false)


func _on_button_pressed(button: String) -> void:
	if button != "trigger_click" or grabbed_handle or not _grab_area:
		return
	
	# Find closest handle in area
	var closest : XRToolsInteractableHandle = null
	var min_dist := INF
	
	for body in _grab_area.get_overlapping_bodies():
		var handle := body as XRToolsInteractableHandle
		if handle and handle.can_pick_up(self):
			var dist := global_position.distance_to(handle.global_position)
			if dist < min_dist:
				min_dist = dist
				closest = handle
	
	# Grab the closest handle
	if closest:
		grabbed_handle = closest
		# Store the relative transform
		_grab_transform = global_transform.inverse() * grabbed_handle.global_transform
		grabbed_handle.pick_up(self)
		set_physics_process(true)


func _on_button_released(button: String) -> void:
	if button == "trigger_click" and grabbed_handle:
		grabbed_handle.let_go(self, Vector3.ZERO, Vector3.ZERO)
		grabbed_handle = null
		set_physics_process(false)


func _set_grab_distance(value: float) -> void:
	grab_distance = value
	if is_inside_tree() and _grab_collision:
		_grab_collision.shape.radius = value


func _set_grab_mask(value: int) -> void:
	grab_mask = value
	if is_inside_tree() and _grab_area:
		_grab_area.collision_mask = value


## Methods required for compatibility with pickup system
func can_pick_up(_by: Node3D) -> bool:
	return false  # Tool grabber cannot be picked up


func is_picked_up() -> bool:
	return false  # Tool grabber is never "picked up"


func pick_up(_by: Node3D) -> void:
	pass  # Tool grabber ignores pickup attempts


func let_go(_by: Node3D, _linear_velocity: Vector3, _angular_velocity: Vector3) -> void:
	pass  # Tool grabber ignores let_go attempts

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.