Merge branch 'main' into refactor/ball-turbo-charging

pull/323/head
Alejandro Santiago 3 years ago committed by GitHub
commit ff0d0da47f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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),

@ -130,6 +130,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';
@ -14,12 +13,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,
@ -81,7 +80,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;
@ -99,19 +98,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() {
@ -182,8 +181,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);
await audio.load(); bumperBPool = _MockAudioPool();
audio.score(); 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();
audio.bumper();
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(audioPool.start).called(1); 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();
@ -133,6 +134,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();

@ -11,8 +11,7 @@ export 'behaviors/behaviors.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,

@ -14,6 +14,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(
Assets.images.launchRamp.ramp.keyName, gameRef.images.fromCache(
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(
Assets.images.launchRamp.backgroundRailing.keyName, gameRef.images.fromCache(
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(
Assets.images.launchRamp.foregroundRailing.keyName, gameRef.images.fromCache(
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/x5/ - assets/images/multiplier/x5/
- assets/images/multiplier/x6/ - assets/images/multiplier/x6/
- assets/images/score/ - assets/images/score/
- assets/images/flapper/
flutter_gen: flutter_gen:
line_length: 80 line_length: 80

@ -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,
);
},
);
});
}

@ -3,7 +3,6 @@
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart'; import 'package:flame/components.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';
@ -16,7 +15,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);
},
);
});
});
} }

@ -124,6 +124,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;
@ -323,7 +326,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);
}); });
@ -346,7 +349,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);
}); });
@ -369,14 +372,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);
@ -400,11 +403,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);
}); });
@ -426,7 +429,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);
@ -452,7 +455,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(

@ -26,14 +26,13 @@
<!-- Open Graph Data --> <!-- Open Graph Data -->
<meta property="og:title" content="Google I/O Pinball"> <meta property="og:title" content="Google I/O Pinball">
<!-- TODO(jonathandaniels-vgv): revisit once Google sets up deployments --> <meta property="og:url" content="https://pinball.flutter.dev">
<meta property="og:url" content="https://flutter.dev">
<meta property="og:image" <meta property="og:image"
content="https://firebasestorage.googleapis.com/v0/b/pinball-dev.appspot.com/o/images%2Fpinball_share_image.png?alt=media"> content="https://firebasestorage.googleapis.com/v0/b/io-pinball.appspot.com/o/images%2Fpinball_share_image.png?alt=media">
<!-- Twitter Share Data --> <!-- Twitter Share Data -->
<meta name="twitter:image" <meta name="twitter:image"
content="https://firebasestorage.googleapis.com/v0/b/pinball-dev.appspot.com/o/images%2Fpinball_share_image.png?alt=media"> content="https://firebasestorage.googleapis.com/v0/b/io-pinball.appspot.com/o/images%2Fpinball_share_image.png?alt=media">
<meta name="twitter:text:title" content="I/O Pinball Machine - Flutter"> <meta name="twitter:text:title" content="I/O Pinball Machine - Flutter">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="I/O Pinball Machine - Flutter"> <meta name="twitter:title" content="I/O Pinball Machine - Flutter">

Loading…
Cancel
Save