terminalCode Example

Sandbox: ThrusterEntity and WheelEntity — contraption propulsion with ClientInput bindings

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

ThrusterEntity and WheelEntity — Contraption Propulsion Components

Both ThrusterEntity and WheelEntity implement IPlayerControllable and use ClientInput properties so players can bind them to keys from a vehicle seat.

ThrusterEntity

CSHARP
[Alias( "thruster" )]
public class ThrusterEntity : Component, IPlayerControllable
{
    [Property, Range( 0, 1 ), ClientEditable]
    public float Power { get; set; } = 0.5f;

    [Property, ClientEditable]
    public bool Invert { get; set; } = false;

    [Property, Sync, ClientEditable]
    public ClientInput Activate { get; set; }

    [Property, Sync, ClientEditable]
    public ClientInput Reverse { get; set; }

    // Current thrust output, -1 to 1 (synced for effects)
    [Sync] public float CurrentThrust { get; private set; }

    void IPlayerControllable.OnControl()
    {
        var forward = Activate.Down() ? 1f : 0f;
        var backward = Reverse.Down() ? -1f : 0f;
        CurrentThrust = (forward + backward) * (Invert ? -1f : 1f);
    }

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

        if ( CurrentThrust == 0 ) return;

        var rb = GetComponentInParent<Rigidbody>( true );
        if ( !rb.IsValid() ) return;

        var force = WorldRotation.Up * CurrentThrust * Power * rb.Mass * 800f;
        rb.ApplyForceAt( WorldPosition, force );
    }
}

ThrusterTool — placement

CSHARP
[Rpc.Host]
public void SpawnThruster( SelectionPoint point, GameObject thrusterPrefab, Transform tx, bool noWeld )
{
    var go = thrusterPrefab.GetScene().Clone();
    go.Tags.Add( "removable" );
    go.Tags.Add( "constraint" );
    go.WorldTransform = tx;

    if ( !noWeld && !point.GameObject.Tags.Contains( "world" ) )
    {
        var thruster = go.GetComponent<ThrusterEntity>();

        // Attach with a rigid FixedJoint
        var joint = thruster.AddComponent<FixedJoint>();
        joint.Attachment = Joint.AttachmentMode.LocalFrames;
        joint.LocalFrame2 = point.GameObject.WorldTransform.WithScale( 1 ).ToLocal( tx );
        joint.LocalFrame1 = new Transform();
        joint.AngularFrequency = 0;
        joint.LinearFrequency = 0;
        joint.Body = point.GameObject;
        joint.EnableCollision = false;
    }

    go.NetworkSpawn( true, null );
    Track( go );
}

WheelEntity

CSHARP
public class WheelEntity : Component, IPlayerControllable
{
    [Property, ClientEditable] public bool Reversed { get; set; } = false;
    [Property, Range( 0, 1 ), ClientEditable] public float Power { get; set; } = 0.5f;

    [Property, Sync, ClientEditable] public ClientInput Accelerate { get; set; }
    [Property, Sync, ClientEditable] public ClientInput Brake { get; set; }
    [Property, Sync, ClientEditable] public ClientInput SteerLeft { get; set; }
    [Property, Sync, ClientEditable] public ClientInput SteerRight { get; set; }

    void IPlayerControllable.OnControl()
    {
        var joint = GetComponentInChildren<WheelJoint>( true );
        if ( !joint.IsValid() ) return;

        var accel = Accelerate.Down() ? 1f : 0f;
        var brake = Brake.Down() ? 1f : 0f;
        var steer = (SteerRight.Down() ? 1f : 0f) - (SteerLeft.Down() ? 1f : 0f);

        var dir = Reversed ? -1f : 1f;
        joint.MotorTorque = accel * Power * 500000f * dir;
        joint.BrakeTorque = brake * 500000f;

        if ( steer != 0 )
        {
            joint.EnableSteering = true;
            joint.TargetSteeringAngle = 30 * steer;
        }
        else
        {
            joint.TargetSteeringAngle = 0;
        }
    }
}

Key points

Was this helpful?