Skip to content

Vehicle Cockpit

The Vehicle Cockpit simulates a realtime speedometer and tachometer as the user accelerates around the track.

Screenshot of the Vehicle Cockpit.

The source code for this project is available on GitHub. To view the live demo, click here.

Flame

Flame is a game engine that is built for Flutter. It is used to render and update our gauge. Let’s take a deeper look into how the components that make up the gauge are created and composed together to form a working speedometer and tachometer.

The gauge that shows the vehicle's current speed and RPM.

GaugeGame

The GaugeGame is the FlameGame object. All of the components that make up the gauge are added inside of the onLoad method. These components include the GaugeComponent, Speedometer, and the Gear.

@override
Future<void> onLoad() async {
await add(
gauge = GaugeComponent(
size: Vector2.all(340),
position: size / 2,
maxRpm: (sim.vehicle.engineRpmMaximum / 1000).round(),
dangerZone: (sim.vehicle.engineRpmRedline / 1000).round(),
appTheme: appTheme,
),
);
await add(
speedometer = Speedometer(
speed: 0,
position: size / 2 - Vector2(0, 40),
),
);
await add(
gear = Gear(
position: size / 2 + Vector2(0, 30),
triangleSize: 60,
),
);
}

Not only does the GaugeGame add the components to be displayed, it also handles the user input to update the game. As you’ll see below, the GaugeGame can directly call methods on its components, and can also make its variables and methods available to its children.

The GaugeGame relies on a GameLoop to update the components on the screen. This calls the update method where we can update the gauge’s progress, speedometer, and current gear.

@override
void update(double dt) {
sim.simulate(dt * timeScale, hittingGas ? 1.0 : -1.3);
gauge.setProgress(
sim.engineRpm / sim.vehicle.engineRpmMaximum,
dt,
);
speedometer.speed = sim.speed;
gear.gearText.text = sim.gear.toString();
onSpeedChanged(sim.speed);
super.update(dt);
}

GaugeComponent

The GaugeComponent is a PositionComponent that composes GaugeRing and the components that are needed to display the vehicle’s RPM information: GaugeProgress and GaugeRpmNumbers. These are added in the GaugeComponent’s onLoad method:

@override
Future<void> onLoad() async {
await add(
GaugeRing(
size: size.clone(),
color: appTheme.colorScheme.error,
),
);
const offset = 16.0;
final innerRingPosition = Vector2.all(offset / 2);
final innerRingSize = size.clone() - Vector2.all(offset);
await add(
GaugeProgress(
position: innerRingPosition,
size: innerRingSize,
),
);
await add(
GaugeRpmNumbers(
position: innerRingPosition,
size: innerRingSize,
),
);
}
Gauge Ring

The GaugeRing is a PositionComponent that forms the outline of the gauge. This component simply renders the ring.

The outer ring of the gauge.
Gauge Progress

The GaugeProgress is another PositionComponent object that draws the gradient RPM progress bar that is located just inside of the GaugeRing. The GaugeComponent’s progress value gets set within the GaugeGame’s update method. The GaugeProgress object makes use of the ParentIsA mixin, which gives the component access to the parent component via the parent property.

The vehicle's current RPM.
Gauge RPM Numbers

The GaugeRpmNumbers is the last PositionComponent object that is used for the tachometer. This component consist of the GaugeRmpPoint component, that draws the RPM tick marks, and also the GaugeNumberIndicator that draws the RPM numbers below the ticks.

The RPM ticks and numbers within the gauge.

Speedometer

The Speedometer is a TextComponent object that renders the current speed and the “MPH” label to the screen. The Speedometer object uses the HasGameRef<GaugeGame> mixin, which allows it to access variables and methods that are in the GaugeGame class. This makes it easier for the Speedometer to access the GaugeGame’s appTheme and l10n members.

The vehicle's current speed.

Gear

The Gear is a PositionComponent that renders the vehicle’s current gear inside of a triangle. The gearText is set directly in the GaugeGame.

gear.gearText.text = sim.gear.toString();
The vehicle's current gear.

Adding the Gauge to the Widget Tree

To add the gauge to the DashboardPage, we’ll use Flame’s GameWidget. The GameWidget requires a Game object. In this case, that would be our GaugeGame.

final game = GaugeGame(
sim: VehicleSim(vehicle: Vehicles.compactCrossoverSUV),
appTheme: theme,
l10n: l10n,
onSpeedChanged: onSpeedChanged,
);
// ...
LayoutBuilder(
builder: (context, constraints) {
final width = min<double>(400, constraints.maxWidth);
return SizedBox.square(
dimension: width * .75,
child: Transform.scale(
scale: width / 500,
child: GameWidget(
game: game,
),
),
);
},
),

The acceleratorPedalPushed() and acceleratorPedalReleased() can be called on the game object to simulate pressing and releasing the accelerator pedal.