ObjectGrabber.cs

A component that lets a player pick up, hold, drop and throw physics objects in the scene. It traces a ray from the camera to find a rigidbody, stores the grabbed GameObject and Rigidbody, moves it toward a hold position each update, and applies velocity to throw.

Native Interop
using Sandbox;

public sealed class ObjectGrabber : Component
{
	[Property] public float MaxReachDistance { get; set; } = 300f;
	[Property] public float HoldDistance { get; set; } = 150f;
	[Property] public float ThrowForce { get; set; } = 1200f;

	private GameObject _heldObject;
	private Rigidbody _heldRigidbody;

	protected override void OnUpdate()
	{
		if ( IsProxy ) return;

		if ( Input.Pressed( "use" ) || Input.Pressed( "e" ) )
		{
			if ( _heldObject.IsValid() )
			{
				DropObject();
			}
			else
			{
				TryGrabObject();
			}
		}

		if ( Input.Pressed( "attack1" ) && _heldObject.IsValid() )
		{
			ThrowObject();
		}

		if ( _heldObject.IsValid() )
		{
			UpdateHeldObjectPosition();
		}
	}

	private void TryGrabObject()
	{
		var cam = Components.Get<CameraComponent>() ?? Scene.GetAllComponents<CameraComponent>().FirstOrDefault();
		if ( !cam.IsValid() ) return;

		var ray = cam.Transform.World.Forward;
		var start = cam.WorldPosition;
		var end = start + ray * MaxReachDistance;

		var tr = Scene.Trace.Ray( start, end )
			.IgnoreGameObject( GameObject )
			.Run();

		if ( tr.Hit && tr.GameObject.IsValid() )
		{
			var rootObj = tr.GameObject.Root;
			var rb = rootObj.Components.Get<Rigidbody>() ?? tr.GameObject.Components.Get<Rigidbody>();

			if ( rb.IsValid() && !rootObj.Tags.Has( "player" ) )
			{
				_heldObject = rootObj;
				_heldRigidbody = rb;
				_heldRigidbody.MotionEnabled = true;
			}
		}
	}

	private void UpdateHeldObjectPosition()
	{
		var cam = Components.Get<CameraComponent>() ?? Scene.GetAllComponents<CameraComponent>().FirstOrDefault();
		if ( !cam.IsValid() ) return;

		var targetPos = cam.WorldPosition + cam.Transform.World.Forward * HoldDistance;
		var dir = targetPos - _heldObject.WorldPosition;

		if ( _heldRigidbody.IsValid() )
		{
			_heldRigidbody.Velocity = dir * 10f;
		}
		else
		{
			_heldObject.WorldPosition = Vector3.Lerp( _heldObject.WorldPosition, targetPos, Time.Delta * 15f );
		}
	}

	private void DropObject()
	{
		_heldObject = null;
		_heldRigidbody = null;
	}

	private void ThrowObject()
	{
		if ( !_heldObject.IsValid() ) return;

		var cam = Components.Get<CameraComponent>() ?? Scene.GetAllComponents<CameraComponent>().FirstOrDefault();
		var throwDir = cam.IsValid() ? cam.Transform.World.Forward : Transform.World.Forward;

		if ( _heldRigidbody.IsValid() )
		{
			_heldRigidbody.Velocity = throwDir * ThrowForce;
		}

		CameraJuice.Instance?.Shake( 8f );

		_heldObject = null;
		_heldRigidbody = null;
	}
}