class_name HoldComponent extends Node3D ## Component for managing physics object holding signal held signal dropped const ROTATION_SCALE_FACTOR := 10.0 * TAU ## Held object position lerp acceleration. @export var hold_accel := 20.0 ## Held object position velocity scale. @export var hold_speed := 12.0 ## The maximum distance the object may be from the hold point before breaking the hold. @export var max_distance := 3.0 ## Temporary linear damping for held objects @export var hold_damping := 8.0 ## Impulse force when throwing an object @export var throw_force := 100.0 ## Temporary collision layer. @export_flags_3d_physics var hold_collision_layer := 0b01000000 ## Temporary collision mask. @export_flags_3d_physics var hold_collision_physics := 0b01000001 ## The object currently being held. var _held_object: RigidBody3D var _original_damping: float var _original_collision_layer: int var _original_collision_mask: int @onready var interact_ray: RayCast3D = %InteractRay @onready var hold_point: Marker3D = %HoldPoint func attach(prop: RigidBody3D, hold_distance: float) -> void: hold_point.position = Vector3(0, 0, -hold_distance) hold_point.global_basis = global_basis _held_object = prop _original_damping = prop.linear_damp prop.linear_damp = hold_damping _original_collision_layer = prop.collision_layer prop.collision_layer = hold_collision_layer _original_collision_mask = prop.collision_mask prop.collision_mask = hold_collision_physics held.emit() func drop() -> void: _held_object.linear_damp = _original_damping _held_object.collision_layer = _original_collision_layer _held_object.collision_mask = _original_collision_mask _held_object = null dropped.emit() func throw() -> void: var impulse := -global_basis.z.normalized() * throw_force _held_object.apply_central_impulse(impulse) drop() func rotate_object(relative_motion: Vector2) -> void: hold_point.rotate( Vector3.UP, relative_motion.x * GameSettings.object_rotation_sensitivity_x / ROTATION_SCALE_FACTOR ) hold_point.rotate( Vector3.RIGHT, relative_motion.y * GameSettings.object_rotation_sensitivity_y / ROTATION_SCALE_FACTOR ) func holding_object() -> bool: return !!_held_object func _unhandled_input(event: InputEvent) -> void: var mouse_motion := event as InputEventMouseMotion if ( mouse_motion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED and holding_object() and Input.is_action_pressed("rotate_object") ): rotate_object(mouse_motion.relative) get_viewport().set_input_as_handled() func _process_hold_controls() -> void: if Input.is_action_just_pressed("interact"): drop() elif Input.is_action_just_pressed("fire"): throw() func _physics_process(delta: float) -> void: # Object hold action logic if holding_object(): _process_hold_controls() # Held object logic if not holding_object(): return var diff := hold_point.global_position - (_held_object.global_position) if diff.length() > max_distance: drop() return var weight := 1 - exp(-hold_accel * delta) _held_object.linear_velocity = _held_object.linear_velocity.lerp(diff * hold_speed, weight) _held_object.global_basis = _held_object.global_basis.slerp(hold_point.global_basis, weight)