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

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.

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.

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.

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.

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.

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();

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.