Merge branch 'main' into refactor/backboard-asset-and-sizing

pull/319/head
Allison Ryan 3 years ago
commit a09c4dd9d3

@ -25,17 +25,17 @@ class AndroidAcres extends Component {
)..initialPosition = Vector2(-26, -28.25), )..initialPosition = Vector2(-26, -28.25),
AndroidBumper.a( AndroidBumper.a(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), BumperScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-25, 1.3), )..initialPosition = Vector2(-25, 1.3),
AndroidBumper.b( AndroidBumper.b(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), BumperScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-32.8, -9.2), )..initialPosition = Vector2(-32.8, -9.2),
AndroidBumper.cow( AndroidBumper.cow(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), BumperScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-20.5, -13.8), )..initialPosition = Vector2(-20.5, -13.8),
AndroidSpaceshipBonusBehavior(), AndroidSpaceshipBonusBehavior(),

@ -18,22 +18,22 @@ class FlutterForest extends Component with ZIndex {
children: [ children: [
Signpost( Signpost(
children: [ children: [
ScoringBehavior(points: Points.fiveThousand), BumperScoringBehavior(points: Points.fiveThousand),
], ],
)..initialPosition = Vector2(8.35, -58.3), )..initialPosition = Vector2(8.35, -58.3),
DashNestBumper.main( DashNestBumper.main(
children: [ children: [
ScoringBehavior(points: Points.twoHundredThousand), BumperScoringBehavior(points: Points.twoHundredThousand),
], ],
)..initialPosition = Vector2(18.55, -59.35), )..initialPosition = Vector2(18.55, -59.35),
DashNestBumper.a( DashNestBumper.a(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), BumperScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(8.95, -51.95), )..initialPosition = Vector2(8.95, -51.95),
DashNestBumper.b( DashNestBumper.b(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), BumperScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(22.3, -46.75), )..initialPosition = Vector2(22.3, -46.75),
DashAnimatronic()..position = Vector2(20, -66), DashAnimatronic()..position = Vector2(20, -66),

@ -12,6 +12,7 @@ class Launcher extends Component {
: super( : super(
children: [ children: [
LaunchRamp(), LaunchRamp(),
Flapper(),
ControlledPlunger(compressionDistance: 9.2) ControlledPlunger(compressionDistance: 9.2)
..initialPosition = Vector2(41.2, 43.7), ..initialPosition = Vector2(41.2, 43.7),
RocketSpriteComponent()..position = Vector2(43, 62.3), RocketSpriteComponent()..position = Vector2(43, 62.3),

@ -23,7 +23,6 @@ class ScoringBehavior extends ContactBehavior with HasGameRef<PinballGame> {
if (other is! Ball) return; if (other is! Ball) return;
gameRef.read<GameBloc>().add(Scored(points: _points.value)); gameRef.read<GameBloc>().add(Scored(points: _points.value));
gameRef.audio.score();
gameRef.firstChild<ZCanvasComponent>()!.add( gameRef.firstChild<ZCanvasComponent>()!.add(
ScoreComponent( ScoreComponent(
points: _points, points: _points,
@ -32,3 +31,23 @@ class ScoringBehavior extends ContactBehavior with HasGameRef<PinballGame> {
); );
} }
} }
/// {@template bumper_scoring_behavior}
/// A specific [ScoringBehavior] used for Bumpers.
/// In addition to its parent logic, also plays the
/// SFX for bumpers
/// {@endtemplate}
class BumperScoringBehavior extends ScoringBehavior {
/// {@macro bumper_scoring_behavior}
BumperScoringBehavior({
required Points points,
}) : super(points: points);
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
gameRef.audio.bumper();
}
}

@ -16,17 +16,17 @@ class SparkyScorch extends Component {
children: [ children: [
SparkyBumper.a( SparkyBumper.a(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), BumperScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-22.9, -41.65), )..initialPosition = Vector2(-22.9, -41.65),
SparkyBumper.b( SparkyBumper.b(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), BumperScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-21.25, -57.9), )..initialPosition = Vector2(-21.25, -57.9),
SparkyBumper.c( SparkyBumper.c(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), BumperScoringBehavior(points: Points.twentyThousand),
], ],
)..initialPosition = Vector2(-3.3, -52.55), )..initialPosition = Vector2(-3.3, -52.55),
SparkyComputerSensor()..initialPosition = Vector2(-13, -49.9), SparkyComputerSensor()..initialPosition = Vector2(-13, -49.9),

@ -129,6 +129,9 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.score.twentyThousand.keyName), images.load(components.Assets.images.score.twentyThousand.keyName),
images.load(components.Assets.images.score.twoHundredThousand.keyName), images.load(components.Assets.images.score.twoHundredThousand.keyName),
images.load(components.Assets.images.score.oneMillion.keyName), images.load(components.Assets.images.score.oneMillion.keyName),
images.load(components.Assets.images.flapper.backSupport.keyName),
images.load(components.Assets.images.flapper.frontSupport.keyName),
images.load(components.Assets.images.flapper.flap.keyName),
images.load(dashTheme.leaderboardIcon.keyName), images.load(dashTheme.leaderboardIcon.keyName),
images.load(sparkyTheme.leaderboardIcon.keyName), images.load(sparkyTheme.leaderboardIcon.keyName),
images.load(androidTheme.leaderboardIcon.keyName), images.load(androidTheme.leaderboardIcon.keyName),

@ -5,7 +5,6 @@ import 'package:flame/components.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
@ -15,12 +14,12 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
import 'package:pinball_theme/pinball_theme.dart'; import 'package:pinball_theme/pinball_theme.dart';
class PinballGame extends Forge2DGame class PinballGame extends PinballForge2DGame
with with
FlameBloc, FlameBloc,
HasKeyboardHandlerComponents, HasKeyboardHandlerComponents,
Controls<_GameBallsController>, Controls<_GameBallsController>,
TapDetector { MultiTouchTapDetector {
PinballGame({ PinballGame({
required this.characterTheme, required this.characterTheme,
required this.audio, required this.audio,
@ -85,7 +84,7 @@ class PinballGame extends Forge2DGame
BoardSide? focusedBoardSide; BoardSide? focusedBoardSide;
@override @override
void onTapDown(TapDownInfo info) { void onTapDown(int pointerId, TapDownInfo info) {
if (info.raw.kind == PointerDeviceKind.touch) { if (info.raw.kind == PointerDeviceKind.touch) {
final rocket = descendants().whereType<RocketSpriteComponent>().first; final rocket = descendants().whereType<RocketSpriteComponent>().first;
final bounds = rocket.topLeftPosition & rocket.size; final bounds = rocket.topLeftPosition & rocket.size;
@ -103,19 +102,19 @@ class PinballGame extends Forge2DGame
} }
} }
super.onTapDown(info); super.onTapDown(pointerId, info);
} }
@override @override
void onTapUp(TapUpInfo info) { void onTapUp(int pointerId, TapUpInfo info) {
_moveFlippersDown(); _moveFlippersDown();
super.onTapUp(info); super.onTapUp(pointerId, info);
} }
@override @override
void onTapCancel() { void onTapCancel(int pointerId) {
_moveFlippersDown(); _moveFlippersDown();
super.onTapCancel(); super.onTapCancel(pointerId);
} }
void _moveFlippersDown() { void _moveFlippersDown() {
@ -188,8 +187,8 @@ class DebugPinballGame extends PinballGame with FPSCounter {
} }
@override @override
void onTapUp(TapUpInfo info) { void onTapUp(int pointerId, TapUpInfo info) {
super.onTapUp(info); super.onTapUp(pointerId, info);
if (info.raw.kind == PointerDeviceKind.mouse) { if (info.raw.kind == PointerDeviceKind.mouse) {
final ball = ControlledBall.debug() final ball = ControlledBall.debug()

@ -14,9 +14,10 @@ class $AssetsMusicGen {
class $AssetsSfxGen { class $AssetsSfxGen {
const $AssetsSfxGen(); const $AssetsSfxGen();
String get bumperA => 'assets/sfx/bumper_a.mp3';
String get bumperB => 'assets/sfx/bumper_b.mp3';
String get google => 'assets/sfx/google.mp3'; String get google => 'assets/sfx/google.mp3';
String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.mp3'; String get ioPinballVoiceOver => 'assets/sfx/io_pinball_voice_over.mp3';
String get plim => 'assets/sfx/plim.mp3';
} }
class Assets { class Assets {

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart'; import 'package:flame_audio/flame_audio.dart';
@ -40,6 +42,7 @@ class PinballAudio {
LoopSingleAudio? loopSingleAudio, LoopSingleAudio? loopSingleAudio,
PreCacheSingleAudio? preCacheSingleAudio, PreCacheSingleAudio? preCacheSingleAudio,
ConfigureAudioCache? configureAudioCache, ConfigureAudioCache? configureAudioCache,
Random? seed,
}) : _createAudioPool = createAudioPool ?? AudioPool.create, }) : _createAudioPool = createAudioPool ?? AudioPool.create,
_playSingleAudio = playSingleAudio ?? FlameAudio.audioCache.play, _playSingleAudio = playSingleAudio ?? FlameAudio.audioCache.play,
_loopSingleAudio = loopSingleAudio ?? FlameAudio.audioCache.loop, _loopSingleAudio = loopSingleAudio ?? FlameAudio.audioCache.loop,
@ -48,7 +51,8 @@ class PinballAudio {
_configureAudioCache = configureAudioCache ?? _configureAudioCache = configureAudioCache ??
((AudioCache a) { ((AudioCache a) {
a.prefix = ''; a.prefix = '';
}); }),
_seed = seed ?? Random();
final CreateAudioPool _createAudioPool; final CreateAudioPool _createAudioPool;
@ -60,14 +64,24 @@ class PinballAudio {
final ConfigureAudioCache _configureAudioCache; final ConfigureAudioCache _configureAudioCache;
late AudioPool _scorePool; final Random _seed;
late AudioPool _bumperAPool;
late AudioPool _bumperBPool;
/// Loads the sounds effects into the memory /// Loads the sounds effects into the memory
Future<void> load() async { Future<void> load() async {
_configureAudioCache(FlameAudio.audioCache); _configureAudioCache(FlameAudio.audioCache);
_scorePool = await _createAudioPool( _bumperAPool = await _createAudioPool(
_prefixFile(Assets.sfx.plim), _prefixFile(Assets.sfx.bumperA),
maxPlayers: 4,
prefix: '',
);
_bumperBPool = await _createAudioPool(
_prefixFile(Assets.sfx.bumperB),
maxPlayers: 4, maxPlayers: 4,
prefix: '', prefix: '',
); );
@ -79,9 +93,9 @@ class PinballAudio {
]); ]);
} }
/// Plays the basic score sound /// Plays a random bumper sfx.
void score() { void bumper() {
_scorePool.start(); (_seed.nextBool() ? _bumperAPool : _bumperBPool).start(volume: 0.6);
} }
/// Plays the google word bonus /// Plays the google word bonus

@ -1,4 +1,6 @@
// ignore_for_file: prefer_const_constructors, one_member_abstracts // ignore_for_file: prefer_const_constructors, one_member_abstracts
import 'dart:math';
import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/audioplayers.dart';
import 'package:flame_audio/audio_pool.dart'; import 'package:flame_audio/audio_pool.dart';
import 'package:flame_audio/flame_audio.dart'; import 'package:flame_audio/flame_audio.dart';
@ -39,6 +41,8 @@ abstract class _PreCacheSingleAudio {
class _MockPreCacheSingleAudio extends Mock implements _PreCacheSingleAudio {} class _MockPreCacheSingleAudio extends Mock implements _PreCacheSingleAudio {}
class _MockRandom extends Mock implements Random {}
void main() { void main() {
group('PinballAudio', () { group('PinballAudio', () {
late _MockCreateAudioPool createAudioPool; late _MockCreateAudioPool createAudioPool;
@ -46,6 +50,7 @@ void main() {
late _MockPlaySingleAudio playSingleAudio; late _MockPlaySingleAudio playSingleAudio;
late _MockLoopSingleAudio loopSingleAudio; late _MockLoopSingleAudio loopSingleAudio;
late _PreCacheSingleAudio preCacheSingleAudio; late _PreCacheSingleAudio preCacheSingleAudio;
late Random seed;
late PinballAudio audio; late PinballAudio audio;
setUpAll(() { setUpAll(() {
@ -74,12 +79,15 @@ void main() {
preCacheSingleAudio = _MockPreCacheSingleAudio(); preCacheSingleAudio = _MockPreCacheSingleAudio();
when(() => preCacheSingleAudio.onCall(any())).thenAnswer((_) async {}); when(() => preCacheSingleAudio.onCall(any())).thenAnswer((_) async {});
seed = _MockRandom();
audio = PinballAudio( audio = PinballAudio(
configureAudioCache: configureAudioCache.onCall, configureAudioCache: configureAudioCache.onCall,
createAudioPool: createAudioPool.onCall, createAudioPool: createAudioPool.onCall,
playSingleAudio: playSingleAudio.onCall, playSingleAudio: playSingleAudio.onCall,
loopSingleAudio: loopSingleAudio.onCall, loopSingleAudio: loopSingleAudio.onCall,
preCacheSingleAudio: preCacheSingleAudio.onCall, preCacheSingleAudio: preCacheSingleAudio.onCall,
seed: seed,
); );
}); });
@ -88,12 +96,20 @@ void main() {
}); });
group('load', () { group('load', () {
test('creates the score pool', () async { test('creates the bumpers pools', () async {
await audio.load(); await audio.load();
verify( verify(
() => createAudioPool.onCall( () => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.plim}', 'packages/pinball_audio/${Assets.sfx.bumperA}',
maxPlayers: 4,
prefix: '',
),
).called(1);
verify(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.bumperB}',
maxPlayers: 4, maxPlayers: 4,
prefix: '', prefix: '',
), ),
@ -137,22 +153,52 @@ void main() {
}); });
}); });
group('score', () { group('bumper', () {
test('plays the score sound pool', () async { late AudioPool bumperAPool;
final audioPool = _MockAudioPool(); late AudioPool bumperBPool;
when(audioPool.start).thenAnswer((_) async => () {});
setUp(() {
bumperAPool = _MockAudioPool();
when(() => bumperAPool.start(volume: any(named: 'volume')))
.thenAnswer((_) async => () {});
when( when(
() => createAudioPool.onCall( () => createAudioPool.onCall(
any(), 'packages/pinball_audio/${Assets.sfx.bumperA}',
maxPlayers: any(named: 'maxPlayers'), maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'), prefix: any(named: 'prefix'),
), ),
).thenAnswer((_) async => audioPool); ).thenAnswer((_) async => bumperAPool);
bumperBPool = _MockAudioPool();
when(() => bumperBPool.start(volume: any(named: 'volume')))
.thenAnswer((_) async => () {});
when(
() => createAudioPool.onCall(
'packages/pinball_audio/${Assets.sfx.bumperB}',
maxPlayers: any(named: 'maxPlayers'),
prefix: any(named: 'prefix'),
),
).thenAnswer((_) async => bumperBPool);
});
group('when seed is true', () {
test('plays the bumper A sound pool', () async {
when(seed.nextBool).thenReturn(true);
await audio.load(); await audio.load();
audio.score(); audio.bumper();
verify(audioPool.start).called(1); verify(() => bumperAPool.start(volume: 0.6)).called(1);
});
});
group('when seed is false', () {
test('plays the bumper B sound pool', () async {
when(seed.nextBool).thenReturn(false);
await audio.load();
audio.bumper();
verify(() => bumperBPool.start(volume: 0.6)).called(1);
});
}); });
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -22,6 +22,7 @@ class $AssetsImagesGen {
$AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen();
$AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDashGen get dash => const $AssetsImagesDashGen();
$AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen();
$AssetsImagesFlapperGen get flapper => const $AssetsImagesFlapperGen();
$AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen(); $AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen();
$AssetsImagesGoogleWordGen get googleWord => $AssetsImagesGoogleWordGen get googleWord =>
const $AssetsImagesGoogleWordGen(); const $AssetsImagesGoogleWordGen();
@ -129,6 +130,22 @@ class $AssetsImagesDinoGen {
const AssetGenImage('assets/images/dino/top-wall.png'); const AssetGenImage('assets/images/dino/top-wall.png');
} }
class $AssetsImagesFlapperGen {
const $AssetsImagesFlapperGen();
/// File path: assets/images/flapper/back-support.png
AssetGenImage get backSupport =>
const AssetGenImage('assets/images/flapper/back-support.png');
/// File path: assets/images/flapper/flap.png
AssetGenImage get flap =>
const AssetGenImage('assets/images/flapper/flap.png');
/// File path: assets/images/flapper/front-support.png
AssetGenImage get frontSupport =>
const AssetGenImage('assets/images/flapper/front-support.png');
}
class $AssetsImagesFlipperGen { class $AssetsImagesFlipperGen {
const $AssetsImagesFlipperGen(); const $AssetsImagesFlipperGen();

@ -5,14 +5,14 @@ import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/ball_gravitating_behavior.dart';
import 'package:pinball_components/src/components/ball/behaviors/ball_scaling_behavior.dart'; import 'package:pinball_components/src/components/ball/behaviors/ball_scaling_behavior.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template ball} /// {@template ball}
/// A solid, [BodyType.dynamic] sphere that rolls and bounces around. /// A solid, [BodyType.dynamic] sphere that rolls and bounces around.
/// {@endtemplate} /// {@endtemplate}
class Ball<T extends Forge2DGame> extends BodyComponent<T> class Ball extends BodyComponent with Layered, InitialPosition, ZIndex {
with Layered, InitialPosition, ZIndex {
/// {@macro ball} /// {@macro ball}
Ball({ Ball({
required this.baseColor, required this.baseColor,
@ -21,6 +21,7 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
children: [ children: [
_BallSpriteComponent()..tint(baseColor.withOpacity(0.5)), _BallSpriteComponent()..tint(baseColor.withOpacity(0.5)),
BallScalingBehavior(), BallScalingBehavior(),
BallGravitatingBehavior(),
], ],
) { ) {
// TODO(ruimiguel): while developing Ball can be launched by clicking mouse, // TODO(ruimiguel): while developing Ball can be launched by clicking mouse,
@ -86,31 +87,6 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
body.linearVelocity = impulse; body.linearVelocity = impulse;
await add(_TurboChargeSpriteAnimationComponent()); await add(_TurboChargeSpriteAnimationComponent());
} }
@override
void update(double dt) {
super.update(dt);
_setPositionalGravity();
}
void _setPositionalGravity() {
final defaultGravity = gameRef.world.gravity.y;
final maxXDeviationFromCenter = BoardDimensions.bounds.width / 2;
const maxXGravityPercentage =
(1 - BoardDimensions.perspectiveShrinkFactor) / 2;
final xDeviationFromCenter = body.position.x;
final positionalXForce = ((xDeviationFromCenter / maxXDeviationFromCenter) *
maxXGravityPercentage) *
defaultGravity;
final positionalYForce = math.sqrt(
math.pow(defaultGravity, 2) - math.pow(positionalXForce, 2),
);
body.gravityOverride = Vector2(positionalXForce, positionalYForce);
}
} }
class _BallSpriteComponent extends SpriteComponent with HasGameRef { class _BallSpriteComponent extends SpriteComponent with HasGameRef {

@ -0,0 +1,35 @@
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// Scales the ball's gravity according to its position on the board.
class BallGravitatingBehavior extends Component
with ParentIsA<Ball>, HasGameRef<Forge2DGame> {
@override
void update(double dt) {
super.update(dt);
final defaultGravity = gameRef.world.gravity.y;
final maxXDeviationFromCenter = BoardDimensions.bounds.width / 2;
const maxXGravityPercentage =
(1 - BoardDimensions.perspectiveShrinkFactor) / 2;
final xDeviationFromCenter = parent.body.position.x;
final positionalXForce = ((xDeviationFromCenter / maxXDeviationFromCenter) *
maxXGravityPercentage) *
defaultGravity;
final positionalYForce = math.sqrt(
math.pow(defaultGravity, 2) - math.pow(positionalXForce, 2),
);
final gravityOverride = parent.body.gravityOverride;
if (gravityOverride != null) {
gravityOverride.setValues(positionalXForce, positionalYForce);
} else {
parent.body.gravityOverride = Vector2(positionalXForce, positionalYForce);
}
}
}

@ -1 +1,2 @@
export 'ball_gravitating_behavior.dart';
export 'ball_scaling_behavior.dart'; export 'ball_scaling_behavior.dart';

@ -13,6 +13,7 @@ export 'dash_animatronic.dart';
export 'dash_nest_bumper/dash_nest_bumper.dart'; export 'dash_nest_bumper/dash_nest_bumper.dart';
export 'dino_walls.dart'; export 'dino_walls.dart';
export 'fire_effect.dart'; export 'fire_effect.dart';
export 'flapper/flapper.dart';
export 'flipper.dart'; export 'flipper.dart';
export 'google_letter/google_letter.dart'; export 'google_letter/google_letter.dart';
export 'initial_position.dart'; export 'initial_position.dart';

@ -0,0 +1,15 @@
// ignore_for_file: public_member_api_docs
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class FlapperSpinningBehavior extends ContactBehavior<FlapperEntrance> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
parent.parent?.firstChild<SpriteAnimationComponent>()?.playing = true;
}
}

@ -0,0 +1,215 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/flapper/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template flapper}
/// Flap to let a [Ball] out of the [LaunchRamp] and to prevent [Ball]s from
/// going back in.
/// {@endtemplate}
class Flapper extends Component {
/// {@macro flapper}
Flapper()
: super(
children: [
FlapperEntrance(
children: [
FlapperSpinningBehavior(),
],
)..initialPosition = Vector2(4, -69.3),
_FlapperStructure(),
_FlapperExit()..initialPosition = Vector2(-0.6, -33.8),
_BackSupportSpriteComponent(),
_FrontSupportSpriteComponent(),
FlapSpriteAnimationComponent(),
],
);
/// Creates a [Flapper] without any children.
///
/// This can be used for testing [Flapper]'s behaviors in isolation.
@visibleForTesting
Flapper.test();
}
/// {@template flapper_entrance}
/// Sensor used in [FlapperSpinningBehavior] to animate
/// [FlapSpriteAnimationComponent].
/// {@endtemplate}
class FlapperEntrance extends BodyComponent with InitialPosition, Layered {
/// {@macro flapper_entrance}
FlapperEntrance({
Iterable<Component>? children,
}) : super(
children: children,
renderBody: false,
) {
layer = Layer.launcher;
}
@override
Body createBody() {
final shape = EdgeShape()
..set(
Vector2.zero(),
Vector2(0, 3.2),
);
final fixtureDef = FixtureDef(
shape,
isSensor: true,
);
final bodyDef = BodyDef(position: initialPosition);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
class _FlapperStructure extends BodyComponent with Layered {
_FlapperStructure() : super(renderBody: false) {
layer = Layer.board;
}
List<FixtureDef> _createFixtureDefs() {
final leftEdgeShape = EdgeShape()
..set(
Vector2(1.9, -69.3),
Vector2(1.9, -66),
);
final bottomEdgeShape = EdgeShape()
..set(
leftEdgeShape.vertex2,
Vector2(3.9, -66),
);
return [
FixtureDef(leftEdgeShape),
FixtureDef(bottomEdgeShape),
];
}
@override
Body createBody() {
final body = world.createBody(BodyDef());
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}
class _FlapperExit extends LayerSensor {
_FlapperExit()
: super(
insideLayer: Layer.launcher,
outsideLayer: Layer.board,
orientation: LayerEntranceOrientation.down,
insideZIndex: ZIndexes.ballOnLaunchRamp,
outsideZIndex: ZIndexes.ballOnBoard,
) {
layer = Layer.launcher;
}
@override
Shape get shape => PolygonShape()
..setAsBox(
1.7,
0.1,
initialPosition,
1.5708,
);
}
/// {@template flap_sprite_animation_component}
/// Flap suspended between supports that animates to let the [Ball] exit the
/// [LaunchRamp].
/// {@endtemplate}
@visibleForTesting
class FlapSpriteAnimationComponent extends SpriteAnimationComponent
with HasGameRef, ZIndex {
/// {@macro flap_sprite_animation_component}
FlapSpriteAnimationComponent()
: super(
anchor: Anchor.center,
position: Vector2(2.8, -70.7),
playing: false,
) {
zIndex = ZIndexes.flapper;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final spriteSheet = gameRef.images.fromCache(
Assets.images.flapper.flap.keyName,
);
const amountPerRow = 14;
const amountPerColumn = 1;
final textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
size = textureSize / 10;
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
loop: false,
),
)..onComplete = () {
animation?.reset();
playing = false;
};
}
}
class _BackSupportSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_BackSupportSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(2.95, -70.6),
) {
zIndex = ZIndexes.flapperBack;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.flapper.backSupport.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}
class _FrontSupportSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_FrontSupportSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(2.9, -67.6),
) {
zIndex = ZIndexes.flapperFront;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.flapper.frontSupport.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}

@ -5,7 +5,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
/// ///
/// Note: If the [initialPosition] is set after the [BodyComponent] has been /// Note: If the [initialPosition] is set after the [BodyComponent] has been
/// loaded it will have no effect; defaulting to [Vector2.zero]. /// loaded it will have no effect; defaulting to [Vector2.zero].
mixin InitialPosition<T extends Forge2DGame> on BodyComponent<T> { mixin InitialPosition on BodyComponent {
final Vector2 _initialPosition = Vector2.zero(); final Vector2 _initialPosition = Vector2.zero();
set initialPosition(Vector2 value) { set initialPosition(Vector2 value) {

@ -1,7 +1,5 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
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:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -17,8 +15,6 @@ class LaunchRamp extends Component {
children: [ children: [
_LaunchRampBase(), _LaunchRampBase(),
_LaunchRampForegroundRailing(), _LaunchRampForegroundRailing(),
_LaunchRampExit()..initialPosition = Vector2(0.6, -34),
_LaunchRampCloseWall()..initialPosition = Vector2(4, -69.5),
], ],
); );
} }
@ -109,8 +105,10 @@ class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.launchRamp.ramp.keyName, Assets.images.launchRamp.ramp.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
@ -125,8 +123,10 @@ class _LaunchRampBackgroundRailingSpriteComponent extends SpriteComponent
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.launchRamp.backgroundRailing.keyName, Assets.images.launchRamp.backgroundRailing.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
@ -190,8 +190,10 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.launchRamp.foregroundRailing.keyName, Assets.images.launchRamp.foregroundRailing.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
@ -199,51 +201,3 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent
position = Vector2(22.8, 0.5); position = Vector2(22.8, 0.5);
} }
} }
class _LaunchRampCloseWall extends BodyComponent with InitialPosition, Layered {
_LaunchRampCloseWall() : super(renderBody: false) {
layer = Layer.board;
}
@override
Body createBody() {
final shape = EdgeShape()..set(Vector2.zero(), Vector2(0, 3));
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef()
..userData = this
..position = initialPosition;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
/// {@template launch_ramp_exit}
/// [LayerSensor] with [Layer.launcher] to filter [Ball]s exiting the
/// [LaunchRamp].
/// {@endtemplate}
class _LaunchRampExit extends LayerSensor {
/// {@macro launch_ramp_exit}
_LaunchRampExit()
: super(
insideLayer: Layer.launcher,
outsideLayer: Layer.board,
orientation: LayerEntranceOrientation.down,
insideZIndex: ZIndexes.ballOnLaunchRamp,
outsideZIndex: ZIndexes.ballOnBoard,
) {
layer = Layer.launcher;
}
static final Vector2 _size = Vector2(1.6, 0.1);
@override
Shape get shape => PolygonShape()
..setAsBox(
_size.x,
_size.y,
initialPosition,
math.pi / 2,
);
}

@ -9,7 +9,7 @@ import 'package:flutter/material.dart';
/// ignoring others. This compatibility depends on bit masking operation /// ignoring others. This compatibility depends on bit masking operation
/// between layers. For more information read: https://en.wikipedia.org/wiki/Mask_(computing). /// between layers. For more information read: https://en.wikipedia.org/wiki/Mask_(computing).
/// {@endtemplate} /// {@endtemplate}
mixin Layered<T extends Forge2DGame> on BodyComponent<T> { mixin Layered on BodyComponent {
Layer _layer = Layer.all; Layer _layer = Layer.all;
/// {@macro layered} /// {@macro layered}

@ -45,6 +45,12 @@ abstract class ZIndexes {
static const launchRampForegroundRailing = _above + ballOnLaunchRamp; static const launchRampForegroundRailing = _above + ballOnLaunchRamp;
static const flapperBack = _above + outerBoundary;
static const flapperFront = _above + flapper;
static const flapper = _above + ballOnLaunchRamp;
static const plunger = _above + launchRamp; static const plunger = _above + launchRamp;
static const rocket = _below + bottomBoundary; static const rocket = _below + bottomBoundary;

@ -88,6 +88,7 @@ flutter:
- assets/images/multiplier/x6/ - assets/images/multiplier/x6/
- assets/images/score/ - assets/images/score/
- assets/images/backbox/ - assets/images/backbox/
- assets/images/flapper/
flutter_gen: flutter_gen:
line_length: 80 line_length: 80

@ -36,7 +36,8 @@ void main() {
}, },
); );
flameTester.test('add a BallScalingBehavior', (game) async { group('adds', () {
flameTester.test('a BallScalingBehavior', (game) async {
final ball = Ball(baseColor: baseColor); final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball); await game.ensureAdd(ball);
expect( expect(
@ -45,6 +46,16 @@ void main() {
); );
}); });
flameTester.test('a BallGravitatingBehavior', (game) async {
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(
ball.descendants().whereType<BallGravitatingBehavior>().length,
equals(1),
);
});
});
group('body', () { group('body', () {
flameTester.test( flameTester.test(
'is dynamic', 'is dynamic',

@ -0,0 +1,63 @@
// ignore_for_file: cascade_invocations
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/behaviors.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final asset = Assets.images.ball.ball.keyName;
final flameTester = FlameTester(() => TestGame([asset]));
group('BallGravitatingBehavior', () {
const baseColor = Color(0xFFFFFFFF);
test('can be instantiated', () {
expect(
BallGravitatingBehavior(),
isA<BallGravitatingBehavior>(),
);
});
flameTester.test('can be loaded', (game) async {
final ball = Ball.test(baseColor: baseColor);
final behavior = BallGravitatingBehavior();
await ball.add(behavior);
await game.ensureAdd(ball);
expect(
ball.firstChild<BallGravitatingBehavior>(),
equals(behavior),
);
});
flameTester.test(
"overrides the body's horizontal gravity symmetrically",
(game) async {
final ball1 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(10, 0);
await ball1.add(BallGravitatingBehavior());
final ball2 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(-10, 0);
await ball2.add(BallGravitatingBehavior());
await game.ensureAddAll([ball1, ball2]);
game.update(1);
expect(
ball1.body.gravityOverride!.x,
equals(-ball2.body.gravityOverride!.x),
);
expect(
ball1.body.gravityOverride!.y,
equals(ball2.body.gravityOverride!.y),
);
},
);
});
}

@ -35,17 +35,6 @@ void main() {
); );
}); });
flameTester.test('can be loaded', (game) async {
final ball = Ball.test(baseColor: baseColor);
final behavior = BallScalingBehavior();
await ball.add(behavior);
await game.ensureAdd(ball);
expect(
ball.firstChild<BallScalingBehavior>(),
equals(behavior),
);
});
flameTester.test('scales the shape radius', (game) async { flameTester.test('scales the shape radius', (game) async {
final ball1 = Ball.test(baseColor: baseColor) final ball1 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, 10); ..initialPosition = Vector2(0, 10);
@ -66,9 +55,9 @@ void main() {
); );
}); });
flameTester.testGameWidget( flameTester.test(
'scales the sprite', 'scales the sprite',
setUp: (game, tester) async { (game) async {
final ball1 = Ball.test(baseColor: baseColor) final ball1 = Ball.test(baseColor: baseColor)
..initialPosition = Vector2(0, 10); ..initialPosition = Vector2(0, 10);
await ball1.add(BallScalingBehavior()); await ball1.add(BallScalingBehavior());
@ -80,9 +69,6 @@ void main() {
await game.ensureAddAll([ball1, ball2]); await game.ensureAddAll([ball1, ball2]);
game.update(1); game.update(1);
await tester.pump();
await game.ready();
final sprite1 = ball1.firstChild<SpriteComponent>()!; final sprite1 = ball1.firstChild<SpriteComponent>()!;
final sprite2 = ball2.firstChild<SpriteComponent>()!; final sprite2 = ball2.firstChild<SpriteComponent>()!;
expect( expect(

@ -0,0 +1,53 @@
// ignore_for_file: cascade_invocations
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/flapper/behaviors/behaviors.dart';
import '../../../../helpers/helpers.dart';
class _MockBall extends Mock implements Ball {}
class _MockContact extends Mock implements Contact {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.flapper.flap.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group(
'FlapperSpinningBehavior',
() {
test('can be instantiated', () {
expect(
FlapperSpinningBehavior(),
isA<FlapperSpinningBehavior>(),
);
});
flameTester.test(
'beginContact plays the flapper animation',
(game) async {
final behavior = FlapperSpinningBehavior();
final entrance = FlapperEntrance();
final flap = FlapSpriteAnimationComponent();
final flapper = Flapper.test();
await flapper.addAll([entrance, flap]);
await entrance.add(behavior);
await game.ensureAdd(flapper);
behavior.beginContact(_MockBall(), _MockContact());
expect(flap.playing, isTrue);
},
);
},
);
}

@ -0,0 +1,100 @@
// ignore_for_file: cascade_invocations
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/flapper/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../helpers/helpers.dart';
void main() {
group('Flapper', () {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.flapper.flap.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.test('loads correctly', (game) async {
final component = Flapper();
await game.ensureAdd(component);
expect(game.contains(component), isTrue);
});
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final canvas = ZCanvasComponent(children: [Flapper()]);
await game.ensureAdd(canvas);
game.camera
..followVector2(Vector2(3, -70))
..zoom = 25;
await tester.pump();
},
verify: (game, tester) async {
const goldenFilePath = '../golden/flapper/';
final flapSpriteAnimationComponent = game
.descendants()
.whereType<FlapSpriteAnimationComponent>()
.first
..playing = true;
final animationDuration =
flapSpriteAnimationComponent.animation!.totalDuration();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('${goldenFilePath}start.png'),
);
game.update(animationDuration * 0.25);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('${goldenFilePath}middle.png'),
);
game.update(animationDuration * 0.75);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('${goldenFilePath}end.png'),
);
},
);
flameTester.test('adds a FlapperSpiningBehavior to FlapperEntrance',
(game) async {
final flapper = Flapper();
await game.ensureAdd(flapper);
final flapperEntrance = flapper.firstChild<FlapperEntrance>()!;
expect(
flapperEntrance.firstChild<FlapperSpinningBehavior>(),
isNotNull,
);
});
flameTester.test(
'flap stops animating after animation completes',
(game) async {
final flapper = Flapper();
await game.ensureAdd(flapper);
final flapSpriteAnimationComponent =
flapper.firstChild<FlapSpriteAnimationComponent>()!;
flapSpriteAnimationComponent.playing = true;
game.update(
flapSpriteAnimationComponent.animation!.totalDuration() + 0.1,
);
expect(flapSpriteAnimationComponent.playing, isFalse);
},
);
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@ -9,7 +9,13 @@ import '../../helpers/helpers.dart';
void main() { void main() {
group('LaunchRamp', () { group('LaunchRamp', () {
final flameTester = FlameTester(TestGame.new); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.launchRamp.ramp.keyName,
Assets.images.launchRamp.backgroundRailing.keyName,
Assets.images.launchRamp.foregroundRailing.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.test('loads correctly', (game) async { flameTester.test('loads correctly', (game) async {
final component = LaunchRamp(); final component = LaunchRamp();
@ -20,9 +26,12 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'renders correctly', 'renders correctly',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.ensureAdd(LaunchRamp()); await game.ensureAdd(LaunchRamp());
game.camera.followVector2(Vector2.zero()); game.camera.followVector2(Vector2.zero());
game.camera.zoom = 4.1; game.camera.zoom = 4.1;
await game.ready();
await tester.pump();
}, },
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(

@ -4,5 +4,6 @@ export 'src/component_controller.dart';
export 'src/contact_behavior.dart'; export 'src/contact_behavior.dart';
export 'src/keyboard_input_controller.dart'; export 'src/keyboard_input_controller.dart';
export 'src/parent_is_a.dart'; export 'src/parent_is_a.dart';
export 'src/pinball_forge2d_game.dart';
export 'src/sprite_animation.dart'; export 'src/sprite_animation.dart';
export 'src/z_canvas_component.dart'; export 'src/z_canvas_component.dart';

@ -0,0 +1,44 @@
import 'dart:math';
import 'package:flame/game.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_forge2d/world_contact_listener.dart';
// NOTE(wolfen): This should be removed when https://github.com/flame-engine/flame/pull/1597 is solved.
/// {@template pinball_forge2d_game}
/// A [Game] that uses the Forge2D physics engine.
/// {@endtemplate}
class PinballForge2DGame extends FlameGame implements Forge2DGame {
/// {@macro pinball_forge2d_game}
PinballForge2DGame({
required Vector2 gravity,
}) : world = World(gravity),
super(camera: Camera()) {
camera.zoom = Forge2DGame.defaultZoom;
world.setContactListener(WorldContactListener());
}
@override
final World world;
@override
void update(double dt) {
super.update(dt);
world.stepDt(min(dt, 1 / 60));
}
@override
Vector2 screenToFlameWorld(Vector2 position) {
throw UnimplementedError();
}
@override
Vector2 screenToWorld(Vector2 position) {
throw UnimplementedError();
}
@override
Vector2 worldToScreen(Vector2 position) {
throw UnimplementedError();
}
}

@ -0,0 +1,51 @@
// ignore_for_file: cascade_invocations
import 'package:flame/extensions.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_flame/pinball_flame.dart';
void main() {
final flameTester = FlameTester(
() => PinballForge2DGame(gravity: Vector2.zero()),
);
group('PinballForge2DGame', () {
test('can instantiate', () {
expect(
() => PinballForge2DGame(gravity: Vector2.zero()),
returnsNormally,
);
});
flameTester.test(
'screenToFlameWorld throws UnimpelementedError',
(game) async {
expect(
() => game.screenToFlameWorld(Vector2.zero()),
throwsUnimplementedError,
);
},
);
flameTester.test(
'screenToWorld throws UnimpelementedError',
(game) async {
expect(
() => game.screenToWorld(Vector2.zero()),
throwsUnimplementedError,
);
},
);
flameTester.test(
'worldToScreen throws UnimpelementedError',
(game) async {
expect(
() => game.worldToScreen(Vector2.zero()),
throwsUnimplementedError,
);
},
);
});
}

@ -2,7 +2,6 @@
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/extensions.dart'; import 'package:flame/extensions.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';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
@ -15,7 +14,7 @@ import '../../helpers/helpers.dart';
// TODO(allisonryan0002): remove once // TODO(allisonryan0002): remove once
// https://github.com/flame-engine/flame/pull/1520 is merged // https://github.com/flame-engine/flame/pull/1520 is merged
class _WrappedBallController extends BallController { class _WrappedBallController extends BallController {
_WrappedBallController(Ball<Forge2DGame> ball, this._gameRef) : super(ball); _WrappedBallController(Ball ball, this._gameRef) : super(ball);
final PinballGame _gameRef; final PinballGame _gameRef;

@ -0,0 +1,85 @@
// ignore_for_file: cascade_invocations
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.launchRamp.ramp.keyName,
Assets.images.launchRamp.backgroundRailing.keyName,
Assets.images.launchRamp.foregroundRailing.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
Assets.images.flapper.flap.keyName,
Assets.images.plunger.plunger.keyName,
Assets.images.plunger.rocket.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
);
group('Launcher', () {
flameTester.test(
'loads correctly',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
expect(game.contains(launcher), isTrue);
},
);
group('loads', () {
flameTester.test(
'a LaunchRamp',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
final descendantsQuery =
launcher.descendants().whereType<LaunchRamp>();
expect(descendantsQuery.length, equals(1));
},
);
flameTester.test(
'a Flapper',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
final descendantsQuery = launcher.descendants().whereType<Flapper>();
expect(descendantsQuery.length, equals(1));
},
);
flameTester.test(
'a Plunger',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
final descendantsQuery = launcher.descendants().whereType<Plunger>();
expect(descendantsQuery.length, equals(1));
},
);
flameTester.test(
'a RocketSpriteComponent',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
final descendantsQuery =
launcher.descendants().whereType<RocketSpriteComponent>();
expect(descendantsQuery.length, equals(1));
},
);
});
});
}

@ -90,20 +90,6 @@ void main() {
}, },
); );
flameBlocTester.testGameWidget(
'plays score sound',
setUp: (game, tester) async {
final scoringBehavior = ScoringBehavior(points: Points.oneMillion);
await parent.add(scoringBehavior);
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
scoringBehavior.beginContact(ball, _MockContact());
verify(audio.score).called(1);
},
);
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
"adds a ScoreComponent at Ball's position with points", "adds a ScoreComponent at Ball's position with points",
setUp: (game, tester) async { setUp: (game, tester) async {
@ -130,4 +116,57 @@ void main() {
); );
}); });
}); });
group('BumperScoringBehavior', () {
group('beginContact', () {
late GameBloc bloc;
late PinballAudio audio;
late Ball ball;
late BodyComponent parent;
setUp(() {
audio = _MockPinballAudio();
ball = _MockBall();
final ballBody = _MockBody();
when(() => ball.body).thenReturn(ballBody);
when(() => ballBody.position).thenReturn(Vector2.all(4));
parent = _TestBodyComponent();
});
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
gameBuilder: () => EmptyPinballTestGame(
audio: audio,
),
blocBuilder: () {
bloc = _MockGameBloc();
const state = GameState(
score: 0,
multiplier: 1,
rounds: 3,
bonusHistory: [],
);
whenListen(bloc, Stream.value(state), initialState: state);
return bloc;
},
assets: assets,
);
flameBlocTester.testGameWidget(
'plays bumper sound',
setUp: (game, tester) async {
final scoringBehavior = BumperScoringBehavior(
points: Points.oneMillion,
);
await parent.add(scoringBehavior);
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
scoringBehavior.beginContact(ball, _MockContact());
verify(audio.bumper).called(1);
},
);
});
});
} }

@ -123,6 +123,9 @@ void main() {
Assets.images.sparky.bumper.b.dimmed.keyName, Assets.images.sparky.bumper.b.dimmed.keyName,
Assets.images.sparky.bumper.c.lit.keyName, Assets.images.sparky.bumper.c.lit.keyName,
Assets.images.sparky.bumper.c.dimmed.keyName, Assets.images.sparky.bumper.c.dimmed.keyName,
Assets.images.flapper.flap.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
]; ];
late GameBloc gameBloc; late GameBloc gameBloc;
@ -322,7 +325,7 @@ void main() {
(flipper) => flipper.side == BoardSide.left, (flipper) => flipper.side == BoardSide.left,
); );
game.onTapDown(tapDownEvent); game.onTapDown(0, tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative); expect(flippers.first.body.linearVelocity.y, isNegative);
}); });
@ -345,7 +348,7 @@ void main() {
(flipper) => flipper.side == BoardSide.right, (flipper) => flipper.side == BoardSide.right,
); );
game.onTapDown(tapDownEvent); game.onTapDown(0, tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative); expect(flippers.first.body.linearVelocity.y, isNegative);
}); });
@ -368,14 +371,14 @@ void main() {
(flipper) => flipper.side == BoardSide.left, (flipper) => flipper.side == BoardSide.left,
); );
game.onTapDown(tapDownEvent); game.onTapDown(0, tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative); expect(flippers.first.body.linearVelocity.y, isNegative);
final tapUpEvent = _MockTapUpInfo(); final tapUpEvent = _MockTapUpInfo();
when(() => tapUpEvent.eventPosition).thenReturn(eventPosition); when(() => tapUpEvent.eventPosition).thenReturn(eventPosition);
game.onTapUp(tapUpEvent); game.onTapUp(0, tapUpEvent);
await game.ready(); await game.ready();
expect(flippers.first.body.linearVelocity.y, isPositive); expect(flippers.first.body.linearVelocity.y, isPositive);
@ -399,11 +402,11 @@ void main() {
(flipper) => flipper.side == BoardSide.left, (flipper) => flipper.side == BoardSide.left,
); );
game.onTapDown(tapDownEvent); game.onTapDown(0, tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative); expect(flippers.first.body.linearVelocity.y, isNegative);
game.onTapCancel(); game.onTapCancel(0);
expect(flippers.first.body.linearVelocity.y, isPositive); expect(flippers.first.body.linearVelocity.y, isPositive);
}); });
@ -425,7 +428,7 @@ void main() {
final plunger = game.descendants().whereType<Plunger>().first; final plunger = game.descendants().whereType<Plunger>().first;
game.onTapDown(tapDownEvent); game.onTapDown(0, tapDownEvent);
game.update(1); game.update(1);
@ -451,7 +454,7 @@ void main() {
final previousBalls = final previousBalls =
game.descendants().whereType<ControlledBall>().toList(); game.descendants().whereType<ControlledBall>().toList();
game.onTapUp(tapUpEvent); game.onTapUp(0, tapUpEvent);
await game.ready(); await game.ready();
expect( expect(

Loading…
Cancel
Save