class_name Socket extends Node3D @export var snapPoint: Marker3D @export var snapArea: Area3D @export var snapDistance: float = 0.5 @export var lockOnSnap: bool = true var candidate: Pickupable = null var snappedObject: Pickupable = null func _ready() -> void: if snapArea: snapArea.body_entered.connect(onBodyEntered) snapArea.body_exited.connect(onBodyExited) func isInRange(object: Pickupable) -> bool: if object == null: return false if object.snapAnchor == null: return false var distance := object.snapAnchor.global_position.distance_to( snapPoint.global_position ) return distance <= snapDistance func getSnapTransform(object: Pickupable) -> Transform3D: var objectTransform := object.body.global_transform var anchorTransform := object.snapAnchor.global_transform # Where is the SnapAnchor relative to the Rigidbody? var anchorOffset := objectTransform.affine_inverse() * anchorTransform # We want the SnapAnchor to have exactly the same # transform as the Socket's SnapPoint. return snapPoint.global_transform * anchorOffset.affine_inverse() func snapObject(object: Pickupable): if not isInRange(object): return var targetTransform := getSnapTransform(object) var body := object.body body.linear_velocity = Vector3.ZERO body.angular_velocity = Vector3.ZERO body.global_transform = targetTransform if lockOnSnap: body.freeze = true snappedObject = object candidate = null func onBodyEntered(body: Node3D): if body is RigidBody3D and body.get_parent() is Pickupable: var pickup = body.get_parent() as Pickupable pickup.currentSocket = self candidate = pickup func onBodyExited(body: Node3D): if body is RigidBody3D and body.get_parent() is Pickupable: var pickup = body.get_parent() as Pickupable if pickup.currentSocket == self: pickup.currentSocket = null if candidate == pickup: candidate = null