Merge branch 'main' into chore/character-icon-by-input

pull/209/head
Allison Ryan 3 years ago
commit 21cac3bba2

@ -5,14 +5,12 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template alien_zone} /// {@template alien_zone}
/// Area positioned below [Spaceship] where the [Ball] /// Area positioned below [Spaceship] where the [Ball]
/// can bounce off [AlienBumper]s. /// can bounce off [AlienBumper]s.
/// ///
/// When a [Ball] hits [AlienBumper]s, they toggle between activated and /// When a [Ball] hits an [AlienBumper], the bumper animates.
/// deactivated states.
/// {@endtemplate} /// {@endtemplate}
class AlienZone extends Component with HasGameRef<PinballGame> { class AlienZone extends Component with HasGameRef<PinballGame> {
/// {@macro alien_zone} /// {@macro alien_zone}
@ -22,12 +20,12 @@ class AlienZone extends Component with HasGameRef<PinballGame> {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
gameRef.addContactCallback(_ControlledAlienBumperBallContactCallback()); gameRef.addContactCallback(AlienBumperBallContactCallback());
final lowerBumper = ControlledAlienBumper.a() final lowerBumper = _AlienBumper.a()
..initialPosition = Vector2(-32.52, -9.34); ..initialPosition = Vector2(-32.52, -9.1);
final upperBumper = ControlledAlienBumper.b() final upperBumper = _AlienBumper.b()
..initialPosition = Vector2(-22.89, -17.43); ..initialPosition = Vector2(-22.89, -17.35);
await addAll([ await addAll([
lowerBumper, lowerBumper,
@ -36,60 +34,27 @@ class AlienZone extends Component with HasGameRef<PinballGame> {
} }
} }
/// {@template controlled_alien_bumper} // TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D
/// [AlienBumper] with [_AlienBumperController] attached. // ContactCallback process is enhanced.
/// {@endtemplate} class _AlienBumper extends AlienBumper with ScorePoints {
@visibleForTesting _AlienBumper.a() : super.a();
class ControlledAlienBumper extends AlienBumper
with Controls<_AlienBumperController>, ScorePoints {
/// {@macro controlled_alien_bumper}
ControlledAlienBumper.a() : super.a() {
controller = _AlienBumperController(this);
}
/// {@macro controlled_alien_bumper} _AlienBumper.b() : super.b();
ControlledAlienBumper.b() : super.b() {
controller = _AlienBumperController(this);
}
@override @override
// TODO(ruimiguel): change points when get final points map.
int get points => 20; int get points => 20;
} }
/// {@template alien_bumper_controller} /// Listens when a [Ball] bounces against an [AlienBumper].
/// Controls a [AlienBumper]. @visibleForTesting
/// {@endtemplate} class AlienBumperBallContactCallback
class _AlienBumperController extends ComponentController<AlienBumper> extends ContactCallback<AlienBumper, Ball> {
with HasGameRef<PinballGame> {
/// {@macro alien_bumper_controller}
_AlienBumperController(AlienBumper alienBumper) : super(alienBumper);
/// Flag for activated state of the [AlienBumper].
///
/// Used to toggle [AlienBumper]s' state between activated and deactivated.
bool isActivated = false;
/// Registers when a [AlienBumper] is hit by a [Ball].
void hit() {
if (isActivated) {
component.deactivate();
} else {
component.activate();
}
isActivated = !isActivated;
}
}
/// Listens when a [Ball] bounces bounces against a [AlienBumper].
class _ControlledAlienBumperBallContactCallback
extends ContactCallback<Controls<_AlienBumperController>, Ball> {
@override @override
void begin( void begin(
Controls<_AlienBumperController> controlledAlienBumper, AlienBumper alienBumper,
Ball _, Ball _,
Contact __, Contact __,
) { ) {
controlledAlienBumper.controller.hit(); alienBumper.animate();
} }
} }

@ -9,7 +9,6 @@ export 'flutter_forest.dart';
export 'game_flow_controller.dart'; export 'game_flow_controller.dart';
export 'google_word.dart'; export 'google_word.dart';
export 'launcher.dart'; export 'launcher.dart';
export 'score_effect_controller.dart';
export 'score_points.dart'; export 'score_points.dart';
export 'sparky_fire_zone.dart'; export 'sparky_fire_zone.dart';
export 'wall.dart'; export 'wall.dart';

@ -11,7 +11,7 @@ import 'package:pinball_flame/pinball_flame.dart';
/// can bounce off [DashNestBumper]s. /// can bounce off [DashNestBumper]s.
/// ///
/// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest] /// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest]
/// is awarded, and the [BigDashNestBumper] releases a new [Ball]. /// is awarded, and the [DashNestBumper.main] releases a new [Ball].
/// {@endtemplate} /// {@endtemplate}
class FlutterForest extends Component class FlutterForest extends Component
with Controls<_FlutterForestController>, HasGameRef<PinballGame> { with Controls<_FlutterForestController>, HasGameRef<PinballGame> {
@ -27,11 +27,11 @@ class FlutterForest extends Component
final signpost = Signpost()..initialPosition = Vector2(8.35, -58.3); final signpost = Signpost()..initialPosition = Vector2(8.35, -58.3);
final bigNest = _BigDashNestBumper() final bigNest = _DashNestBumper.main()
..initialPosition = Vector2(18.55, -59.35); ..initialPosition = Vector2(18.55, -59.35);
final smallLeftNest = _SmallDashNestBumper.a() final smallLeftNest = _DashNestBumper.a()
..initialPosition = Vector2(8.95, -51.95); ..initialPosition = Vector2(8.95, -51.95);
final smallRightNest = _SmallDashNestBumper.b() final smallRightNest = _DashNestBumper.b()
..initialPosition = Vector2(23.3, -46.75); ..initialPosition = Vector2(23.3, -46.75);
final dashAnimatronic = DashAnimatronic()..position = Vector2(20, -66); final dashAnimatronic = DashAnimatronic()..position = Vector2(20, -66);
@ -70,8 +70,6 @@ class _FlutterForestController extends ComponentController<FlutterForest>
} }
Future<void> _addBonusBall() async { Future<void> _addBonusBall() async {
// TODO(alestiago): Remove hardcoded duration.
await Future<void>.delayed(const Duration(milliseconds: 700));
await gameRef.add( await gameRef.add(
ControlledBall.bonus(theme: gameRef.theme) ControlledBall.bonus(theme: gameRef.theme)
..initialPosition = Vector2(17.2, -52.7), ..initialPosition = Vector2(17.2, -52.7),
@ -81,15 +79,12 @@ class _FlutterForestController extends ComponentController<FlutterForest>
// TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D // TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D
// ContactCallback process is enhanced. // ContactCallback process is enhanced.
class _BigDashNestBumper extends BigDashNestBumper with ScorePoints { class _DashNestBumper extends DashNestBumper with ScorePoints {
@override _DashNestBumper.main() : super.main();
int get points => 20;
}
class _SmallDashNestBumper extends SmallDashNestBumper with ScorePoints { _DashNestBumper.a() : super.a();
_SmallDashNestBumper.a() : super.a();
_SmallDashNestBumper.b() : super.b(); _DashNestBumper.b() : super.b();
@override @override
int get points => 20; int get points => 20;

@ -1,45 +0,0 @@
import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template score_effect_controller}
/// A [ComponentController] responsible for adding [ScoreText]s
/// on the game screen when the user earns points.
/// {@endtemplate}
class ScoreEffectController extends ComponentController<PinballGame>
with BlocComponent<GameBloc, GameState> {
/// {@macro score_effect_controller}
ScoreEffectController(PinballGame component) : super(component);
int _lastScore = 0;
final _random = Random();
double _noise() {
return _random.nextDouble() * 5 * (_random.nextBool() ? -1 : 1);
}
@override
bool listenWhen(GameState? previousState, GameState newState) {
return previousState?.score != newState.score;
}
@override
void onNewState(GameState state) {
final newScore = state.score - _lastScore;
_lastScore = state.score;
component.add(
ScoreText(
text: newScore.toString(),
position: Vector2(
_noise(),
_noise() + (BoardDimensions.bounds.topCenter.dy + 10),
),
),
);
}
}

@ -30,11 +30,18 @@ class BallScorePointsCallback extends ContactCallback<Ball, ScorePoints> {
@override @override
void begin( void begin(
Ball _, Ball ball,
ScorePoints scorePoints, ScorePoints scorePoints,
Contact __, Contact _,
) { ) {
_gameRef.read<GameBloc>().add(Scored(points: scorePoints.points)); _gameRef.read<GameBloc>().add(Scored(points: scorePoints.points));
_gameRef.audio.score(); _gameRef.audio.score();
_gameRef.add(
ScoreText(
text: scorePoints.points.toString(),
position: ball.body.position,
),
);
} }
} }

@ -5,14 +5,12 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template sparky_fire_zone} /// {@template sparky_fire_zone}
/// Area positioned at the top left of the [Board] where the [Ball] /// Area positioned at the top left of the [Board] where the [Ball]
/// can bounce off [SparkyBumper]s. /// can bounce off [SparkyBumper]s.
/// ///
/// When a [Ball] hits [SparkyBumper]s, they toggle between activated and /// When a [Ball] hits [SparkyBumper]s, the bumper animates.
/// deactivated states.
/// {@endtemplate} /// {@endtemplate}
class SparkyFireZone extends Component with HasGameRef<PinballGame> { class SparkyFireZone extends Component with HasGameRef<PinballGame> {
/// {@macro sparky_fire_zone} /// {@macro sparky_fire_zone}
@ -22,14 +20,14 @@ class SparkyFireZone extends Component with HasGameRef<PinballGame> {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
gameRef.addContactCallback(_ControlledSparkyBumperBallContactCallback()); gameRef.addContactCallback(SparkyBumperBallContactCallback());
final lowerLeftBumper = ControlledSparkyBumper.a() final lowerLeftBumper = _SparkyBumper.a()
..initialPosition = Vector2(-23.15, -41.65); ..initialPosition = Vector2(-22.9, -41.65);
final upperLeftBumper = ControlledSparkyBumper.b() final upperLeftBumper = _SparkyBumper.b()
..initialPosition = Vector2(-21.25, -58.15); ..initialPosition = Vector2(-21.25, -57.9);
final rightBumper = ControlledSparkyBumper.c() final rightBumper = _SparkyBumper.c()
..initialPosition = Vector2(-3.56, -53.051); ..initialPosition = Vector2(-3.3, -52.55);
await addAll([ await addAll([
lowerLeftBumper, lowerLeftBumper,
@ -39,65 +37,29 @@ class SparkyFireZone extends Component with HasGameRef<PinballGame> {
} }
} }
/// {@template controlled_sparky_bumper} // TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D
/// [SparkyBumper] with [_SparkyBumperController] attached. // ContactCallback process is enhanced.
/// {@endtemplate} class _SparkyBumper extends SparkyBumper with ScorePoints {
@visibleForTesting _SparkyBumper.a() : super.a();
class ControlledSparkyBumper extends SparkyBumper
with Controls<_SparkyBumperController>, ScorePoints {
///{@macro controlled_sparky_bumper}
ControlledSparkyBumper.a() : super.a() {
controller = _SparkyBumperController(this);
}
///{@macro controlled_sparky_bumper} _SparkyBumper.b() : super.b();
ControlledSparkyBumper.b() : super.b() {
controller = _SparkyBumperController(this);
}
///{@macro controlled_sparky_bumper} _SparkyBumper.c() : super.c();
ControlledSparkyBumper.c() : super.c() {
controller = _SparkyBumperController(this);
}
@override @override
int get points => 20; int get points => 20;
} }
/// {@template sparky_bumper_controller}
/// Controls a [SparkyBumper].
/// {@endtemplate}
class _SparkyBumperController extends ComponentController<SparkyBumper>
with HasGameRef<PinballGame> {
/// {@macro sparky_bumper_controller}
_SparkyBumperController(ControlledSparkyBumper controlledSparkyBumper)
: super(controlledSparkyBumper);
/// Flag for activated state of the [SparkyBumper].
///
/// Used to toggle [SparkyBumper]s' state between activated and deactivated.
bool isActivated = false;
/// Registers when a [SparkyBumper] is hit by a [Ball].
void hit() {
if (isActivated) {
component.deactivate();
} else {
component.activate();
}
isActivated = !isActivated;
}
}
/// Listens when a [Ball] bounces bounces against a [SparkyBumper]. /// Listens when a [Ball] bounces bounces against a [SparkyBumper].
class _ControlledSparkyBumperBallContactCallback @visibleForTesting
extends ContactCallback<Controls<_SparkyBumperController>, Ball> { class SparkyBumperBallContactCallback
extends ContactCallback<SparkyBumper, Ball> {
@override @override
void begin( void begin(
Controls<_SparkyBumperController> controlledSparkyBumper, SparkyBumper sparkyBumper,
Ball _, Ball _,
Contact __, Contact __,
) { ) {
controlledSparkyBumper.controller.hit(); sparkyBumper.animate();
} }
} }

@ -41,16 +41,37 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.boundary.outer.keyName), images.load(components.Assets.images.boundary.outer.keyName),
images.load(components.Assets.images.spaceship.saucer.keyName), images.load(components.Assets.images.spaceship.saucer.keyName),
images.load(components.Assets.images.spaceship.bridge.keyName), images.load(components.Assets.images.spaceship.bridge.keyName),
images.load(components.Assets.images.spaceship.ramp.main.keyName),
images.load(components.Assets.images.spaceship.ramp.boardOpening.keyName), images.load(components.Assets.images.spaceship.ramp.boardOpening.keyName),
images.load(
components.Assets.images.spaceship.ramp.railingForeground.keyName,
),
images.load( images.load(
components.Assets.images.spaceship.ramp.railingBackground.keyName, components.Assets.images.spaceship.ramp.railingBackground.keyName,
), ),
images.load(components.Assets.images.spaceship.ramp.main.keyName),
images
.load(components.Assets.images.spaceship.ramp.arrow.inactive.keyName),
images.load( images.load(
components.Assets.images.spaceship.ramp.railingForeground.keyName, components.Assets.images.spaceship.ramp.arrow.active1.keyName,
),
images.load(
components.Assets.images.spaceship.ramp.arrow.active2.keyName,
),
images.load(
components.Assets.images.spaceship.ramp.arrow.active3.keyName,
),
images.load(
components.Assets.images.spaceship.ramp.arrow.active4.keyName,
),
images.load(
components.Assets.images.spaceship.ramp.arrow.active5.keyName,
), ),
images.load(components.Assets.images.spaceship.rail.main.keyName), images.load(components.Assets.images.spaceship.rail.main.keyName),
images.load(components.Assets.images.spaceship.rail.foreground.keyName), images.load(components.Assets.images.spaceship.rail.foreground.keyName),
images.load(components.Assets.images.alienBumper.a.active.keyName),
images.load(components.Assets.images.alienBumper.a.inactive.keyName),
images.load(components.Assets.images.alienBumper.b.active.keyName),
images.load(components.Assets.images.alienBumper.b.inactive.keyName),
images.load(components.Assets.images.chromeDino.mouth.keyName), images.load(components.Assets.images.chromeDino.mouth.keyName),
images.load(components.Assets.images.chromeDino.head.keyName), images.load(components.Assets.images.chromeDino.head.keyName),
images.load(components.Assets.images.plunger.plunger.keyName), images.load(components.Assets.images.plunger.plunger.keyName),

@ -42,7 +42,6 @@ class PinballGame extends Forge2DGame
Future<void> onLoad() async { Future<void> onLoad() async {
_addContactCallbacks(); _addContactCallbacks();
unawaited(add(ScoreEffectController(this)));
unawaited(add(gameFlowController = GameFlowController(this))); unawaited(add(gameFlowController = GameFlowController(this)));
unawaited(add(CameraController(this))); unawaited(add(CameraController(this)));
unawaited(add(Backboard.waiting(position: Vector2(0, -88)))); unawaited(add(Backboard.waiting(position: Vector2(0, -88))));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

@ -311,6 +311,9 @@ class $AssetsImagesSpaceshipRailGen {
class $AssetsImagesSpaceshipRampGen { class $AssetsImagesSpaceshipRampGen {
const $AssetsImagesSpaceshipRampGen(); const $AssetsImagesSpaceshipRampGen();
$AssetsImagesSpaceshipRampArrowGen get arrow =>
const $AssetsImagesSpaceshipRampArrowGen();
/// File path: assets/images/spaceship/ramp/board-opening.png /// File path: assets/images/spaceship/ramp/board-opening.png
AssetGenImage get boardOpening => AssetGenImage get boardOpening =>
const AssetGenImage('assets/images/spaceship/ramp/board-opening.png'); const AssetGenImage('assets/images/spaceship/ramp/board-opening.png');
@ -384,6 +387,34 @@ class $AssetsImagesDashBumperMainGen {
const AssetGenImage('assets/images/dash/bumper/main/inactive.png'); const AssetGenImage('assets/images/dash/bumper/main/inactive.png');
} }
class $AssetsImagesSpaceshipRampArrowGen {
const $AssetsImagesSpaceshipRampArrowGen();
/// File path: assets/images/spaceship/ramp/arrow/active1.png
AssetGenImage get active1 =>
const AssetGenImage('assets/images/spaceship/ramp/arrow/active1.png');
/// File path: assets/images/spaceship/ramp/arrow/active2.png
AssetGenImage get active2 =>
const AssetGenImage('assets/images/spaceship/ramp/arrow/active2.png');
/// File path: assets/images/spaceship/ramp/arrow/active3.png
AssetGenImage get active3 =>
const AssetGenImage('assets/images/spaceship/ramp/arrow/active3.png');
/// File path: assets/images/spaceship/ramp/arrow/active4.png
AssetGenImage get active4 =>
const AssetGenImage('assets/images/spaceship/ramp/arrow/active4.png');
/// File path: assets/images/spaceship/ramp/arrow/active5.png
AssetGenImage get active5 =>
const AssetGenImage('assets/images/spaceship/ramp/arrow/active5.png');
/// File path: assets/images/spaceship/ramp/arrow/inactive.png
AssetGenImage get inactive =>
const AssetGenImage('assets/images/spaceship/ramp/arrow/inactive.png');
}
class $AssetsImagesSparkyBumperAGen { class $AssetsImagesSparkyBumperAGen {
const $AssetsImagesSparkyBumperAGen(); const $AssetsImagesSparkyBumperAGen();

@ -1,37 +1,41 @@
import 'dart:async';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template alien_bumper} /// {@template alien_bumper}
/// Bumper for Alien area. /// Bumper for area under the [Spaceship].
/// {@endtemplate} /// {@endtemplate}
// TODO(ruimiguel): refactor later to unify with DashBumpers.
class AlienBumper extends BodyComponent with InitialPosition { class AlienBumper extends BodyComponent with InitialPosition {
/// {@macro alien_bumper} /// {@macro alien_bumper}
AlienBumper._({ AlienBumper._({
required double majorRadius, required double majorRadius,
required double minorRadius, required double minorRadius,
required String activeAssetPath, required String onAssetPath,
required String inactiveAssetPath, required String offAssetPath,
required SpriteComponent spriteComponent,
}) : _majorRadius = majorRadius, }) : _majorRadius = majorRadius,
_minorRadius = minorRadius, _minorRadius = minorRadius,
_activeAssetPath = activeAssetPath, super(
_inactiveAssetPath = inactiveAssetPath, priority: RenderPriority.alienBumper,
_spriteComponent = spriteComponent, children: [
super(priority: RenderPriority.alienBumper); _AlienBumperSpriteGroupComponent(
onAssetPath: onAssetPath,
offAssetPath: offAssetPath,
),
],
) {
renderBody = false;
}
/// {@macro alien_bumper} /// {@macro alien_bumper}
AlienBumper.a() AlienBumper.a()
: this._( : this._(
majorRadius: 3.52, majorRadius: 3.52,
minorRadius: 2.97, minorRadius: 2.97,
activeAssetPath: Assets.images.alienBumper.a.active.keyName, onAssetPath: Assets.images.alienBumper.a.active.keyName,
inactiveAssetPath: Assets.images.alienBumper.a.inactive.keyName, offAssetPath: Assets.images.alienBumper.a.inactive.keyName,
spriteComponent: SpriteComponent(
anchor: Anchor.center,
position: Vector2(0, -0.1),
),
); );
/// {@macro alien_bumper} /// {@macro alien_bumper}
@ -39,32 +43,12 @@ class AlienBumper extends BodyComponent with InitialPosition {
: this._( : this._(
majorRadius: 3.19, majorRadius: 3.19,
minorRadius: 2.79, minorRadius: 2.79,
activeAssetPath: Assets.images.alienBumper.b.active.keyName, onAssetPath: Assets.images.alienBumper.b.active.keyName,
inactiveAssetPath: Assets.images.alienBumper.b.inactive.keyName, offAssetPath: Assets.images.alienBumper.b.inactive.keyName,
spriteComponent: SpriteComponent(
anchor: Anchor.center,
position: Vector2(0, -0.1),
),
); );
final double _majorRadius; final double _majorRadius;
final double _minorRadius; final double _minorRadius;
final String _activeAssetPath;
late final Sprite _activeSprite;
final String _inactiveAssetPath;
late final Sprite _inactiveSprite;
final SpriteComponent _spriteComponent;
@override
Future<void> onLoad() async {
await super.onLoad();
renderBody = false;
await _loadSprites();
deactivate();
await add(_spriteComponent);
}
@override @override
Body createBody() { Body createBody() {
@ -85,25 +69,53 @@ class AlienBumper extends BodyComponent with InitialPosition {
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
Future<void> _loadSprites() async { /// Animates the [AlienBumper].
// TODO(alestiago): I think ideally we would like to do: Future<void> animate() async {
// Sprite(path).load so we don't require to store the activeAssetPath and final spriteGroupComponent = firstChild<_AlienBumperSpriteGroupComponent>()
// the inactive assetPath. ?..current = AlienBumperSpriteState.inactive;
_inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath); await Future<void>.delayed(const Duration(milliseconds: 50));
_activeSprite = await gameRef.loadSprite(_activeAssetPath); spriteGroupComponent?.current = AlienBumperSpriteState.active;
} }
}
/// Activates the [AlienBumper]. /// Indicates the [AlienBumper]'s current sprite state.
void activate() { @visibleForTesting
_spriteComponent enum AlienBumperSpriteState {
..sprite = _activeSprite /// A lit up bumper.
..size = _activeSprite.originalSize / 10; active,
}
/// A dimmed bumper.
inactive,
}
class _AlienBumperSpriteGroupComponent
extends SpriteGroupComponent<AlienBumperSpriteState> with HasGameRef {
_AlienBumperSpriteGroupComponent({
required String onAssetPath,
required String offAssetPath,
}) : _onAssetPath = onAssetPath,
_offAssetPath = offAssetPath,
super(
anchor: Anchor.center,
position: Vector2(0, -0.1),
);
final String _onAssetPath;
final String _offAssetPath;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprites = {
AlienBumperSpriteState.active:
Sprite(gameRef.images.fromCache(_onAssetPath)),
AlienBumperSpriteState.inactive:
Sprite(gameRef.images.fromCache(_offAssetPath)),
};
this.sprites = sprites;
/// Deactivates the [AlienBumper]. current = AlienBumperSpriteState.active;
void deactivate() { size = sprites[current]!.originalSize / 10;
_spriteComponent
..sprite = _inactiveSprite
..size = _inactiveSprite.originalSize / 10;
} }
} }

@ -2,7 +2,7 @@ import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template dash_animatronic} /// {@template dash_animatronic}
/// Animated Dash that sits on top of the [BigDashNestBumper]. /// Animated Dash that sits on top of the [DashNestBumper.main].
/// {@endtemplate} /// {@endtemplate}
class DashAnimatronic extends SpriteAnimationComponent with HasGameRef { class DashAnimatronic extends SpriteAnimationComponent with HasGameRef {
/// {@macro dash_animatronic} /// {@macro dash_animatronic}

@ -2,139 +2,76 @@ import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template dash_nest_bumper} /// {@template dash_nest_bumper}
/// Bumper with a nest appearance. /// Bumper with a nest appearance.
/// {@endtemplate} /// {@endtemplate}
abstract class DashNestBumper extends BodyComponent with InitialPosition { class DashNestBumper extends BodyComponent with InitialPosition {
/// {@macro dash_nest_bumper} /// {@macro dash_nest_bumper}
DashNestBumper._({ DashNestBumper._({
required double majorRadius,
required double minorRadius,
required String activeAssetPath, required String activeAssetPath,
required String inactiveAssetPath, required String inactiveAssetPath,
required SpriteComponent spriteComponent, required Vector2 spritePosition,
}) : _activeAssetPath = activeAssetPath, }) : _majorRadius = majorRadius,
_inactiveAssetPath = inactiveAssetPath, _minorRadius = minorRadius,
_spriteComponent = spriteComponent, super(
super(priority: RenderPriority.dashBumper); priority: RenderPriority.dashBumper,
children: [
final String _activeAssetPath; _DashNestBumperSpriteGroupComponent(
late final Sprite _activeSprite; activeAssetPath: activeAssetPath,
final String _inactiveAssetPath; inactiveAssetPath: inactiveAssetPath,
late final Sprite _inactiveSprite; position: spritePosition,
final SpriteComponent _spriteComponent; ),
],
Future<void> _loadSprites() async { ) {
// TODO(alestiago): I think ideally we would like to do: renderBody = false;
// Sprite(path).load so we don't require to store the activeAssetPath and
// the inactive assetPath.
_inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath);
_activeSprite = await gameRef.loadSprite(_activeAssetPath);
}
/// Activates the [DashNestBumper].
void activate() {
_spriteComponent
..sprite = _activeSprite
..size = _activeSprite.originalSize / 10;
} }
/// Deactivates the [DashNestBumper].
void deactivate() {
_spriteComponent
..sprite = _inactiveSprite
..size = _inactiveSprite.originalSize / 10;
}
@override
Future<void> onLoad() async {
await super.onLoad();
await _loadSprites();
// TODO(erickzanardo): Look into using onNewState instead.
// Currently doing: onNewState(gameRef.read<GameState>()) will throw an
// `Exception: build context is not available yet`
deactivate();
await add(_spriteComponent);
}
}
/// {@macro dash_nest_bumper}
class BigDashNestBumper extends DashNestBumper {
/// {@macro dash_nest_bumper} /// {@macro dash_nest_bumper}
BigDashNestBumper() DashNestBumper.main()
: super._( : this._(
majorRadius: 5.1,
minorRadius: 3.75,
activeAssetPath: Assets.images.dash.bumper.main.active.keyName, activeAssetPath: Assets.images.dash.bumper.main.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName,
spriteComponent: SpriteComponent( spritePosition: Vector2(0, -0.3),
anchor: Anchor.center,
position: Vector2(0, -0.3),
),
); );
@override
Body createBody() {
final shape = EllipseShape(
center: Vector2.zero(),
majorRadius: 5.1,
minorRadius: 3.75,
)..rotate(math.pi / 1.9);
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef(
position: initialPosition,
userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
/// {@macro dash_nest_bumper}
class SmallDashNestBumper extends DashNestBumper {
/// {@macro dash_nest_bumper} /// {@macro dash_nest_bumper}
SmallDashNestBumper._({ DashNestBumper.a()
required String activeAssetPath,
required String inactiveAssetPath,
required SpriteComponent spriteComponent,
}) : super._(
activeAssetPath: activeAssetPath,
inactiveAssetPath: inactiveAssetPath,
spriteComponent: spriteComponent,
);
/// {@macro dash_nest_bumper}
SmallDashNestBumper.a()
: this._( : this._(
majorRadius: 3,
minorRadius: 2.5,
activeAssetPath: Assets.images.dash.bumper.a.active.keyName, activeAssetPath: Assets.images.dash.bumper.a.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName,
spriteComponent: SpriteComponent( spritePosition: Vector2(0.35, -1.2),
anchor: Anchor.center,
position: Vector2(0.35, -1.2),
),
); );
/// {@macro dash_nest_bumper} /// {@macro dash_nest_bumper}
SmallDashNestBumper.b() DashNestBumper.b()
: this._( : this._(
majorRadius: 3,
minorRadius: 2.5,
activeAssetPath: Assets.images.dash.bumper.b.active.keyName, activeAssetPath: Assets.images.dash.bumper.b.active.keyName,
inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName, inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName,
spriteComponent: SpriteComponent( spritePosition: Vector2(0.35, -1.2),
anchor: Anchor.center,
position: Vector2(0.35, -1.2),
),
); );
final double _majorRadius;
final double _minorRadius;
@override @override
Body createBody() { Body createBody() {
final shape = EllipseShape( final shape = EllipseShape(
center: Vector2.zero(), center: Vector2.zero(),
majorRadius: 3, majorRadius: _majorRadius,
minorRadius: 2.25, minorRadius: _minorRadius,
)..rotate(math.pi / 2); )..rotate(math.pi / 1.9);
final fixtureDef = FixtureDef( final fixtureDef = FixtureDef(shape, restitution: 4);
shape,
restitution: 4,
);
final bodyDef = BodyDef( final bodyDef = BodyDef(
position: initialPosition, position: initialPosition,
userData: this, userData: this,
@ -142,4 +79,58 @@ class SmallDashNestBumper extends DashNestBumper {
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
/// Activates the [DashNestBumper].
void activate() {
firstChild<_DashNestBumperSpriteGroupComponent>()?.current =
DashNestBumperSpriteState.active;
}
/// Deactivates the [DashNestBumper].
void deactivate() {
firstChild<_DashNestBumperSpriteGroupComponent>()?.current =
DashNestBumperSpriteState.inactive;
}
}
/// Indicates the [DashNestBumper]'s current sprite state.
@visibleForTesting
enum DashNestBumperSpriteState {
/// A lit up bumper.
active,
/// A dimmed bumper.
inactive,
}
class _DashNestBumperSpriteGroupComponent
extends SpriteGroupComponent<DashNestBumperSpriteState> with HasGameRef {
_DashNestBumperSpriteGroupComponent({
required String activeAssetPath,
required String inactiveAssetPath,
required Vector2 position,
}) : _activeAssetPath = activeAssetPath,
_inactiveAssetPath = inactiveAssetPath,
super(
anchor: Anchor.center,
position: position,
);
final String _activeAssetPath;
final String _inactiveAssetPath;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprites = {
DashNestBumperSpriteState.active:
Sprite(gameRef.images.fromCache(_activeAssetPath)),
DashNestBumperSpriteState.inactive:
Sprite(gameRef.images.fromCache(_inactiveAssetPath)),
};
this.sprites = sprites;
current = DashNestBumperSpriteState.inactive;
size = sprites[current]!.originalSize / 10;
}
} }

@ -101,6 +101,8 @@ abstract class RenderPriority {
static const int spaceshipRampBackgroundRailing = _above + spaceshipRamp; static const int spaceshipRampBackgroundRailing = _above + spaceshipRamp;
static const int spaceshipRampArrow = _above + spaceshipRamp;
static const int spaceshipRampForegroundRailing = static const int spaceshipRampForegroundRailing =
_above + ballOnSpaceshipRamp; _above + ballOnSpaceshipRamp;

@ -88,12 +88,9 @@ class _SignpostSpriteComponent extends SpriteGroupComponent<SignpostSpriteState>
final sprites = <SignpostSpriteState, Sprite>{}; final sprites = <SignpostSpriteState, Sprite>{};
this.sprites = sprites; this.sprites = sprites;
for (final spriteState in SignpostSpriteState.values) { for (final spriteState in SignpostSpriteState.values) {
// TODO(allisonryan0002): Support caching sprites[spriteState] = Sprite(
// https://github.com/VGVentures/pinball/pull/204 gameRef.images.fromCache(spriteState.path),
// sprites[spriteState] = Sprite( );
// gameRef.images.fromCache(spriteState.path),
// );
sprites[spriteState] = await gameRef.loadSprite(spriteState.path);
} }
current = SignpostSpriteState.inactive; current = SignpostSpriteState.inactive;

@ -4,6 +4,7 @@ import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/gen/assets.gen.dart'; import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
@ -15,6 +16,15 @@ class SpaceshipRamp extends Forge2DBlueprint {
/// {@macro spaceship_ramp} /// {@macro spaceship_ramp}
SpaceshipRamp(); SpaceshipRamp();
/// [SpriteGroupComponent] representing the arrow that lights up.
@visibleForTesting
late final SpaceshipRampArrowSpriteComponent spaceshipRampArrow;
/// Forwards the sprite to the next [SpaceshipRampArrowSpriteState].
///
/// If the current state is the last one it cycles back to the initial state.
void progress() => spaceshipRampArrow.progress();
@override @override
void build(_) { void build(_) {
addAllContactCallback([ addAllContactCallback([
@ -23,20 +33,22 @@ class SpaceshipRamp extends Forge2DBlueprint {
final rightOpening = _SpaceshipRampOpening( final rightOpening = _SpaceshipRampOpening(
outsidePriority: RenderPriority.ballOnBoard, outsidePriority: RenderPriority.ballOnBoard,
rotation: math.pi, rotation: -5 * math.pi / 180,
) )
..initialPosition = Vector2(1.7, -19.8) ..initialPosition = Vector2(1.7, -19.12)
..layer = Layer.opening; ..layer = Layer.opening;
final leftOpening = _SpaceshipRampOpening( final leftOpening = _SpaceshipRampOpening(
outsideLayer: Layer.spaceship, outsideLayer: Layer.spaceship,
outsidePriority: RenderPriority.ballOnSpaceship, outsidePriority: RenderPriority.ballOnSpaceship,
rotation: math.pi, rotation: -5 * math.pi / 180,
) )
..initialPosition = Vector2(-13.7, -18.6) ..initialPosition = Vector2(-13.7, -19)
..layer = Layer.spaceshipEntranceRamp; ..layer = Layer.spaceshipEntranceRamp;
final spaceshipRamp = _SpaceshipRampBackground(); final spaceshipRamp = _SpaceshipRampBackground();
spaceshipRampArrow = SpaceshipRampArrowSpriteComponent();
final spaceshipRampBoardOpeningSprite = final spaceshipRampBoardOpeningSprite =
_SpaceshipRampBoardOpeningSpriteComponent() _SpaceshipRampBoardOpeningSpriteComponent()
..position = Vector2(3.4, -39.5); ..position = Vector2(3.4, -39.5);
@ -52,15 +64,69 @@ class SpaceshipRamp extends Forge2DBlueprint {
baseRight, baseRight,
_SpaceshipRampBackgroundRailingSpriteComponent(), _SpaceshipRampBackgroundRailingSpriteComponent(),
spaceshipRamp, spaceshipRamp,
spaceshipRampArrow,
spaceshipRampForegroundRailing, spaceshipRampForegroundRailing,
]); ]);
} }
} }
/// Indicates the state of the arrow on the [SpaceshipRamp].
@visibleForTesting
enum SpaceshipRampArrowSpriteState {
/// Arrow with no dashes lit up.
inactive,
/// Arrow with 1 light lit up.
active1,
/// Arrow with 2 lights lit up.
active2,
/// Arrow with 3 lights lit up.
active3,
/// Arrow with 4 lights lit up.
active4,
/// Arrow with all 5 lights lit up.
active5,
}
extension on SpaceshipRampArrowSpriteState {
String get path {
switch (this) {
case SpaceshipRampArrowSpriteState.inactive:
return Assets.images.spaceship.ramp.arrow.inactive.keyName;
case SpaceshipRampArrowSpriteState.active1:
return Assets.images.spaceship.ramp.arrow.active1.keyName;
case SpaceshipRampArrowSpriteState.active2:
return Assets.images.spaceship.ramp.arrow.active2.keyName;
case SpaceshipRampArrowSpriteState.active3:
return Assets.images.spaceship.ramp.arrow.active3.keyName;
case SpaceshipRampArrowSpriteState.active4:
return Assets.images.spaceship.ramp.arrow.active4.keyName;
case SpaceshipRampArrowSpriteState.active5:
return Assets.images.spaceship.ramp.arrow.active5.keyName;
}
}
SpaceshipRampArrowSpriteState get next {
return SpaceshipRampArrowSpriteState
.values[(index + 1) % SpaceshipRampArrowSpriteState.values.length];
}
}
class _SpaceshipRampBackground extends BodyComponent class _SpaceshipRampBackground extends BodyComponent
with InitialPosition, Layered { with InitialPosition, Layered {
_SpaceshipRampBackground() : super(priority: RenderPriority.spaceshipRamp) { _SpaceshipRampBackground()
: super(
priority: RenderPriority.spaceshipRamp,
children: [
_SpaceshipRampBackgroundRampSpriteComponent(),
],
) {
layer = Layer.spaceshipEntranceRamp; layer = Layer.spaceshipEntranceRamp;
renderBody = false;
} }
/// Width between walls of the ramp. /// Width between walls of the ramp.
@ -112,30 +178,26 @@ class _SpaceshipRampBackground extends BodyComponent
return body; return body;
} }
@override
Future<void> onLoad() async {
await super.onLoad();
renderBody = false;
await add(_SpaceshipRampBackgroundRampSpriteComponent());
}
} }
class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent
with HasGameRef { with HasGameRef {
_SpaceshipRampBackgroundRailingSpriteComponent() _SpaceshipRampBackgroundRailingSpriteComponent()
: super(priority: RenderPriority.spaceshipRampBackgroundRailing); : super(
anchor: Anchor.center,
position: Vector2(-11.7, -54.3),
priority: RenderPriority.spaceshipRampBackgroundRailing,
);
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
Assets.images.spaceship.ramp.railingBackground.keyName, gameRef.images.fromCache(
Assets.images.spaceship.ramp.railingBackground.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(-11.7, -54.3);
} }
} }
@ -144,13 +206,50 @@ class _SpaceshipRampBackgroundRampSpriteComponent extends SpriteComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
Assets.images.spaceship.ramp.main.keyName, gameRef.images.fromCache(
Assets.images.spaceship.ramp.main.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
anchor = Anchor.center; anchor = Anchor.center;
position = Vector2(-11.7, -53.6); position = Vector2(-10.7, -53.6);
}
}
/// {@template spaceship_ramp_arrow_sprite_component}
/// An arrow inside [SpaceshipRamp].
///
/// Lights up a each dash whenever a [Ball] gets into [SpaceshipRamp].
/// {@endtemplate}
class SpaceshipRampArrowSpriteComponent
extends SpriteGroupComponent<SpaceshipRampArrowSpriteState>
with HasGameRef {
/// {@macro spaceship_ramp_arrow_sprite_component}
SpaceshipRampArrowSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-3.9, -56.5),
priority: RenderPriority.spaceshipRampArrow,
);
/// Changes arrow image to the next [Sprite].
void progress() => current = current?.next;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprites = <SpaceshipRampArrowSpriteState, Sprite>{};
this.sprites = sprites;
for (final spriteState in SpaceshipRampArrowSpriteState.values) {
sprites[spriteState] = Sprite(
gameRef.images.fromCache(spriteState.path),
);
}
current = SpaceshipRampArrowSpriteState.inactive;
size = sprites[current]!.originalSize / 10;
} }
} }
@ -159,8 +258,10 @@ class _SpaceshipRampBoardOpeningSpriteComponent extends SpriteComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
Assets.images.spaceship.ramp.boardOpening.keyName, gameRef.images.fromCache(
Assets.images.spaceship.ramp.boardOpening.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
@ -231,16 +332,22 @@ class _SpaceshipRampForegroundRailing extends BodyComponent
class _SpaceshipRampForegroundRailingSpriteComponent extends SpriteComponent class _SpaceshipRampForegroundRailingSpriteComponent extends SpriteComponent
with HasGameRef { with HasGameRef {
_SpaceshipRampForegroundRailingSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-12.3, -52.5),
);
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
Assets.images.spaceship.ramp.railingForeground.keyName, gameRef.images.fromCache(
Assets.images.spaceship.ramp.railingForeground.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(-12.3, -52.5);
} }
} }

@ -2,38 +2,43 @@ import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
/// {@template sparky_bumper} /// {@template sparky_bumper}
/// Bumper for Sparky area. /// Bumper for Sparky area.
/// {@endtemplate} /// {@endtemplate}
// TODO(ruimiguel): refactor later to unify with DashBumpers.
class SparkyBumper extends BodyComponent with InitialPosition { class SparkyBumper extends BodyComponent with InitialPosition {
/// {@macro sparky_bumper} /// {@macro sparky_bumper}
SparkyBumper._({ SparkyBumper._({
required double majorRadius, required double majorRadius,
required double minorRadius, required double minorRadius,
required String activeAssetPath, required String onAssetPath,
required String inactiveAssetPath, required String offAssetPath,
required SpriteComponent spriteComponent, required Vector2 spritePosition,
}) : _majorRadius = majorRadius, }) : _majorRadius = majorRadius,
_minorRadius = minorRadius, _minorRadius = minorRadius,
_activeAssetPath = activeAssetPath, super(
_inactiveAssetPath = inactiveAssetPath, priority: RenderPriority.sparkyBumper,
_spriteComponent = spriteComponent, children: [
super(priority: RenderPriority.sparkyBumper); _SparkyBumperSpriteGroupComponent(
onAssetPath: onAssetPath,
offAssetPath: offAssetPath,
position: spritePosition,
),
],
) {
renderBody = false;
}
/// {@macro sparky_bumper} /// {@macro sparky_bumper}
SparkyBumper.a() SparkyBumper.a()
: this._( : this._(
majorRadius: 2.9, majorRadius: 2.9,
minorRadius: 2.1, minorRadius: 2.1,
activeAssetPath: Assets.images.sparky.bumper.a.active.keyName, onAssetPath: Assets.images.sparky.bumper.a.active.keyName,
inactiveAssetPath: Assets.images.sparky.bumper.a.inactive.keyName, offAssetPath: Assets.images.sparky.bumper.a.inactive.keyName,
spriteComponent: SpriteComponent( spritePosition: Vector2(0, -0.25),
anchor: Anchor.center,
position: Vector2(0, -0.25),
),
); );
/// {@macro sparky_bumper} /// {@macro sparky_bumper}
@ -41,12 +46,9 @@ class SparkyBumper extends BodyComponent with InitialPosition {
: this._( : this._(
majorRadius: 2.85, majorRadius: 2.85,
minorRadius: 2, minorRadius: 2,
activeAssetPath: Assets.images.sparky.bumper.b.active.keyName, onAssetPath: Assets.images.sparky.bumper.b.active.keyName,
inactiveAssetPath: Assets.images.sparky.bumper.b.inactive.keyName, offAssetPath: Assets.images.sparky.bumper.b.inactive.keyName,
spriteComponent: SpriteComponent( spritePosition: Vector2(0, -0.35),
anchor: Anchor.center,
position: Vector2(0, -0.35),
),
); );
/// {@macro sparky_bumper} /// {@macro sparky_bumper}
@ -54,33 +56,13 @@ class SparkyBumper extends BodyComponent with InitialPosition {
: this._( : this._(
majorRadius: 3, majorRadius: 3,
minorRadius: 2.2, minorRadius: 2.2,
activeAssetPath: Assets.images.sparky.bumper.c.active.keyName, onAssetPath: Assets.images.sparky.bumper.c.active.keyName,
inactiveAssetPath: Assets.images.sparky.bumper.c.inactive.keyName, offAssetPath: Assets.images.sparky.bumper.c.inactive.keyName,
spriteComponent: SpriteComponent( spritePosition: Vector2(0, -0.4),
anchor: Anchor.center,
position: Vector2(0, -0.4),
),
); );
final double _majorRadius; final double _majorRadius;
final double _minorRadius; final double _minorRadius;
final String _activeAssetPath;
late final Sprite _activeSprite;
final String _inactiveAssetPath;
late final Sprite _inactiveSprite;
final SpriteComponent _spriteComponent;
@override
Future<void> onLoad() async {
await super.onLoad();
await _loadSprites();
// TODO(erickzanardo): Look into using onNewState instead.
// Currently doing: onNewState(gameRef.read<GameState>()) will throw an
// `Exception: build context is not available yet`
deactivate();
await add(_spriteComponent);
}
@override @override
Body createBody() { Body createBody() {
@ -102,25 +84,53 @@ class SparkyBumper extends BodyComponent with InitialPosition {
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
Future<void> _loadSprites() async { /// Animates the [DashNestBumper].
// TODO(alestiago): I think ideally we would like to do: Future<void> animate() async {
// Sprite(path).load so we don't require to store the activeAssetPath and final spriteGroupComponent = firstChild<_SparkyBumperSpriteGroupComponent>()
// the inactive assetPath. ?..current = SparkyBumperSpriteState.inactive;
_inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath); await Future<void>.delayed(const Duration(milliseconds: 50));
_activeSprite = await gameRef.loadSprite(_activeAssetPath); spriteGroupComponent?.current = SparkyBumperSpriteState.active;
} }
}
/// Activates the [DashNestBumper]. /// Indicates the [SparkyBumper]'s current sprite state.
void activate() { @visibleForTesting
_spriteComponent enum SparkyBumperSpriteState {
..sprite = _activeSprite /// A lit up bumper.
..size = _activeSprite.originalSize / 10; active,
}
/// A dimmed bumper.
inactive,
}
class _SparkyBumperSpriteGroupComponent
extends SpriteGroupComponent<SparkyBumperSpriteState> with HasGameRef {
_SparkyBumperSpriteGroupComponent({
required String onAssetPath,
required String offAssetPath,
required Vector2 position,
}) : _onAssetPath = onAssetPath,
_offAssetPath = offAssetPath,
super(
anchor: Anchor.center,
position: position,
);
final String _onAssetPath;
final String _offAssetPath;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprites = {
SparkyBumperSpriteState.active:
Sprite(gameRef.images.fromCache(_onAssetPath)),
SparkyBumperSpriteState.inactive:
Sprite(gameRef.images.fromCache(_offAssetPath)),
};
this.sprites = sprites;
/// Deactivates the [DashNestBumper]. current = SparkyBumperSpriteState.active;
void deactivate() { size = sprites[current]!.originalSize / 10;
_spriteComponent
..sprite = _inactiveSprite
..size = _inactiveSprite.originalSize / 10;
} }
} }

@ -54,6 +54,7 @@ flutter:
- assets/images/spaceship/ - assets/images/spaceship/
- assets/images/spaceship/rail/ - assets/images/spaceship/rail/
- assets/images/spaceship/ramp/ - assets/images/spaceship/ramp/
- assets/images/spaceship/ramp/arrow/
- assets/images/chrome_dino/ - assets/images/chrome_dino/
- assets/images/kicker/ - assets/images/kicker/
- assets/images/plunger/ - assets/images/plunger/

@ -17,6 +17,11 @@ class AlienBumperAGame extends BasicBallGame {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await images.loadAll([
Assets.images.alienBumper.a.active.keyName,
Assets.images.alienBumper.a.inactive.keyName,
]);
final center = screenToWorld(camera.viewport.canvasSize! / 2); final center = screenToWorld(camera.viewport.canvasSize! / 2);
final alienBumperA = AlienBumper.a() final alienBumperA = AlienBumper.a()
..initialPosition = Vector2(center.x - 20, center.y - 20) ..initialPosition = Vector2(center.x - 20, center.y - 20)

@ -17,6 +17,11 @@ class AlienBumperBGame extends BasicBallGame {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await images.loadAll([
Assets.images.alienBumper.b.active.keyName,
Assets.images.alienBumper.b.inactive.keyName,
]);
final center = screenToWorld(camera.viewport.canvasSize! / 2); final center = screenToWorld(camera.viewport.canvasSize! / 2);
final alienBumperB = AlienBumper.b() final alienBumperB = AlienBumper.b()
..initialPosition = Vector2(center.x - 10, center.y + 10) ..initialPosition = Vector2(center.x - 10, center.y + 10)

@ -15,9 +15,14 @@ class BigDashNestBumperGame extends BasicBallGame with Traceable {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await images.loadAll([
Assets.images.dash.bumper.main.active.keyName,
Assets.images.dash.bumper.main.inactive.keyName,
]);
camera.followVector2(Vector2.zero()); camera.followVector2(Vector2.zero());
await add(BigDashNestBumper()..priority = 1); await add(DashNestBumper.main()..priority = 1);
await traceAllBodies();
await traceAllBodies(); await traceAllBodies();
} }
} }

@ -17,8 +17,15 @@ class SignpostGame extends BasicBallGame with Traceable, TapDetector {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await images.loadAll([
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
]);
camera.followVector2(Vector2.zero()); camera.followVector2(Vector2.zero());
await add(Signpost()..priority = 1); await add(Signpost());
await traceAllBodies(); await traceAllBodies();
} }

@ -15,9 +15,14 @@ class SmallDashNestBumperAGame extends BasicBallGame with Traceable {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await images.loadAll([
Assets.images.dash.bumper.a.active.keyName,
Assets.images.dash.bumper.a.inactive.keyName,
]);
camera.followVector2(Vector2.zero()); camera.followVector2(Vector2.zero());
await add(SmallDashNestBumper.a()..priority = 1); await add(DashNestBumper.a()..priority = 1);
await traceAllBodies();
await traceAllBodies(); await traceAllBodies();
} }
} }

@ -15,9 +15,14 @@ class SmallDashNestBumperBGame extends BasicBallGame with Traceable {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await images.loadAll([
Assets.images.dash.bumper.b.active.keyName,
Assets.images.dash.bumper.b.inactive.keyName,
]);
camera.followVector2(Vector2.zero()); camera.followVector2(Vector2.zero());
await add(SmallDashNestBumper.b()..priority = 1); await add(DashNestBumper.b()..priority = 1);
await traceAllBodies();
await traceAllBodies(); await traceAllBodies();
} }
} }

@ -2,11 +2,12 @@ import 'dart:async';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart'; import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SpaceshipRampGame extends BasicBallGame { class SpaceshipRampGame extends BasicBallGame with KeyboardEvents {
SpaceshipRampGame() SpaceshipRampGame()
: super( : super(
color: Colors.blue, color: Colors.blue,
@ -19,13 +20,45 @@ class SpaceshipRampGame extends BasicBallGame {
- Activate the "trace" parameter to overlay the body. - Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game. - Tap anywhere on the screen to spawn a ball into the game.
- Press space to progress arrow sprites.
'''; ''';
late final SpaceshipRamp _spaceshipRamp;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await addFromBlueprint(SpaceshipRamp());
await images.loadAll([
Assets.images.spaceship.ramp.railingBackground.keyName,
Assets.images.spaceship.ramp.main.keyName,
Assets.images.spaceship.ramp.boardOpening.keyName,
Assets.images.spaceship.ramp.railingForeground.keyName,
Assets.images.spaceship.ramp.arrow.inactive.keyName,
Assets.images.spaceship.ramp.arrow.active1.keyName,
Assets.images.spaceship.ramp.arrow.active2.keyName,
Assets.images.spaceship.ramp.arrow.active3.keyName,
Assets.images.spaceship.ramp.arrow.active4.keyName,
Assets.images.spaceship.ramp.arrow.active5.keyName,
]);
_spaceshipRamp = SpaceshipRamp();
await addFromBlueprint(_spaceshipRamp);
camera.followVector2(Vector2(-12, -50)); camera.followVector2(Vector2(-12, -50));
await traceAllBodies(); await traceAllBodies();
} }
@override
KeyEventResult onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (event is RawKeyDownEvent &&
event.logicalKey == LogicalKeyboardKey.space) {
_spaceshipRamp.progress();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
}
} }

@ -16,12 +16,21 @@ class SparkyBumperGame extends BasicBallGame with Traceable {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
await images.loadAll([
Assets.images.sparky.bumper.a.active.keyName,
Assets.images.sparky.bumper.a.inactive.keyName,
Assets.images.sparky.bumper.b.active.keyName,
Assets.images.sparky.bumper.b.inactive.keyName,
Assets.images.sparky.bumper.c.active.keyName,
Assets.images.sparky.bumper.c.inactive.keyName,
]);
final center = screenToWorld(camera.viewport.canvasSize! / 2); final center = screenToWorld(camera.viewport.canvasSize! / 2);
final sparkyBumperA = SparkyBumper.a() final sparkyBumperA = SparkyBumper.a()
..initialPosition = Vector2(center.x - 20, center.y - 20) ..initialPosition = Vector2(center.x - 20, center.y + 20)
..priority = 1; ..priority = 1;
final sparkyBumperB = SparkyBumper.b() final sparkyBumperB = SparkyBumper.b()
..initialPosition = Vector2(center.x - 10, center.y + 10) ..initialPosition = Vector2(center.x - 10, center.y - 10)
..priority = 1; ..priority = 1;
final sparkyBumperC = SparkyBumper.c() final sparkyBumperC = SparkyBumper.c()
..initialPosition = Vector2(center.x + 20, center.y) ..initialPosition = Vector2(center.x + 20, center.y)

@ -2,9 +2,19 @@ import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
class TestGame extends Forge2DGame { class TestGame extends Forge2DGame {
TestGame() { TestGame([List<String>? assets]) : _assets = assets {
images.prefix = ''; images.prefix = '';
} }
final List<String>? _assets;
@override
Future<void> onLoad() async {
if (_assets != null) {
await images.loadAll(_assets!);
}
await super.onLoad();
}
} }
class KeyboardTestGame extends TestGame with HasKeyboardHandlerComponents {} class KeyboardTestGame extends TestGame with HasKeyboardHandlerComponents {}

@ -9,7 +9,13 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new); final assets = [
Assets.images.alienBumper.a.active.keyName,
Assets.images.alienBumper.a.inactive.keyName,
Assets.images.alienBumper.b.active.keyName,
Assets.images.alienBumper.b.inactive.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group('AlienBumper', () { group('AlienBumper', () {
flameTester.test('"a" loads correctly', (game) async { flameTester.test('"a" loads correctly', (game) async {
@ -25,43 +31,30 @@ void main() {
expect(game.contains(bumper), isTrue); expect(game.contains(bumper), isTrue);
}); });
flameTester.test('activate returns normally', (game) async { flameTester.test('animate switches between on and off sprites',
(game) async {
final bumper = AlienBumper.a(); final bumper = AlienBumper.a();
await game.ensureAdd(bumper); await game.ensureAdd(bumper);
expect(bumper.activate, returnsNormally); final spriteGroupComponent = bumper.firstChild<SpriteGroupComponent>()!;
});
flameTester.test('deactivate returns normally', (game) async {
final bumper = AlienBumper.a();
await game.ensureAdd(bumper);
expect(bumper.deactivate, returnsNormally);
});
flameTester.test('changes sprite', (game) async {
final bumper = AlienBumper.a();
await game.ensureAdd(bumper);
final spriteComponent = bumper.firstChild<SpriteComponent>()!;
final deactivatedSprite = spriteComponent.sprite;
bumper.activate();
expect( expect(
spriteComponent.sprite, spriteGroupComponent.current,
isNot(equals(deactivatedSprite)), equals(AlienBumperSpriteState.active),
); );
final activatedSprite = spriteComponent.sprite; final future = bumper.animate();
bumper.deactivate();
expect( expect(
spriteComponent.sprite, spriteGroupComponent.current,
isNot(equals(activatedSprite)), equals(AlienBumperSpriteState.inactive),
); );
await future;
expect( expect(
activatedSprite, spriteGroupComponent.current,
isNot(equals(deactivatedSprite)), equals(AlienBumperSpriteState.active),
); );
}); });
}); });

@ -9,107 +9,68 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group('BigDashNestBumper', () { group('DashNestBumper', () {
flameTester.test('loads correctly', (game) async { final assets = [
final bumper = BigDashNestBumper(); Assets.images.dash.bumper.main.active.keyName,
Assets.images.dash.bumper.main.inactive.keyName,
Assets.images.dash.bumper.a.active.keyName,
Assets.images.dash.bumper.a.inactive.keyName,
Assets.images.dash.bumper.b.active.keyName,
Assets.images.dash.bumper.b.inactive.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.test('"main" loads correctly', (game) async {
final bumper = DashNestBumper.main();
await game.ensureAdd(bumper); await game.ensureAdd(bumper);
expect(game.contains(bumper), isTrue); expect(game.contains(bumper), isTrue);
}); });
flameTester.test('activate returns normally', (game) async { flameTester.test('"a" loads correctly', (game) async {
final bumper = BigDashNestBumper(); final bumper = DashNestBumper.a();
await game.ensureAdd(bumper); await game.ensureAdd(bumper);
expect(bumper.activate, returnsNormally); expect(game.contains(bumper), isTrue);
}); });
flameTester.test('deactivate returns normally', (game) async { flameTester.test('"b" loads correctly', (game) async {
final bumper = BigDashNestBumper(); final bumper = DashNestBumper.b();
await game.ensureAdd(bumper); await game.ensureAdd(bumper);
expect(game.contains(bumper), isTrue);
expect(bumper.deactivate, returnsNormally);
}); });
flameTester.test('changes sprite', (game) async { flameTester.test('activate switches to active sprite', (game) async {
final bumper = BigDashNestBumper(); final bumper = DashNestBumper.main();
await game.ensureAdd(bumper); await game.ensureAdd(bumper);
final spriteComponent = bumper.firstChild<SpriteComponent>()!; final spriteGroupComponent = bumper.firstChild<SpriteGroupComponent>()!;
final deactivatedSprite = spriteComponent.sprite;
bumper.activate();
expect( expect(
spriteComponent.sprite, spriteGroupComponent.current,
isNot(equals(deactivatedSprite)), equals(DashNestBumperSpriteState.inactive),
); );
final activatedSprite = spriteComponent.sprite; bumper.activate();
bumper.deactivate();
expect(
spriteComponent.sprite,
isNot(equals(activatedSprite)),
);
expect( expect(
activatedSprite, spriteGroupComponent.current,
isNot(equals(deactivatedSprite)), equals(DashNestBumperSpriteState.active),
); );
}); });
});
group('SmallDashNestBumper', () {
flameTester.test('"a" loads correctly', (game) async {
final bumper = SmallDashNestBumper.a();
await game.ensureAdd(bumper);
expect(game.contains(bumper), isTrue);
});
flameTester.test('"b" loads correctly', (game) async {
final bumper = SmallDashNestBumper.b();
await game.ensureAdd(bumper);
expect(game.contains(bumper), isTrue);
});
flameTester.test('activate returns normally', (game) async {
final bumper = SmallDashNestBumper.a();
await game.ensureAdd(bumper);
expect(bumper.activate, returnsNormally);
});
flameTester.test('deactivate returns normally', (game) async {
final bumper = SmallDashNestBumper.a();
await game.ensureAdd(bumper);
expect(bumper.deactivate, returnsNormally);
});
flameTester.test('changes sprite', (game) async { flameTester.test('deactivate switches to inactive sprite', (game) async {
final bumper = SmallDashNestBumper.a(); final bumper = DashNestBumper.main();
await game.ensureAdd(bumper); await game.ensureAdd(bumper);
final spriteComponent = bumper.firstChild<SpriteComponent>()!; final spriteGroupComponent = bumper.firstChild<SpriteGroupComponent>()!
..current = DashNestBumperSpriteState.active;
final deactivatedSprite = spriteComponent.sprite;
bumper.activate();
expect(
spriteComponent.sprite,
isNot(equals(deactivatedSprite)),
);
final activatedSprite = spriteComponent.sprite;
bumper.deactivate(); bumper.deactivate();
expect(
spriteComponent.sprite,
isNot(equals(activatedSprite)),
);
expect( expect(
activatedSprite, spriteGroupComponent.current,
isNot(equals(deactivatedSprite)), equals(DashNestBumperSpriteState.inactive),
); );
}); });
}); });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

@ -9,7 +9,13 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new); final assets = [
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group('Signpost', () { group('Signpost', () {
flameTester.test( flameTester.test(
@ -27,8 +33,10 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'inactive sprite', 'inactive sprite',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
final signpost = Signpost(); final signpost = Signpost();
await game.ensureAdd(signpost); await game.ensureAdd(signpost);
await tester.pump();
expect( expect(
signpost.firstChild<SpriteGroupComponent>()!.current, signpost.firstChild<SpriteGroupComponent>()!.current,
@ -48,9 +56,11 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'active1 sprite', 'active1 sprite',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
final signpost = Signpost(); final signpost = Signpost();
await game.ensureAdd(signpost); await game.ensureAdd(signpost);
signpost.progress(); signpost.progress();
await tester.pump();
expect( expect(
signpost.firstChild<SpriteGroupComponent>()!.current, signpost.firstChild<SpriteGroupComponent>()!.current,
@ -70,11 +80,13 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'active2 sprite', 'active2 sprite',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
final signpost = Signpost(); final signpost = Signpost();
await game.ensureAdd(signpost); await game.ensureAdd(signpost);
signpost signpost
..progress() ..progress()
..progress(); ..progress();
await tester.pump();
expect( expect(
signpost.firstChild<SpriteGroupComponent>()!.current, signpost.firstChild<SpriteGroupComponent>()!.current,
@ -94,12 +106,14 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'active3 sprite', 'active3 sprite',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
final signpost = Signpost(); final signpost = Signpost();
await game.ensureAdd(signpost); await game.ensureAdd(signpost);
signpost signpost
..progress() ..progress()
..progress() ..progress()
..progress(); ..progress();
await tester.pump();
expect( expect(
signpost.firstChild<SpriteGroupComponent>()!.current, signpost.firstChild<SpriteGroupComponent>()!.current,

@ -1,5 +1,6 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -9,22 +10,197 @@ import 'package:pinball_flame/pinball_flame.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.spaceship.ramp.boardOpening.keyName,
Assets.images.spaceship.ramp.railingForeground.keyName,
Assets.images.spaceship.ramp.railingBackground.keyName,
Assets.images.spaceship.ramp.main.keyName,
Assets.images.spaceship.ramp.arrow.inactive.keyName,
Assets.images.spaceship.ramp.arrow.active1.keyName,
Assets.images.spaceship.ramp.arrow.active2.keyName,
Assets.images.spaceship.ramp.arrow.active3.keyName,
Assets.images.spaceship.ramp.arrow.active4.keyName,
Assets.images.spaceship.ramp.arrow.active5.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group('SpaceshipRamp', () { group('SpaceshipRamp', () {
final tester = FlameTester(TestGame.new); flameTester.test(
'loads correctly',
tester.testGameWidget( (game) async {
'renders correctly', final spaceshipRamp = SpaceshipRamp();
setUp: (game, tester) async { await game.ensureAdd(spaceshipRamp);
await game.addFromBlueprint(SpaceshipRamp());
game.camera.followVector2(Vector2(-13, -50)); expect(game.contains(spaceshipRamp), isTrue);
await game.ready();
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/spaceship-ramp.png'),
);
}, },
); );
group('renders correctly', () {
final centerForSpaceshipRamp = Vector2(-13, -55);
flameTester.testGameWidget(
'inactive sprite',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final spaceshipRamp = SpaceshipRamp();
await game.addFromBlueprint(spaceshipRamp);
await game.ready();
await tester.pump();
expect(
spaceshipRamp.spaceshipRampArrow.current,
SpaceshipRampArrowSpriteState.inactive,
);
game.camera.followVector2(centerForSpaceshipRamp);
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/spaceship_ramp/inactive.png'),
);
},
);
flameTester.testGameWidget(
'active1 sprite',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final spaceshipRamp = SpaceshipRamp();
await game.addFromBlueprint(spaceshipRamp);
await game.ready();
spaceshipRamp.progress();
await tester.pump();
expect(
spaceshipRamp.spaceshipRampArrow.current,
SpaceshipRampArrowSpriteState.active1,
);
game.camera.followVector2(centerForSpaceshipRamp);
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/spaceship_ramp/active1.png'),
);
},
);
flameTester.testGameWidget(
'active2 sprite',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final spaceshipRamp = SpaceshipRamp();
await game.addFromBlueprint(spaceshipRamp);
await game.ready();
spaceshipRamp
..progress()
..progress();
await tester.pump();
expect(
spaceshipRamp.spaceshipRampArrow.current,
SpaceshipRampArrowSpriteState.active2,
);
game.camera.followVector2(centerForSpaceshipRamp);
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/spaceship_ramp/active2.png'),
);
},
);
flameTester.testGameWidget(
'active3 sprite',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final spaceshipRamp = SpaceshipRamp();
await game.addFromBlueprint(spaceshipRamp);
await game.ready();
spaceshipRamp
..progress()
..progress()
..progress();
await tester.pump();
expect(
spaceshipRamp.spaceshipRampArrow.current,
SpaceshipRampArrowSpriteState.active3,
);
game.camera.followVector2(centerForSpaceshipRamp);
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/spaceship_ramp/active3.png'),
);
},
);
flameTester.testGameWidget(
'active4 sprite',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final spaceshipRamp = SpaceshipRamp();
await game.addFromBlueprint(spaceshipRamp);
await game.ready();
spaceshipRamp
..progress()
..progress()
..progress()
..progress();
await tester.pump();
expect(
spaceshipRamp.spaceshipRampArrow.current,
SpaceshipRampArrowSpriteState.active4,
);
game.camera.followVector2(centerForSpaceshipRamp);
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/spaceship_ramp/active4.png'),
);
},
);
flameTester.testGameWidget(
'active5 sprite',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final spaceshipRamp = SpaceshipRamp();
await game.addFromBlueprint(spaceshipRamp);
await game.ready();
spaceshipRamp
..progress()
..progress()
..progress()
..progress()
..progress();
await tester.pump();
expect(
spaceshipRamp.spaceshipRampArrow.current,
SpaceshipRampArrowSpriteState.active5,
);
game.camera.followVector2(centerForSpaceshipRamp);
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/spaceship_ramp/active5.png'),
);
},
);
});
}); });
} }

@ -9,7 +9,15 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new); final assets = [
Assets.images.sparky.bumper.a.active.keyName,
Assets.images.sparky.bumper.a.inactive.keyName,
Assets.images.sparky.bumper.b.active.keyName,
Assets.images.sparky.bumper.b.inactive.keyName,
Assets.images.sparky.bumper.c.active.keyName,
Assets.images.sparky.bumper.c.inactive.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group('SparkyBumper', () { group('SparkyBumper', () {
flameTester.test('"a" loads correctly', (game) async { flameTester.test('"a" loads correctly', (game) async {
@ -31,43 +39,30 @@ void main() {
expect(game.contains(bumper), isTrue); expect(game.contains(bumper), isTrue);
}); });
flameTester.test('activate returns normally', (game) async { flameTester.test('animate switches between on and off sprites',
(game) async {
final bumper = SparkyBumper.a(); final bumper = SparkyBumper.a();
await game.ensureAdd(bumper); await game.ensureAdd(bumper);
expect(bumper.activate, returnsNormally); final spriteGroupComponent = bumper.firstChild<SpriteGroupComponent>()!;
});
flameTester.test('deactivate returns normally', (game) async {
final bumper = SparkyBumper.a();
await game.ensureAdd(bumper);
expect(bumper.deactivate, returnsNormally);
});
flameTester.test('changes sprite', (game) async {
final bumper = SparkyBumper.a();
await game.ensureAdd(bumper);
final spriteComponent = bumper.firstChild<SpriteComponent>()!;
final deactivatedSprite = spriteComponent.sprite;
bumper.activate();
expect( expect(
spriteComponent.sprite, spriteGroupComponent.current,
isNot(equals(deactivatedSprite)), equals(SparkyBumperSpriteState.active),
); );
final activatedSprite = spriteComponent.sprite; final future = bumper.animate();
bumper.deactivate();
expect( expect(
spriteComponent.sprite, spriteGroupComponent.current,
isNot(equals(activatedSprite)), equals(SparkyBumperSpriteState.inactive),
); );
await future;
expect( expect(
activatedSprite, spriteGroupComponent.current,
isNot(equals(deactivatedSprite)), equals(SparkyBumperSpriteState.active),
); );
}); });
}); });

@ -13,13 +13,18 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(EmptyPinballTestGame.new); final assets = [
Assets.images.alienBumper.a.active.keyName,
Assets.images.alienBumper.a.inactive.keyName,
Assets.images.alienBumper.b.active.keyName,
Assets.images.alienBumper.b.inactive.keyName,
];
final flameTester = FlameTester(() => EmptyPinballTestGame(assets));
group('AlienZone', () { group('AlienZone', () {
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
(game) async { (game) async {
await game.ready();
final alienZone = AlienZone(); final alienZone = AlienZone();
await game.ensureAdd(alienZone); await game.ensureAdd(alienZone);
@ -31,7 +36,6 @@ void main() {
flameTester.test( flameTester.test(
'two AlienBumper', 'two AlienBumper',
(game) async { (game) async {
await game.ready();
final alienZone = AlienZone(); final alienZone = AlienZone();
await game.ensureAdd(alienZone); await game.ensureAdd(alienZone);
@ -44,55 +48,40 @@ void main() {
}); });
group('bumpers', () { group('bumpers', () {
late ControlledAlienBumper controlledAlienBumper;
late GameBloc gameBloc; late GameBloc gameBloc;
setUp(() { setUp(() {
gameBloc = MockGameBloc(); gameBloc = MockGameBloc();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new, gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc, blocBuilder: () => gameBloc,
assets: assets,
); );
flameTester.testGameWidget( flameTester.test('call animate on contact', (game) async {
'activate when deactivated bumper is hit', final contactCallback = AlienBumperBallContactCallback();
setUp: (game, tester) async { final bumper = MockAlienBumper();
controlledAlienBumper = ControlledAlienBumper.a(); final ball = MockBall();
await game.ensureAdd(controlledAlienBumper);
controlledAlienBumper.controller.hit(); when(bumper.animate).thenAnswer((_) async {});
},
verify: (game, tester) async {
expect(controlledAlienBumper.controller.isActivated, isTrue);
},
);
flameTester.testGameWidget( contactCallback.begin(bumper, ball, MockContact());
'deactivate when activated bumper is hit',
setUp: (game, tester) async {
controlledAlienBumper = ControlledAlienBumper.a();
await game.ensureAdd(controlledAlienBumper);
controlledAlienBumper.controller.hit(); verify(bumper.animate).called(1);
controlledAlienBumper.controller.hit(); });
},
verify: (game, tester) async {
expect(controlledAlienBumper.controller.isActivated, isFalse);
},
);
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
'add Scored event', 'add Scored event',
setUp: (game, tester) async { setUp: (game, tester) async {
final ball = Ball(baseColor: const Color(0xFF00FFFF)); final ball = Ball(baseColor: const Color(0xFF00FFFF));
final alienZone = AlienZone(); final alienZone = AlienZone();
whenListen(
gameBloc,
const Stream<GameState>.empty(),
initialState: const GameState.initial(),
);
await game.ensureAdd(alienZone); await game.ensureAdd(alienZone);
await game.ensureAdd(ball); await game.ensureAdd(ball);

@ -9,7 +9,19 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(EmptyPinballTestGame.new); final assets = [
Assets.images.dash.bumper.main.active.keyName,
Assets.images.dash.bumper.main.inactive.keyName,
Assets.images.dash.bumper.a.active.keyName,
Assets.images.dash.bumper.a.inactive.keyName,
Assets.images.dash.bumper.b.active.keyName,
Assets.images.dash.bumper.b.inactive.keyName,
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
];
final flameTester = FlameTester(() => EmptyPinballTestGame(assets));
group('Board', () { group('Board', () {
flameTester.test( flameTester.test(

@ -12,7 +12,19 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(EmptyPinballTestGame.new); final assets = [
Assets.images.dash.bumper.main.active.keyName,
Assets.images.dash.bumper.main.inactive.keyName,
Assets.images.dash.bumper.a.active.keyName,
Assets.images.dash.bumper.a.inactive.keyName,
Assets.images.dash.bumper.b.active.keyName,
Assets.images.dash.bumper.b.inactive.keyName,
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
];
final flameTester = FlameTester(() => EmptyPinballTestGame(assets));
group('FlutterForest', () { group('FlutterForest', () {
flameTester.test( flameTester.test(
@ -53,27 +65,14 @@ void main() {
); );
flameTester.test( flameTester.test(
'a BigDashNestBumper', 'three DashNestBumper',
(game) async { (game) async {
final flutterForest = FlutterForest(); final flutterForest = FlutterForest();
await game.ensureAdd(flutterForest); await game.ensureAdd(flutterForest);
expect( expect(
flutterForest.descendants().whereType<BigDashNestBumper>().length, flutterForest.descendants().whereType<DashNestBumper>().length,
equals(1), equals(3),
);
},
);
flameTester.test(
'two SmallDashNestBumper',
(game) async {
final flutterForest = FlutterForest();
await game.ensureAdd(flutterForest);
expect(
flutterForest.descendants().whereType<SmallDashNestBumper>().length,
equals(2),
); );
}, },
); );
@ -88,13 +87,14 @@ void main() {
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new, gameBuilder: () => EmptyPinballTestGame(assets),
blocBuilder: () { blocBuilder: () {
gameBloc = MockGameBloc(); gameBloc = MockGameBloc();
const state = GameState.initial(); const state = GameState.initial();
whenListen(gameBloc, Stream.value(state), initialState: state); whenListen(gameBloc, Stream.value(state), initialState: state);
return gameBloc; return gameBloc;
}, },
assets: assets,
); );
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(

@ -1,105 +0,0 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
group('ScoreEffectController', () {
late ScoreEffectController controller;
late PinballGame game;
setUpAll(() {
registerFallbackValue(Component());
});
setUp(() {
game = MockPinballGame();
when(() => game.add(any())).thenAnswer((_) async {});
controller = ScoreEffectController(game);
});
group('listenWhen', () {
test('returns true when the user has earned points', () {
const previous = GameState.initial();
const current = GameState(
score: 10,
balls: 3,
bonusHistory: [],
);
expect(controller.listenWhen(previous, current), isTrue);
});
test(
'returns true when the user has earned points and there was no '
'previous state',
() {
const current = GameState(
score: 10,
balls: 3,
bonusHistory: [],
);
expect(controller.listenWhen(null, current), isTrue);
},
);
test(
'returns false when no points were earned',
() {
const current = GameState.initial();
const previous = GameState.initial();
expect(controller.listenWhen(previous, current), isFalse);
},
);
});
group('onNewState', () {
test(
'adds a ScoreText with the correct score for the '
'first time',
() {
const state = GameState(
score: 10,
balls: 3,
bonusHistory: [],
);
controller.onNewState(state);
final effect =
verify(() => game.add(captureAny())).captured.first as ScoreText;
expect(effect.text, equals('10'));
},
);
test('adds a ScoreTextEffect with the correct score', () {
controller.onNewState(
const GameState(
score: 10,
balls: 3,
bonusHistory: [],
),
);
controller.onNewState(
const GameState(
score: 14,
balls: 3,
bonusHistory: [],
),
);
final effect =
verify(() => game.add(captureAny())).captured.last as ScoreText;
expect(effect.text, equals('4'));
});
});
});
}

@ -28,9 +28,13 @@ void main() {
setUp(() { setUp(() {
game = MockPinballGame(); game = MockPinballGame();
bloc = MockGameBloc(); bloc = MockGameBloc();
ball = MockBall();
audio = MockPinballAudio(); audio = MockPinballAudio();
fakeScorePoints = FakeScorePoints(); fakeScorePoints = FakeScorePoints();
ball = MockBall();
final ballBody = MockBody();
when(() => ball.body).thenReturn(ballBody);
when(() => ballBody.position).thenReturn(Vector2.all(4));
}); });
setUpAll(() { setUpAll(() {
@ -73,6 +77,29 @@ void main() {
verify(audio.score).called(1); verify(audio.score).called(1);
}, },
); );
test(
"adds a ScoreText component at Ball's position",
() {
when(game.read<GameBloc>).thenReturn(bloc);
when(() => game.audio).thenReturn(audio);
BallScorePointsCallback(game).begin(
ball,
fakeScorePoints,
FakeContact(),
);
verify(
() => game.add(
ScoreText(
text: fakeScorePoints.points.toString(),
position: ball.body.position,
),
),
).called(1);
},
);
}); });
}); });
} }

@ -13,13 +13,20 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(EmptyPinballTestGame.new); final assets = [
Assets.images.sparky.bumper.a.active.keyName,
Assets.images.sparky.bumper.a.inactive.keyName,
Assets.images.sparky.bumper.b.active.keyName,
Assets.images.sparky.bumper.b.inactive.keyName,
Assets.images.sparky.bumper.c.active.keyName,
Assets.images.sparky.bumper.c.inactive.keyName,
];
final flameTester = FlameTester(() => EmptyPinballTestGame(assets));
group('SparkyFireZone', () { group('SparkyFireZone', () {
flameTester.test( flameTester.test(
'loads correctly', 'loads correctly',
(game) async { (game) async {
await game.ready();
final sparkyFireZone = SparkyFireZone(); final sparkyFireZone = SparkyFireZone();
await game.ensureAdd(sparkyFireZone); await game.ensureAdd(sparkyFireZone);
@ -31,7 +38,6 @@ void main() {
flameTester.test( flameTester.test(
'three SparkyBumper', 'three SparkyBumper',
(game) async { (game) async {
await game.ready();
final sparkyFireZone = SparkyFireZone(); final sparkyFireZone = SparkyFireZone();
await game.ensureAdd(sparkyFireZone); await game.ensureAdd(sparkyFireZone);
@ -44,12 +50,9 @@ void main() {
}); });
group('bumpers', () { group('bumpers', () {
late ControlledSparkyBumper controlledSparkyBumper;
late Ball ball;
late GameBloc gameBloc; late GameBloc gameBloc;
setUp(() { setUp(() {
ball = Ball(baseColor: const Color(0xFF00FFFF));
gameBloc = MockGameBloc(); gameBloc = MockGameBloc();
whenListen( whenListen(
gameBloc, gameBloc,
@ -58,41 +61,28 @@ void main() {
); );
}); });
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new, gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc, blocBuilder: () => gameBloc,
assets: assets,
); );
flameTester.testGameWidget( flameTester.test('call animate on contact', (game) async {
'activate when deactivated bumper is hit', final contactCallback = SparkyBumperBallContactCallback();
setUp: (game, tester) async { final bumper = MockSparkyBumper();
controlledSparkyBumper = ControlledSparkyBumper.a(); final ball = MockBall();
await game.ensureAdd(controlledSparkyBumper);
controlledSparkyBumper.controller.hit(); when(bumper.animate).thenAnswer((_) async {});
},
verify: (game, tester) async {
expect(controlledSparkyBumper.controller.isActivated, isTrue);
},
);
flameTester.testGameWidget( contactCallback.begin(bumper, ball, MockContact());
'deactivate when activated bumper is hit',
setUp: (game, tester) async {
controlledSparkyBumper = ControlledSparkyBumper.a();
await game.ensureAdd(controlledSparkyBumper);
controlledSparkyBumper.controller.hit(); verify(bumper.animate).called(1);
controlledSparkyBumper.controller.hit(); });
},
verify: (game, tester) async {
expect(controlledSparkyBumper.controller.isActivated, isFalse);
},
);
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
'add Scored event', 'add Scored event',
setUp: (game, tester) async { setUp: (game, tester) async {
final ball = Ball(baseColor: const Color(0xFF00FFFF));
final sparkyFireZone = SparkyFireZone(); final sparkyFireZone = SparkyFireZone();
await game.ensureAdd(sparkyFireZone); await game.ensureAdd(sparkyFireZone);
await game.ensureAdd(ball); await game.ensureAdd(ball);

@ -12,8 +12,40 @@ import '../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballTestGame.new); final assets = [
final debugModeFlameTester = FlameTester(DebugPinballTestGame.new); Assets.images.dash.bumper.main.active.keyName,
Assets.images.dash.bumper.main.inactive.keyName,
Assets.images.dash.bumper.a.active.keyName,
Assets.images.dash.bumper.a.inactive.keyName,
Assets.images.dash.bumper.b.active.keyName,
Assets.images.dash.bumper.b.inactive.keyName,
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
Assets.images.alienBumper.a.active.keyName,
Assets.images.alienBumper.a.inactive.keyName,
Assets.images.alienBumper.b.active.keyName,
Assets.images.alienBumper.b.inactive.keyName,
Assets.images.sparky.bumper.a.active.keyName,
Assets.images.sparky.bumper.a.inactive.keyName,
Assets.images.sparky.bumper.b.active.keyName,
Assets.images.sparky.bumper.b.inactive.keyName,
Assets.images.sparky.bumper.c.active.keyName,
Assets.images.sparky.bumper.c.inactive.keyName,
Assets.images.spaceship.ramp.boardOpening.keyName,
Assets.images.spaceship.ramp.railingForeground.keyName,
Assets.images.spaceship.ramp.railingBackground.keyName,
Assets.images.spaceship.ramp.main.keyName,
Assets.images.spaceship.ramp.arrow.inactive.keyName,
Assets.images.spaceship.ramp.arrow.active1.keyName,
Assets.images.spaceship.ramp.arrow.active2.keyName,
Assets.images.spaceship.ramp.arrow.active3.keyName,
Assets.images.spaceship.ramp.arrow.active4.keyName,
Assets.images.spaceship.ramp.arrow.active5.keyName,
];
final flameTester = FlameTester(() => PinballTestGame(assets));
final debugModeFlameTester = FlameTester(() => DebugPinballTestGame(assets));
group('PinballGame', () { group('PinballGame', () {
// TODO(alestiago): test if [PinballGame] registers // TODO(alestiago): test if [PinballGame] registers
@ -90,6 +122,7 @@ void main() {
final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>( final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new, gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () => gameBloc, blocBuilder: () => gameBloc,
// assets: assets,
); );
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
@ -208,6 +241,7 @@ void main() {
FlameBlocTester<DebugPinballGame, GameBloc>( FlameBlocTester<DebugPinballGame, GameBloc>(
gameBuilder: DebugPinballTestGame.new, gameBuilder: DebugPinballTestGame.new,
blocBuilder: () => gameBloc, blocBuilder: () => gameBloc,
assets: assets,
); );
debugModeFlameBlocTester.testGameWidget( debugModeFlameBlocTester.testGameWidget(

@ -16,7 +16,8 @@ void main() {
late ThemeCubit themeCubit; late ThemeCubit themeCubit;
late GameBloc gameBloc; late GameBloc gameBloc;
setUp(() { setUp(() async {
await Future.wait<void>(game.preLoadAssets());
themeCubit = MockThemeCubit(); themeCubit = MockThemeCubit();
gameBloc = MockGameBloc(); gameBloc = MockGameBloc();

@ -1,16 +1,23 @@
import 'package:flame/src/game/flame_game.dart'; import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
class FlameBlocTester<T extends FlameGame, B extends Bloc<dynamic, dynamic>> class FlameBlocTester<T extends FlameGame, B extends Bloc<dynamic, dynamic>>
extends FlameTester<T> { extends FlameTester<T> {
FlameBlocTester({ FlameBlocTester({
required GameCreateFunction<T> gameBuilder, required GameCreateFunction<T> gameBuilder,
required B Function() blocBuilder, required B Function() blocBuilder,
// TODO(allisonryan0002): find alternative for testGameWidget. Loading
// assets in onLoad fails because the game loads after
List<String>? assets,
List<RepositoryProvider> Function()? repositories, List<RepositoryProvider> Function()? repositories,
}) : super( }) : super(
gameBuilder, gameBuilder,
pumpWidget: (gameWidget, tester) async { pumpWidget: (gameWidget, tester) async {
if (assets != null) {
await Future.wait(assets.map(gameWidget.game.images.load));
}
await tester.pumpWidget( await tester.pumpWidget(
BlocProvider.value( BlocProvider.value(
value: blocBuilder(), value: blocBuilder(),

@ -82,3 +82,7 @@ class MockActiveOverlaysNotifier extends Mock
implements ActiveOverlaysNotifier {} implements ActiveOverlaysNotifier {}
class MockGameFlowController extends Mock implements GameFlowController {} class MockGameFlowController extends Mock implements GameFlowController {}
class MockAlienBumper extends Mock implements AlienBumper {}
class MockSparkyBumper extends Mock implements SparkyBumper {}

@ -1,5 +1,7 @@
// ignore_for_file: must_call_super // ignore_for_file: must_call_super
import 'dart:async';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
@ -14,26 +16,53 @@ class TestGame extends Forge2DGame with FlameBloc {
} }
class PinballTestGame extends PinballGame { class PinballTestGame extends PinballGame {
PinballTestGame() PinballTestGame([List<String>? assets])
: super( : _assets = assets,
super(
audio: MockPinballAudio(), audio: MockPinballAudio(),
theme: const PinballTheme( theme: const PinballTheme(
characterTheme: DashTheme(), characterTheme: DashTheme(),
), ),
); );
final List<String>? _assets;
@override
Future<void> onLoad() async {
if (_assets != null) {
await images.loadAll(_assets!);
}
await super.onLoad();
}
} }
class DebugPinballTestGame extends DebugPinballGame { class DebugPinballTestGame extends DebugPinballGame {
DebugPinballTestGame() DebugPinballTestGame([List<String>? assets])
: super( : _assets = assets,
super(
audio: MockPinballAudio(), audio: MockPinballAudio(),
theme: const PinballTheme( theme: const PinballTheme(
characterTheme: DashTheme(), characterTheme: DashTheme(),
), ),
); );
final List<String>? _assets;
@override
Future<void> onLoad() async {
if (_assets != null) {
await images.loadAll(_assets!);
}
await super.onLoad();
}
} }
class EmptyPinballTestGame extends PinballTestGame { class EmptyPinballTestGame extends PinballTestGame {
EmptyPinballTestGame([List<String>? assets]) : super(assets);
@override @override
Future<void> onLoad() async {} Future<void> onLoad() async {
if (_assets != null) {
await images.loadAll(_assets!);
}
}
} }

Loading…
Cancel
Save