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