terminalCode Example

Sandbox: ControlSystem — vehicle seat and contraption control with IPlayerControllable

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

ControlSystem — Vehicle Seat and Contraption Control

ControlSystem is a GameObjectSystem that drives IPlayerControllable components on contraptions. When a player sits in a BaseChair, the system walks the connected contraption graph and calls OnControl() on every IPlayerControllable in the contraption.
CSHARP
public class ControlSystem : GameObjectSystem<ControlSystem>
{
    // Tracks when each seat was first occupied — used to sort seats
    private readonly Dictionary<BaseChair, RealTimeSince> _occupiedSince = new();

    public ControlSystem( Scene scene ) : base( scene )
    {
        Listen( Stage.StartFixedUpdate, 10, OnTick, "ControlSystem" );
    }

    void OnTick()
    {
        var driven = new HashSet<GameObject>();

        // Process seats in order of occupancy (earliest first)
        foreach ( var chair in GetSortedSeats() )
        {
            var builder = new LinkedGameObjectBuilder();
            builder.AddConnected( chair.GameObject );

            // Skip if an earlier-occupied seat already claimed this contraption
            if ( builder.Objects.Any( driven.Contains ) ) continue;
            driven.UnionWith( builder.Objects );

            RunControl( chair, builder );
        }
    }

    void RunControl( BaseChair chair, LinkedGameObjectBuilder builder )
    {
        var controller = chair.GetOccupant();
        if ( !controller.IsValid() ) return;

        var player = controller.GetComponent<Player>();
        if ( !player.IsValid() ) return;

        // Push a ClientInput scope so weapons know which player is controlling them
        using var scope = ClientInput.PushScope( player );

        foreach ( var go in builder.Objects )
        {
            foreach ( var controllable in go.GetComponentsInChildren<IPlayerControllable>() )
            {
                if ( controllable is null ) continue;
                if ( !controllable.CanControl( player ) ) continue;

                controllable.OnControl();
            }
        }
    }
}

IPlayerControllable interface

CSHARP
public interface IPlayerControllable
{
    // Return false to prevent this player from controlling this component
    public bool CanControl( Player player ) => true;
    public void OnStartControl() { }
    public void OnEndControl() { }
    public void OnControl(); // Called every fixed update tick while seated
}

ClientInput scope

CSHARP
// ClientInput.PushScope sets the "current player" context for the duration of the control tick
// Weapons read ClientInput.Current to know if they're being controlled from a seat
public static IDisposable PushScope( Player player )
{
    var previousState = _currentState;
    _currentState = new State( player?.Network?.Owner, player );
    return DisposeAction.Create( () => _currentState = previousState );
}

public static Player Current => _currentState.player;

Implementing IPlayerControllable

CSHARP
// Example: Dynamite entity — explodes when Activate input is pressed from a seat
public class DynamiteEntity : Component, IPlayerControllable
{
    [Property, Sync, ClientEditable]
    public ClientInput Activate { get; set; }

    void IPlayerControllable.OnControl()
    {
        if ( Activate.Pressed() )
            Explode();
    }

    void IPlayerControllable.OnStartControl() { }
    void IPlayerControllable.OnEndControl() { }
}

// Example: ThrusterEntity — applies force while input is held
public class ThrusterEntity : Component, IPlayerControllable
{
    [Property, Sync, ClientEditable] public ClientInput Activate { get; set; }
    [Property, Sync, ClientEditable] public ClientInput Reverse { get; set; }

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

Key points

Was this helpful?