PlayerPickupHandler.cs
using Sandbox;
/// <summary>
/// Add to your player GameObject alongside InventoryComponent.
/// Handles the interact key (default "use") to pick up ItemPickup objects,
/// open StorageContainers, and open CraftingStations via raycasts.
/// </summary>
public sealed class PlayerPickupHandler : Component
{
/// <summary>Maximum interaction distance in world units.</summary>
[Property] public float MaxDistance { get; set; } = 200f;
/// <summary>Radius of the interaction trace sphere.</summary>
[Property] public float TraceRadius { get; set; } = 12f;
/// <summary>Eye height offset from the player's transform position.</summary>
[Property] public float EyeHeight { get; set; } = 56f;
/// <summary>Vertical pixel offset applied to the screen-center ray. Use to compensate for camera positioning.</summary>
[Property] public float AimOffsetY { get; set; } = 0f;
/// <summary>Input action name that triggers interaction. Defaults to "use" (E key in default S&box bindings).</summary>
[Property] public string InteractInput { get; set; } = "use";
protected override void OnUpdate()
{
if ( !Input.Pressed( InteractInput ) ) return;
var inventory = Components.Get<InventoryComponent>();
if ( inventory == null ) return;
var cam = Scene.Camera;
if ( cam == null ) return;
// Trace from camera through screen center (aligns with crosshair)
var screenCenter = new Vector2( Screen.Width * 0.5f, Screen.Height * 0.5f + AimOffsetY );
var camRay = cam.ScreenPixelToRay( screenCenter );
var tr = Scene.Trace.Ray( camRay, MaxDistance )
.Radius( TraceRadius )
.IgnoreGameObjectHierarchy( GameObject )
.Run();
if ( tr.Hit && tr.GameObject != null )
{
var pickup = tr.GameObject.Components.Get<ItemPickup>();
if ( pickup != null && pickup.Pickup( inventory ) )
return;
var container = tr.GameObject.Components.Get<StorageContainer>();
if ( container != null )
{
OpenContainer( container );
return;
}
var station = tr.GameObject.Components.Get<CraftingStation>();
if ( station != null )
{
CraftingStation.OnStationOpened?.Invoke( station );
return;
}
}
// Fallback: trace from player's eye position in the camera's forward direction
var eyePos = Transform.Position + Vector3.Up * EyeHeight;
var tr2 = Scene.Trace.Ray( eyePos, eyePos + camRay.Forward * MaxDistance )
.Radius( TraceRadius )
.IgnoreGameObjectHierarchy( GameObject )
.Run();
if ( tr2.Hit && tr2.GameObject != null )
{
var pickup2 = tr2.GameObject.Components.Get<ItemPickup>();
if ( pickup2 != null )
{
pickup2.Pickup( inventory );
return;
}
var container2 = tr2.GameObject.Components.Get<StorageContainer>();
if ( container2 != null )
{
OpenContainer( container2 );
return;
}
var station2 = tr2.GameObject.Components.Get<CraftingStation>();
if ( station2 != null )
CraftingStation.OnStationOpened?.Invoke( station2 );
}
}
private void OpenContainer( StorageContainer container )
{
StorageContainer.OnOpenRequested?.Invoke( container );
}
}