这是indexloc提供的服务,不要输入任何密码
Skip to content

feat: vehicle simulation #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions lib/dashboard/game/gauge_game.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:vehicle_cockpit/dashboard/dashboard.dart';
import 'package:vehicle_cockpit/l10n/l10n.dart';
import 'package:vehicle_sim/vehicle_sim.dart';

class GaugeGame extends FlameGame with KeyboardEvents {
GaugeGame({
required this.sim,
required this.onSpeedChanged,
required this.appTheme,
required this.l10n,
this.timeScale = 2.5,
});

/// Speeds vehicle simulation up by this factor to adjust fun factor.
final double timeScale;
final VehicleSim sim;
final ThemeData appTheme;
final AppLocalizations l10n;
final ValueChanged<double> onSpeedChanged;
Expand All @@ -24,24 +30,29 @@ class GaugeGame extends FlameGame with KeyboardEvents {
@override
Color backgroundColor() => Colors.transparent;

void accelerate() => hittingGas = true;
void acceleratorPedalPushed() => hittingGas = true;

void release() => hittingGas = false;
void acceleratorPedalReleased() => hittingGas = false;

@override
KeyEventResult onKeyEvent(
KeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
final isKeyDown = event is KeyDownEvent || event is KeyRepeatEvent;
final isPressed = event is KeyDownEvent || event is KeyRepeatEvent;
final wasReleased = event is KeyUpEvent;

final isSpace = keysPressed.contains(LogicalKeyboardKey.space);
if (event.logicalKey != LogicalKeyboardKey.space ||
(!isPressed && !wasReleased)) {
return KeyEventResult.ignored;
}

if (isSpace && isKeyDown) {
accelerate();
if (isPressed) {
acceleratorPedalPushed();
} else {
release();
acceleratorPedalReleased();
}

return KeyEventResult.handled;
}

Expand All @@ -51,8 +62,8 @@ class GaugeGame extends FlameGame with KeyboardEvents {
gauge = GaugeComponent(
size: Vector2.all(340),
position: size / 2,
maxRpm: 9,
dangerZone: 8,
maxRpm: (sim.vehicle.engineRpmMaximum / 1000).round(),
dangerZone: (sim.vehicle.engineRpmRedline / 1000).round(),
appTheme: appTheme,
),
);
Expand All @@ -74,18 +85,16 @@ class GaugeGame extends FlameGame with KeyboardEvents {

@override
void update(double dt) {
var rpm = gauge.progress;
var speed = speedometer.speed;
if (hittingGas) {
rpm += .008;
speed += rpm / 8;
} else {
rpm -= .008;
speed -= (speed / 500) + .02;
}
onSpeedChanged(speed);
gauge.progress = rpm;
speedometer.speed = speed;
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);
}
Expand Down
20 changes: 0 additions & 20 deletions lib/dashboard/game/gear.dart
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,6 @@ class Gear extends PositionComponent with HasGameRef<GaugeGame> {
size.addListener(_buildPath);
}

@override
void update(double dt) {
final speed = gameRef.speedometer.speed;
if (speed <= 0) {
gearText.text = 'N';
} else if (speed <= 10) {
gearText.text = '1';
} else if (speed <= 15) {
gearText.text = '2';
} else if (speed <= 35) {
gearText.text = '3';
} else if (speed <= 55) {
gearText.text = '4';
} else if (speed <= 65) {
gearText.text = '5';
} else {
gearText.text = '6';
}
}

void _buildPath() {
final dimensions = size.toRect();
const curveOffset = 15.0;
Expand Down
16 changes: 10 additions & 6 deletions lib/dashboard/game/rpm_gauge/gauge_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import 'package:vehicle_cockpit/dashboard/dashboard.dart';
import 'package:vehicle_cockpit/util/math_utils.dart';

class GaugeComponent extends PositionComponent {
GaugeComponent({
Expand All @@ -18,19 +19,22 @@ class GaugeComponent extends PositionComponent {
final int dangerZone;
final ThemeData appTheme;
double _progress = 0;
double _targetProgress = 0;

final _rng = math.Random();

double get progress => _progress;

set progress(double value) {
if (value < 0) {
_progress = 0;
void setProgress(double value, double delta) {
var current = value;
if (current < 0) {
current = 0;
} else if (value >= 1) {
_progress = 1 - _rng.nextDouble() * 0.02;
} else {
_progress = value;
current = 1 - _rng.nextDouble() * 0.02;
}

_targetProgress = current;
_progress = _progress.expDecay(_targetProgress, 16, delta);
}

@override
Expand Down
3 changes: 1 addition & 2 deletions lib/dashboard/game/speedometer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Speedometer extends TextComponent with HasGameRef<GaugeGame> {
super.position,
}) : super(
anchor: Anchor.center,
text: speed.clamp(0, 160).round().toString(),
text: speed.round().toString(),
);

double speed;
Expand Down Expand Up @@ -57,7 +57,6 @@ class Speedometer extends TextComponent with HasGameRef<GaugeGame> {

@override
void update(double dt) {
speed = speed.clamp(0, 160);
text = speed.round().toString();
_mph.position = Vector2(size.x / 2, size.y);
if (speed >= 120) {
Expand Down
10 changes: 8 additions & 2 deletions lib/dashboard/view/dashboard_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:vehicle_cockpit/dashboard/dashboard.dart';
import 'package:vehicle_cockpit/l10n/l10n.dart';
import 'package:vehicle_cockpit/ui/ui.dart';
import 'package:vehicle_sim/vehicle_sim.dart';

class Dashboard extends StatefulWidget {
const Dashboard({
Expand All @@ -23,13 +24,17 @@ class DashboardState extends State<Dashboard>
late final AnimationController _controller;

void onSpeedChanged(double speed) {
if (speed <= 0) {
_controller.stop();
return;
}

// Based on the max speed of 160mph, 0.222222 miles would be the distance
// covered in 5000 milliseconds if going max speed.

const distance = 0.222222;
final timeInMilliseconds = (distance / speed) * 3600 * 1000;

if (speed <= 0) _controller.stop();

_controller
..duration = Duration(
milliseconds: timeInMilliseconds.round().abs(),
Expand Down Expand Up @@ -59,6 +64,7 @@ class DashboardState extends State<Dashboard>
final l10n = context.l10n;
final theme = Theme.of(context);
final game = GaugeGame(
sim: VehicleSim(vehicle: Vehicles.compactCrossoverSUV),
appTheme: theme,
l10n: l10n,
onSpeedChanged: onSpeedChanged,
Expand Down
4 changes: 2 additions & 2 deletions lib/dashboard/widgets/accelerator_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ class _AcceleratorButtonState extends State<AcceleratorButton> {
bool _isPressed = false;

void _accelerate(_) {
_game.accelerate();
_game.acceleratorPedalPushed();
setState(() => _isPressed = true);
}

void _release([_]) {
_game.release();
_game.acceleratorPedalReleased();
setState(() => _isPressed = false);
}

Expand Down
28 changes: 15 additions & 13 deletions lib/dashboard/widgets/lap_section.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,22 @@ class LapSectionState extends State<LapSection> {
}

void newLap(AnimationStatus status) {
if (status == AnimationStatus.completed) {
setState(() {
if (stopwatch.elapsed < bestTime || bestLap == 0) {
bestTime = stopwatch.elapsed;
bestLap = currentLap;
}
currentLap++;
stopwatch.reset();
setLapTime();
widget.controller
..reset()
..forward();
});
if (status != AnimationStatus.completed) {
return;
}

setState(() {
if (stopwatch.elapsed < bestTime || bestLap == 0) {
bestTime = stopwatch.elapsed;
bestLap = currentLap;
}
currentLap++;
stopwatch.reset();
setLapTime();
widget.controller
..reset()
..forward();
});
}

String _lapTime(Duration duration) {
Expand Down
15 changes: 15 additions & 0 deletions lib/util/math_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'dart:math' as math;

extension DoubleExtensions on double {
/// Exponential decay function. Use this instead of linear interpolation since
/// it is framerate-independent!
///
/// e.g., `myValue.expDecay(toValue, decay, delta);`
///
/// Note that useful [decay] values are typically between 1 (slow) to 25
/// (fast).
///
/// Thanks, Freya! https://youtu.be/LSNQuFEDOyQ?t=2981
double expDecay(double b, double decay, double delta) =>
b + (this - b) * math.exp(-decay * delta);
}
1 change: 1 addition & 0 deletions lib/util/util.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'math_utils.dart';
7 changes: 7 additions & 0 deletions packages/vehicle_sim/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# See https://www.dartlang.org/guides/libraries/private-files

# Files and directories created by pub
.dart_tool/
.packages
build/
pubspec.lock
8 changes: 8 additions & 0 deletions packages/vehicle_sim/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Vehicle Sim

[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]

A very simple vehicle simulation model.

[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
1 change: 1 addition & 0 deletions packages/vehicle_sim/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include: package:very_good_analysis/analysis_options.6.0.0.yaml
20 changes: 20 additions & 0 deletions packages/vehicle_sim/coverage_badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading