terminalCode Example
ControlSystem: Vehicle seat control with IPlayerControllable
ControlSystem: Vehicle Seat Control with IPlayerControllable
This example shows the ControlSystem which manages vehicle seat control, sorting seats by occupation time and routing input to IPlayerControllable components.
CSHARP
public class ControlSystem : GameObjectSystem<ControlSystem>
{
// When each chair first became occupied. Used to sort seats so the earliest occupant is in charge
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>();
foreach (var chair in GetSortedSeats())
{
var builder = new LinkedGameObjectBuilder();
builder.AddConnected(chair.GameObject);
// Skip if a seat occupied earlier already claimed this
if (builder.Objects.Any(driven.Contains)) continue;
driven.UnionWith(builder.Objects);
RunControl(chair, builder);
}
}
IEnumerable<BaseChair> GetSortedSeats()
{
var chairs = Scene.GetAll<BaseChair>();
foreach (var chair in chairs)
{
if (!chair.IsValid() || !chair.IsOccupied)
_occupiedSince.Remove(chair);
else
_occupiedSince.TryAdd(chair, 0);
}
return chairs
.Where(c => c.IsValid() && c.IsOccupied)
.OrderBy(c => (float)_occupiedSince.GetValueOrDefault(c, default));
}
void RunControl(BaseChair chair, LinkedGameObjectBuilder builder)
{
var controller = chair.GetOccupant();
if (!controller.IsValid()) return;
var player = controller.GetComponent<Player>();
if (!player.IsValid()) return;
using var scope = ClientInput.PushScope(player);
foreach (var o in builder.Objects)
{
foreach (var controllable in o.GetComponentsInChildren<IPlayerControllable>())
{
if (controllable is null) continue;
if (!controllable.CanControl(player)) continue;
controllable.OnControl();
}
}
}
}Key Features
- Seat Sorting: Sorts seats by occupation time so earliest occupant has priority
- LinkedGameObjectBuilder: Finds all connected objects from the seat
- Conflict Resolution: Skips objects already controlled by earlier-occupied seats
- ClientInput.PushScope: Routes input from the player to controllable components
- IPlayerControllable: Interface for components that can be controlled by players
- Stage.StartFixedUpdate: Runs in the fixed update loop for consistent physics timing
IPlayerControllable Interface
CSHARP
public interface IPlayerControllable
{
bool CanControl(Player player);
void OnControl();
}Usage Example
CSHARP
public class MyVehicle : Component, IPlayerControllable
{
public bool CanControl(Player player)
{
return true; // Allow any player to control
}
public void OnControl()
{
// Read input and control the vehicle
var throttle = Input.Float("forward");
var steering = Input.Float("right");
// Apply to vehicle physics
}
}Notes
- ControlSystem automatically runs in scenes that have BaseChair components
- Seats are sorted by occupation time to prevent conflicts
- LinkedGameObjectBuilder finds all connected objects (wheels, thrusters, etc.)
- ClientInput.PushScope ensures input comes from the seated player
- Only components implementing IPlayerControllable receive control callbacks
Was this helpful?