diff --git a/cspell.json b/cspell.json index f0fd2ae..87a4005 100644 --- a/cspell.json +++ b/cspell.json @@ -34,6 +34,7 @@ "Filterscript", "fontsource", "Frege", + "gaugecomponent", "Genshi", "GLSL", "goldens", diff --git a/src/content/docs/examples/airplane_entertainment_system.mdx b/src/content/docs/examples/airplane_entertainment_system.mdx index 342b245..1d21407 100644 --- a/src/content/docs/examples/airplane_entertainment_system.mdx +++ b/src/content/docs/examples/airplane_entertainment_system.mdx @@ -12,10 +12,10 @@ The Airplane Entertainment System simulates an in-flight entertainment system th Screenshot of the Airplane Entertainment FileSystem. -The source code for this project is available on [GitHub](https://github.com/VGVentures/airplane_entertainment_system). To view the live demo, click [here](https://cuddly-doodle-kgvmnp1.pages.github.io/). +The source code for this project is available on [GitHub](https://github.com/VGVentures/airplane_entertainment_system). To view the live demo, click [here](https://vgventures.github.io/airplane_entertainment_system/). ## Architecture diff --git a/src/content/docs/examples/images/gauge.png b/src/content/docs/examples/images/gauge.png new file mode 100644 index 0000000..96b7ea6 Binary files /dev/null and b/src/content/docs/examples/images/gauge.png differ diff --git a/src/content/docs/examples/images/gauge_progress.png b/src/content/docs/examples/images/gauge_progress.png new file mode 100644 index 0000000..569a57f Binary files /dev/null and b/src/content/docs/examples/images/gauge_progress.png differ diff --git a/src/content/docs/examples/images/gauge_ring.png b/src/content/docs/examples/images/gauge_ring.png new file mode 100644 index 0000000..b876c7e Binary files /dev/null and b/src/content/docs/examples/images/gauge_ring.png differ diff --git a/src/content/docs/examples/images/gauge_rpm_numbers.png b/src/content/docs/examples/images/gauge_rpm_numbers.png new file mode 100644 index 0000000..dd03359 Binary files /dev/null and b/src/content/docs/examples/images/gauge_rpm_numbers.png differ diff --git a/src/content/docs/examples/images/gear.png b/src/content/docs/examples/images/gear.png new file mode 100644 index 0000000..2cb4005 Binary files /dev/null and b/src/content/docs/examples/images/gear.png differ diff --git a/src/content/docs/examples/images/speedometer.png b/src/content/docs/examples/images/speedometer.png new file mode 100644 index 0000000..d82696c Binary files /dev/null and b/src/content/docs/examples/images/speedometer.png differ diff --git a/src/content/docs/examples/images/vehicle_cockpit.png b/src/content/docs/examples/images/vehicle_cockpit.png new file mode 100644 index 0000000..8211a0b Binary files /dev/null and b/src/content/docs/examples/images/vehicle_cockpit.png differ diff --git a/src/content/docs/examples/vehicle_cockpit.mdx b/src/content/docs/examples/vehicle_cockpit.mdx index cbaf383..c758099 100644 --- a/src/content/docs/examples/vehicle_cockpit.mdx +++ b/src/content/docs/examples/vehicle_cockpit.mdx @@ -2,3 +2,188 @@ title: Vehicle Cockpit description: A sample project that simulates a vehicle cockpit. --- + +import { Image, Picture } from "astro:assets"; +import vehicleCockpitScreenshot from "./images/vehicle_cockpit.png"; +import gaugeProgress from "./images/gauge_progress.png"; +import gaugeRing from "./images/gauge_ring.png"; +import gaugeRpmNumbers from "./images/gauge_rpm_numbers.png"; +import gauge from "./images/gauge.png"; +import gear from "./images/gear.png"; +import speedometer from "./images/speedometer.png"; + +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](https://github.com/VGVentures/vehicle_cockpit). To view the live demo, click [here](https://vgventures.github.io/vehicle_cockpit/). + +## Flame + +[Flame](https://pub.dev/packages/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`](https://docs.flame-engine.org/latest/flame/game.html) object. All of the components that make up the gauge are added inside of the `onLoad` method. These components include the [GaugeComponent](#gaugecomponent), [Speedometer](#speedometer), and the [Gear](#gear). + +```dart + @override + Future 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`](https://docs.flame-engine.org/latest/flame/game.html#game-loop) to update the components on the screen. This calls the `update` method where we can update the gauge's progress, speedometer, and current gear. + +```dart + @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`](https://docs.flame-engine.org/latest/other_modules/oxygen/components.html#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: + +```dart + @override + Future 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`](https://docs.flame-engine.org/latest/flame/components.html#ensuring-a-component-has-a-given-parent) [mixin](https://dart.dev/language/mixins), 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`](https://docs.flame-engine.org/latest/flame/rendering/text_rendering.html#textcomponent) object that renders the current speed and the "MPH" label to the screen. The `Speedometer` object uses the [`HasGameRef`](https://github.com/flame-engine/flame/blob/a5338d0c20d01bbe461c6d7fed5951d11e1c76f0/packages/flame/lib/src/components/mixins/has_game_ref.dart) 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`. + +```dart +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`](https://docs.flame-engine.org/latest/flame/game_widget.html). The `GameWidget` requires a `Game` object. In this case, that would be our `GaugeGame`. + +```dart +final game = GaugeGame( + sim: VehicleSim(vehicle: Vehicles.compactCrossoverSUV), + appTheme: theme, + l10n: l10n, + onSpeedChanged: onSpeedChanged, +); + +// ... + +LayoutBuilder( + builder: (context, constraints) { + final width = min(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.