terminalCode Example

Sandbox: Physgun — grab, freeze, launch, and pull mechanics

calendar_today May 4, 2026 schedule ~1 min read person patrickjr verified 50

Physgun — Grab, Freeze, Launch, and Pull Mechanics

The Physgun (Physgun.cs) is a BaseCarryable that lets players grab, move, freeze, and launch physics objects. It uses a GrabState struct to track the currently held object.

Core grab/release loop

CSHARP
public partial class Physgun : BaseCarryable
{
    static float PullForce => 1000.0f;
    static float LaunchForce => 2000.0f;
    static float PullDistance => 200.0f; // Distance at which pulled objects snap to grab

    public override void OnControl( Player player )
    {
        var aim = AimTransform;

        if ( _state.IsValid() ) // Currently holding something
        {
            if ( Input.Down( "attack2" ) )
            {
                // Right-click while holding = freeze
                Freeze( _state.Body );
                _state = default;
                ViewModel?.PlaySound( ReleasedSound );
                return;
            }

            if ( !Input.Down( "attack1" ) )
            {
                // Release on left-click up
                _state = default;
                ViewModel?.PlaySound( ReleasedSound );
                return;
            }

            // Mouse wheel adjusts grab distance
            if ( !Input.MouseWheel.IsNearZeroLength )
            {
                var state = _state;
                state.GrabDistance += Input.MouseWheel.y * 20.0f;
                state.GrabDistance = state.GrabDistance.Clamp( 50f, 4096f );
                _state = state;
            }
        }
        else if ( Input.Pressed( "reload" ) )
        {
            // Reload = unfreeze hovered object
            if ( _stateHovered.IsValid() )
                UnfreezeAll( _stateHovered.Body );
        }
    }
}

Spinning grabbed objects

CSHARP
// Hold Use key while grabbed to spin with mouse
if ( _isSpinning )
{
    // AbsorbMouseInput returns true when spinning — camera doesn't move
    angles = default;
    _spinRotation *= Rotation.From( Input.AnalogLook );
}

Freeze / Unfreeze

CSHARP
[Rpc.Host]
void Freeze( PhysicsBody body )
{
    if ( !body.IsValid() ) return;

    // Fire IPhysgunEvent — Ownable uses this to check ownership
    var grabEvent = new IPhysgunEvent.GrabEvent { Grabber = Rpc.Caller };
    body.GameObject.RunEvent<IPhysgunEvent>( x => x.OnPhysgunGrab( grabEvent ) );
    if ( grabEvent.Cancelled ) return;

    body.MotionEnabled = false;
    // Play freeze effect
}

void UnfreezeAll( PhysicsBody body )
{
    // Recursively unfreeze all connected bodies
    foreach ( var connected in GetConnectedBodies( body ) )
        connected.MotionEnabled = true;
}

Pull mechanic (secondary fire)

CSHARP
// Pull: fires a beam that drags objects toward the player
if ( isPulling && !_state.IsValid() )
{
    var tr = Scene.Trace.Ray( aim.Position, aim.Position + aim.Forward * 4096f )
        .IgnoreTags( "player" )
        .Run();

    if ( tr.Hit && tr.Body.IsValid() )
    {
        var dir = (aim.Position - tr.Body.MassCenter).Normal;
        tr.Body.ApplyImpulse( dir * PullForce * tr.Body.Mass * Time.Delta );
    }
}

Key points

Was this helpful?