terminalCode Example
Sandbox: Physgun — grab, freeze, launch, and pull mechanics
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
- _state is a GrabState struct — set to default to release
- _preventReselect prevents immediately re-grabbing after release
- Spinning uses Input.AnalogLook and sets AbsorbMouseInput = true to prevent camera movement
- IPhysgunEvent.OnPhysgunGrab fires on the grabbed object — implement it to block grabs (e.g. Ownable)
- Launch force is 2000f applied as an impulse in the aim direction
Was this helpful?