Skip to main content
s&box uses a scene-level PhysicsWorld accessible via Scene.PhysicsWorld. You create dynamic objects by attaching a Rigidbody component alongside one or more collider components. For spatial queries you use the fluent trace builder returned by Scene.PhysicsWorld.Trace.

Physics traces

A trace (or raycast) sweeps a shape through the physics world and returns everything it hits. The builder pattern lets you compose exactly the query you need.

Ray trace

The simplest trace casts an infinitely thin ray from one point to another:
var tr = Scene.PhysicsWorld.Trace
    .Ray( WorldPosition, WorldPosition + WorldRotation.Forward * 1000f )
    .Run();

if ( tr.Hit )
{
    Log.Info( $"Hit {tr.Body?.GameObject?.Name} at {tr.HitPosition}" );
    Log.Info( $"Surface: {tr.Surface?.ResourceName}" );
    Log.Info( $"Normal: {tr.Normal}" );
}

Sphere sweep

var tr = Scene.PhysicsWorld.Trace
    .Sphere( radius: 16f, from: start, to: end )
    .Run();

Box sweep

var extents = new Vector3( 32, 32, 64 );
var tr = Scene.PhysicsWorld.Trace
    .Box( extents, from: start, to: end )
    .Run();

Capsule sweep

var capsule = new Capsule( Vector3.Zero, Vector3.Up * 72f, 16f );
var tr = Scene.PhysicsWorld.Trace
    .Capsule( capsule, from: start, to: end )
    .Run();

RunAll — multiple hits

var hits = Scene.PhysicsWorld.Trace
    .Ray( start, end )
    .RunAll();

foreach ( var hit in hits )
{
    Log.Info( $"Hit {hit.Body?.GameObject?.Name} at fraction {hit.Fraction}" );
}

Filtering the trace

Chain filter methods before calling Run():
var tr = Scene.PhysicsWorld.Trace
    .Ray( start, end )
    .IgnoreStatic()          // Skip static world geometry
    .HitTriggers()           // Include trigger volumes
    .Run();
MethodEffect
IgnoreStatic()Exclude static bodies
IgnoreDynamic()Exclude dynamic bodies
IgnoreKeyframed()Exclude keyframed bodies
HitTriggers()Include trigger shapes in results
HitTriggersOnly()Hit only trigger shapes

PhysicsTraceResult fields

FieldTypeDescription
HitboolWhether the trace hit anything
StartedSolidboolThe trace started inside a solid
HitPositionVector3World position of the hit
EndPositionVector3Final position after travel
NormalVector3Surface normal at the hit point
FractionfloatHow far [0..1] the trace travelled
BodyPhysicsBodyThe physics body that was hit
ShapePhysicsShapeThe specific shape that was hit
SurfaceSurfaceMaterial/surface properties
Tagsstring[]Tags on the hit shape
DistancefloatDistance between start and end

Rigidbody component

Add a Rigidbody component to give a GameObject physics simulation. It requires at least one collider component (e.g. BoxCollider, SphereCollider, CapsuleCollider) on the same or a child GameObject.

Velocity and forces

var rb = Components.Get<Rigidbody>();

// Read or set linear velocity directly
rb.Velocity = Vector3.Up * 500f;

// Read or set angular velocity (degrees per second around each axis)
rb.AngularVelocity = new Vector3( 0, 0, 90f );

// Apply a continuous force (scaled by physics delta time)
rb.ApplyForce( Vector3.Up * 9800f );

// Apply force at a world-space position (creates torque)
rb.ApplyForceAt( worldPoint, Vector3.Right * 500f );

// Apply angular force (torque)
rb.ApplyTorque( new Vector3( 0, 0, 1000f ) );

// Apply an instantaneous impulse (not scaled by delta time)
rb.ApplyImpulse( Vector3.Up * 300f );

// Impulse at a world position
rb.ApplyImpulseAt( worldPoint, Vector3.Forward * 200f );

Mass and damping

// Override mass (0 = calculate from shapes)
rb.MassOverride = 50f;

// How quickly linear motion decays
rb.LinearDamping = 0.1f;

// How quickly rotation decays
rb.AngularDamping = 0.5f;

// Enable or disable gravity for this body
rb.Gravity = true;
rb.GravityScale = 2.0f; // double gravity

Sleeping

Physics bodies automatically sleep after being still to save performance:
// Force wake-up
rb.Sleeping = false;

// Force to sleep immediately
rb.Sleeping = true;

Accessing the underlying PhysicsBody

var body = rb.PhysicsBody;
if ( body.IsValid() )
{
    var velocity = body.Velocity;
    var mass     = body.Mass;
    var bounds   = body.GetBounds(); // BBox in world space
}

PhysicsBody API

You can also work with PhysicsBody directly for lower-level control:
var body = rb.PhysicsBody;

// Position and rotation
body.Position = new Vector3( 0, 0, 100f );
body.Rotation = Rotation.FromYaw( 45f );

// Velocity
body.Velocity        = Vector3.Forward * 200f;
body.AngularVelocity = Vector3.Up * 180f;

// Forces and impulses
body.ApplyForce( Vector3.Up * 9800f );
body.ApplyImpulse( Vector3.Forward * 500f );
body.ApplyTorque( Vector3.Up * 100f );

// Gravity
body.GravityEnabled = false;
body.GravityScale   = 0.5f;

// Damping
body.LinearDamping  = 0.05f;
body.AngularDamping = 0.1f;

// CCD — enable for fast-moving objects like bullets
body.EnhancedCcd = true;

Collision detection

Implement ICollisionListener on a component to receive collision callbacks:
public sealed class DamageOnCollision : Component, Component.ICollisionListener
{
    void ICollisionListener.OnCollisionStart( Collision collision )
    {
        var speed  = collision.Contact.NormalSpeed;
        var other  = collision.Other.GameObject;
        var point  = collision.Contact.Point;
        var normal = collision.Contact.Normal;

        if ( speed > 300f )
        {
            Log.Info( $"Hard collision with {other?.Name} at {speed:0}u/s" );
        }
    }

    void ICollisionListener.OnCollisionUpdate( Collision collision ) { }

    void ICollisionListener.OnCollisionEnd( CollisionStop stop ) { }
}

Shape management

Shapes define the collision geometry of a PhysicsBody. You can add shapes at runtime:
var body = new PhysicsBody( Scene.PhysicsWorld );

// Sphere
body.AddSphereShape( center: Vector3.Zero, radius: 16f );

// Box
body.AddBoxShape( position: Vector3.Zero, rotation: Rotation.Identity, extent: new Vector3( 32, 32, 32 ) );

// Capsule
body.AddCapsuleShape( center: Vector3.Zero, center2: Vector3.Up * 72f, radius: 16f );

// Remove all shapes
body.ClearShapes();

Practical example — shoot and detect

protected override void OnUpdate()
{
    if ( !Input.Pressed( "Attack1" ) ) return;

    var ray = Scene.Camera.ScreenNormalToRay( 0.5f, 0.5f );

    var tr = Scene.PhysicsWorld.Trace
        .Ray( ray, distance: 2000f )
        .IgnoreDynamic()
        .Run();

    if ( !tr.Hit ) return;

    Log.Info( $"Hit {tr.Body?.GameObject?.Name}" );

    // Push the hit object
    var rb = tr.Body?.Component as Rigidbody;
    if ( rb is not null )
    {
        rb.ApplyImpulseAt( tr.HitPosition, ray.Forward * 2000f );
    }
}

Build docs developers (and LLMs) love