From 182e8f56cb8aee5c951e86c16259c61d9d78dce1 Mon Sep 17 00:00:00 2001 From: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Date: Tue, 3 May 2022 15:40:34 -0500 Subject: [PATCH] feat: implemented `Flapper` (#312) * feat: add flapper * chore: add assets to pinball game test * fix: add mocks in file * test: check animation onComplete * fix: image cache in test * Update packages/pinball_components/lib/src/components/flapper/flapper.dart * style: commas and userData removal * refactor: make launcher tests more robust * refactor: removed children parameter Co-authored-by: alestiago --- lib/game/components/launcher.dart | 1 + lib/game/game_assets.dart | 3 + .../assets/images/flapper/back-support.png | Bin 0 -> 1474 bytes .../assets/images/flapper/flap.png | Bin 0 -> 31117 bytes .../assets/images/flapper/front-support.png | Bin 0 -> 1540 bytes .../lib/gen/assets.gen.dart | 17 ++ .../lib/src/components/components.dart | 1 + .../flapper/behaviors/behaviors.dart | 1 + .../behaviors/flapper_spinning_behavior.dart | 15 ++ .../lib/src/components/flapper/flapper.dart | 215 ++++++++++++++++++ .../lib/src/components/launch_ramp.dart | 70 +----- .../lib/src/components/z_indexes.dart | 6 + packages/pinball_components/pubspec.yaml | 1 + .../flapper_spinning_behavior_test.dart | 53 +++++ .../src/components/flapper/flapper_test.dart | 100 ++++++++ .../src/components/golden/flapper/end.png | Bin 0 -> 26826 bytes .../src/components/golden/flapper/middle.png | Bin 0 -> 28444 bytes .../src/components/golden/flapper/start.png | Bin 0 -> 26812 bytes .../test/src/components/launch_ramp_test.dart | 11 +- test/game/components/launcher_test.dart | 85 +++++++ test/game/pinball_game_test.dart | 3 + 21 files changed, 523 insertions(+), 59 deletions(-) create mode 100644 packages/pinball_components/assets/images/flapper/back-support.png create mode 100644 packages/pinball_components/assets/images/flapper/flap.png create mode 100644 packages/pinball_components/assets/images/flapper/front-support.png create mode 100644 packages/pinball_components/lib/src/components/flapper/behaviors/behaviors.dart create mode 100644 packages/pinball_components/lib/src/components/flapper/behaviors/flapper_spinning_behavior.dart create mode 100644 packages/pinball_components/lib/src/components/flapper/flapper.dart create mode 100644 packages/pinball_components/test/src/components/flapper/behaviors/flapper_spinning_behavior_test.dart create mode 100644 packages/pinball_components/test/src/components/flapper/flapper_test.dart create mode 100644 packages/pinball_components/test/src/components/golden/flapper/end.png create mode 100644 packages/pinball_components/test/src/components/golden/flapper/middle.png create mode 100644 packages/pinball_components/test/src/components/golden/flapper/start.png create mode 100644 test/game/components/launcher_test.dart diff --git a/lib/game/components/launcher.dart b/lib/game/components/launcher.dart index ffac6507..da1a3569 100644 --- a/lib/game/components/launcher.dart +++ b/lib/game/components/launcher.dart @@ -12,6 +12,7 @@ class Launcher extends Component { : super( children: [ LaunchRamp(), + Flapper(), ControlledPlunger(compressionDistance: 9.2) ..initialPosition = Vector2(41.2, 43.7), RocketSpriteComponent()..position = Vector2(43, 62.3), diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 2a847ce0..22c1c2d6 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -130,6 +130,9 @@ extension PinballGameAssetsX on PinballGame { images.load(components.Assets.images.score.twentyThousand.keyName), images.load(components.Assets.images.score.twoHundredThousand.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(sparkyTheme.leaderboardIcon.keyName), images.load(androidTheme.leaderboardIcon.keyName), diff --git a/packages/pinball_components/assets/images/flapper/back-support.png b/packages/pinball_components/assets/images/flapper/back-support.png new file mode 100644 index 0000000000000000000000000000000000000000..74b3ae842e9ee22b9a47a7cd0190e950cac08205 GIT binary patch literal 1474 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 literal 0 HcmV?d00001 diff --git a/packages/pinball_components/assets/images/flapper/front-support.png b/packages/pinball_components/assets/images/flapper/front-support.png new file mode 100644 index 0000000000000000000000000000000000000000..c3b7ca1e31f198da05e454b51ef9a0a0420f3175 GIT binary patch literal 1540 zcmV+f2K)JmP)kPM-_&@e^u4K^yT!$=U@lNwk(p^ag1z)lnDdG z03;^Nc?aHtSICe7@4zEO%s?*y8f{1rV1hYb{NLZ=IOs4ee%~|fBw<$KFFtk z|M+X9PKxuJZ>?^A|AYDR`ui#M<`}{?I+mL5D0a*DiT(2Y@xOn|c)Iz&K#H^Z{LSzE za<;trvnHfC>k@4W3?rOw2gY$Eh7d`UJHt49JUssAA0Yde7x*i#baefl>Cu}%HEZIu zF}fH?=FGa5KJ{ep*$pEp_Or?A_RlS?moHb+%fku%;`)zbdvYRckwrdzEI#?~r<|WZ z!Eo>`l8~gPzj8OsuHAiYkTuJBKfnGHg_ttVW*mv(DP3daoY@W=P$sA-%HnL<&fokY zis7|DX?lDkb&GorWOtr!jkD1>r{&2`3=X)n8#BW=APupZo%}#yHhB$@)J(42QS4XX zNJeJO`4~7K#oz&OOduJufMiPD)$drdUR@d_OQD&pAGn1UGAJN@1}cJboJ<0>n1m@H z8e&Svpua>aIj+nUAdl^|K0(q#Qm3aO;1EfYcl zi%`>6{+l3cDjZP8Vt*Va$2VRLibvDbFW-|jk_@SVnzpid#1yL|oFm2(X_dN#Zkqbl z-7gB_0x0zBt05iTlDaxY&T*ROCdyuc_H5A98oGm7N~vFe7d7!KKvJ5l-?rGV-81f1 zlM^0 zLhKZp)h%n+>z9HQrd^t>9-?5S;KJ#_i{CkZa0jpwm8*`R(<+n(OS9w9FK@mSWbI-Z zoB7*d@|=N){H&&>0U0_xl8dC-g2q~|400^kBu&=ug6T^^pPJHT@g+WqrH|Qm{0+a!(=6!OB7y(1JEq5N&}uhk8k&x<%NV z6tY&aU%pFNOfG<=CN|UcLs?9v%dL8xz_%qj-vA|zzBgAtJxG3UxZ+G9HMgu;T)7B} z^GWK~_a(D~OA|o5ecvNRq@lvwLtmAITD7Pmq7a+o(9dsP09n624$Zf2?`2vR*ntnF z$K7yd#ibc*I*Wr#0he$hqA4aDx=HF62ZuO-LU**Xs3|z^;O-SxAkdYKd%%659EbOV zP{=`YoCLl@@({b#H5BZ{h*Hy`Asq~|F5`!9JJ6%O^A`UjAx&!o_x0XWoO6)L(yBvA zdv7TqOPyr#pk6{9+N#kHK#fMl>)eMbdj~ei%aC^jZb;i!wZ*OOPztFRSn)WX>zE=8 zDILxW0PeHm3SQ%OuM?L#Bthjs_K30#Ll3n-QI;&FMae*`-wUSL+)IDD#VG3P2I++*# zM*E&9jaz|xQ&p(@%!fXLP^Xq7+o2E=$~*^hPwf6Owe4?0h^_n9Bn*<=vmBG;GMUDl zak7L-Lhi27qXiR?RlSZeM;|!<6Abx<7cq-RF?L5u7KA%UDpRynLiM-vOqXUR_gYPv q_GduJ$YW-A{^Wf3^rKV0$?Jc6df?ojf!$~T0000 const $AssetsImagesBoundaryGen(); $AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); + $AssetsImagesFlapperGen get flapper => const $AssetsImagesFlapperGen(); $AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen(); $AssetsImagesGoogleWordGen get googleWord => const $AssetsImagesGoogleWordGen(); @@ -133,6 +134,22 @@ class $AssetsImagesDinoGen { 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 { const $AssetsImagesFlipperGen(); diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 6e79ac56..d3d4253b 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -14,6 +14,7 @@ export 'dash_animatronic.dart'; export 'dash_nest_bumper/dash_nest_bumper.dart'; export 'dino_walls.dart'; export 'fire_effect.dart'; +export 'flapper/flapper.dart'; export 'flipper.dart'; export 'google_letter/google_letter.dart'; export 'initial_position.dart'; diff --git a/packages/pinball_components/lib/src/components/flapper/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/flapper/behaviors/behaviors.dart new file mode 100644 index 00000000..573578e5 --- /dev/null +++ b/packages/pinball_components/lib/src/components/flapper/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'flapper_spinning_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/flapper/behaviors/flapper_spinning_behavior.dart b/packages/pinball_components/lib/src/components/flapper/behaviors/flapper_spinning_behavior.dart new file mode 100644 index 00000000..9a4e2a99 --- /dev/null +++ b/packages/pinball_components/lib/src/components/flapper/behaviors/flapper_spinning_behavior.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 { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + if (other is! Ball) return; + parent.parent?.firstChild()?.playing = true; + } +} diff --git a/packages/pinball_components/lib/src/components/flapper/flapper.dart b/packages/pinball_components/lib/src/components/flapper/flapper.dart new file mode 100644 index 00000000..f336273e --- /dev/null +++ b/packages/pinball_components/lib/src/components/flapper/flapper.dart @@ -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? 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 _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 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 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 onLoad() async { + await super.onLoad(); + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.flapper.frontSupport.keyName, + ), + ); + this.sprite = sprite; + size = sprite.originalSize / 10; + } +} diff --git a/packages/pinball_components/lib/src/components/launch_ramp.dart b/packages/pinball_components/lib/src/components/launch_ramp.dart index 4713c3a2..7dcc274e 100644 --- a/packages/pinball_components/lib/src/components/launch_ramp.dart +++ b/packages/pinball_components/lib/src/components/launch_ramp.dart @@ -1,7 +1,5 @@ // ignore_for_file: avoid_renaming_method_parameters -import 'dart:math' as math; - import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -17,8 +15,6 @@ class LaunchRamp extends Component { children: [ _LaunchRampBase(), _LaunchRampForegroundRailing(), - _LaunchRampExit()..initialPosition = Vector2(0.6, -34), - _LaunchRampCloseWall()..initialPosition = Vector2(4, -69.5), ], ); } @@ -109,8 +105,10 @@ class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { Future onLoad() async { await super.onLoad(); - final sprite = await gameRef.loadSprite( - Assets.images.launchRamp.ramp.keyName, + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.launchRamp.ramp.keyName, + ), ); this.sprite = sprite; size = sprite.originalSize / 10; @@ -125,8 +123,10 @@ class _LaunchRampBackgroundRailingSpriteComponent extends SpriteComponent Future onLoad() async { await super.onLoad(); - final sprite = await gameRef.loadSprite( - Assets.images.launchRamp.backgroundRailing.keyName, + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.launchRamp.backgroundRailing.keyName, + ), ); this.sprite = sprite; size = sprite.originalSize / 10; @@ -190,8 +190,10 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent Future onLoad() async { await super.onLoad(); - final sprite = await gameRef.loadSprite( - Assets.images.launchRamp.foregroundRailing.keyName, + final sprite = Sprite( + gameRef.images.fromCache( + Assets.images.launchRamp.foregroundRailing.keyName, + ), ); this.sprite = sprite; size = sprite.originalSize / 10; @@ -199,51 +201,3 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent 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, - ); -} diff --git a/packages/pinball_components/lib/src/components/z_indexes.dart b/packages/pinball_components/lib/src/components/z_indexes.dart index b8371273..a04402b5 100644 --- a/packages/pinball_components/lib/src/components/z_indexes.dart +++ b/packages/pinball_components/lib/src/components/z_indexes.dart @@ -45,6 +45,12 @@ abstract class ZIndexes { 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 rocket = _below + bottomBoundary; diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 61e62386..9716a526 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -88,6 +88,7 @@ flutter: - assets/images/multiplier/x5/ - assets/images/multiplier/x6/ - assets/images/score/ + - assets/images/flapper/ flutter_gen: line_length: 80 diff --git a/packages/pinball_components/test/src/components/flapper/behaviors/flapper_spinning_behavior_test.dart b/packages/pinball_components/test/src/components/flapper/behaviors/flapper_spinning_behavior_test.dart new file mode 100644 index 00000000..c53dc17b --- /dev/null +++ b/packages/pinball_components/test/src/components/flapper/behaviors/flapper_spinning_behavior_test.dart @@ -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(), + ); + }); + + 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); + }, + ); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/flapper/flapper_test.dart b/packages/pinball_components/test/src/components/flapper/flapper_test.dart new file mode 100644 index 00000000..497bb5f6 --- /dev/null +++ b/packages/pinball_components/test/src/components/flapper/flapper_test.dart @@ -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() + .first + ..playing = true; + final animationDuration = + flapSpriteAnimationComponent.animation!.totalDuration(); + + await expectLater( + find.byGame(), + matchesGoldenFile('${goldenFilePath}start.png'), + ); + + game.update(animationDuration * 0.25); + await tester.pump(); + await expectLater( + find.byGame(), + matchesGoldenFile('${goldenFilePath}middle.png'), + ); + + game.update(animationDuration * 0.75); + await tester.pump(); + await expectLater( + find.byGame(), + matchesGoldenFile('${goldenFilePath}end.png'), + ); + }, + ); + + flameTester.test('adds a FlapperSpiningBehavior to FlapperEntrance', + (game) async { + final flapper = Flapper(); + await game.ensureAdd(flapper); + + final flapperEntrance = flapper.firstChild()!; + expect( + flapperEntrance.firstChild(), + isNotNull, + ); + }); + + flameTester.test( + 'flap stops animating after animation completes', + (game) async { + final flapper = Flapper(); + await game.ensureAdd(flapper); + + final flapSpriteAnimationComponent = + flapper.firstChild()!; + + flapSpriteAnimationComponent.playing = true; + game.update( + flapSpriteAnimationComponent.animation!.totalDuration() + 0.1, + ); + + expect(flapSpriteAnimationComponent.playing, isFalse); + }, + ); + }); +} diff --git a/packages/pinball_components/test/src/components/golden/flapper/end.png b/packages/pinball_components/test/src/components/golden/flapper/end.png new file mode 100644 index 0000000000000000000000000000000000000000..31319b3710777445c2a9695341320fe4f7056f3b GIT binary patch literal 26826 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 VHd$c*fAE}kh3@&NZu{q7{Xcy;edquH literal 0 HcmV?d00001 diff --git a/packages/pinball_components/test/src/components/golden/flapper/start.png b/packages/pinball_components/test/src/components/golden/flapper/start.png new file mode 100644 index 0000000000000000000000000000000000000000..e6da466a8cd77afdd08506bdabdd8d9f433b3e44 GIT binary patch literal 26812 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 TestGame(assets)); flameTester.test('loads correctly', (game) async { final component = LaunchRamp(); @@ -20,9 +26,12 @@ void main() { flameTester.testGameWidget( 'renders correctly', setUp: (game, tester) async { + await game.images.loadAll(assets); await game.ensureAdd(LaunchRamp()); game.camera.followVector2(Vector2.zero()); game.camera.zoom = 4.1; + await game.ready(); + await tester.pump(); }, verify: (game, tester) async { await expectLater( diff --git a/test/game/components/launcher_test.dart b/test/game/components/launcher_test.dart new file mode 100644 index 00000000..c76e6b7e --- /dev/null +++ b/test/game/components/launcher_test.dart @@ -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(); + expect(descendantsQuery.length, equals(1)); + }, + ); + + flameTester.test( + 'a Flapper', + (game) async { + final launcher = Launcher(); + await game.ensureAdd(launcher); + + final descendantsQuery = launcher.descendants().whereType(); + expect(descendantsQuery.length, equals(1)); + }, + ); + + flameTester.test( + 'a Plunger', + (game) async { + final launcher = Launcher(); + await game.ensureAdd(launcher); + + final descendantsQuery = launcher.descendants().whereType(); + expect(descendantsQuery.length, equals(1)); + }, + ); + + flameTester.test( + 'a RocketSpriteComponent', + (game) async { + final launcher = Launcher(); + await game.ensureAdd(launcher); + + final descendantsQuery = + launcher.descendants().whereType(); + expect(descendantsQuery.length, equals(1)); + }, + ); + }); + }); +} diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index b85dba5c..dd87fec0 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -124,6 +124,9 @@ void main() { Assets.images.sparky.bumper.b.dimmed.keyName, Assets.images.sparky.bumper.c.lit.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;