From 1c8813e0391f941c7dbf8601fab4fe0e6b620d7a Mon Sep 17 00:00:00 2001 From: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com> Date: Mon, 9 May 2022 18:53:45 -0500 Subject: [PATCH] feat: loop animatronics (#452) * feat: loop animatronics * chore: rename animationCooldown * fix: appease cspell Co-authored-by: Tom Arra --- .../animatronic_looping_behavior.dart | 26 +++++ lib/game/behaviors/behaviors.dart | 1 + .../flutter_forest_bonus_behavior.dart | 2 - .../flutter_forest/flutter_forest.dart | 6 +- .../sparky_computer_bonus_behavior.dart | 2 - .../sparky_scorch/sparky_scorch.dart | 6 +- .../lib/src/components/dash_animatronic.dart | 8 +- .../src/components/sparky_animatronic.dart | 8 +- .../src/components/dash_animatronic_test.dart | 22 ++-- .../components/sparky_animatronic_test.dart | 24 ++-- .../animatronic_looping_behavior_test.dart | 105 ++++++++++++++++++ .../flutter_forest_bonus_behavior_test.dart | 14 +-- .../sparky_computer_bonus_behavior_test.dart | 10 +- 13 files changed, 172 insertions(+), 62 deletions(-) create mode 100644 lib/game/behaviors/animatronic_looping_behavior.dart create mode 100644 test/game/behaviors/animatronic_looping_behavior_test.dart diff --git a/lib/game/behaviors/animatronic_looping_behavior.dart b/lib/game/behaviors/animatronic_looping_behavior.dart new file mode 100644 index 00000000..ec741608 --- /dev/null +++ b/lib/game/behaviors/animatronic_looping_behavior.dart @@ -0,0 +1,26 @@ +import 'package:flame/components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class AnimatronicLoopingBehavior extends TimerComponent + with ParentIsA { + AnimatronicLoopingBehavior({ + required double animationCoolDown, + }) : super(period: animationCoolDown); + + @override + Future onLoad() async { + await super.onLoad(); + parent.animation?.onComplete = () { + parent.animation?.reset(); + parent.playing = false; + timer + ..reset() + ..start(); + }; + } + + @override + void onTick() { + parent.playing = true; + } +} diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart index bca8be14..03a99b57 100644 --- a/lib/game/behaviors/behaviors.dart +++ b/lib/game/behaviors/behaviors.dart @@ -1,3 +1,4 @@ +export 'animatronic_looping_behavior.dart'; export 'ball_spawning_behavior.dart'; export 'bonus_ball_spawning_behavior.dart'; export 'bonus_noise_behavior.dart'; diff --git a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart index 3c4ef02a..d13297f0 100644 --- a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart +++ b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart @@ -21,7 +21,6 @@ class FlutterForestBonusBehavior extends Component final bumpers = parent.children.whereType(); final signpost = parent.firstChild()!; - final animatronic = parent.firstChild()!; for (final bumper in bumpers) { bumper.bloc.stream.listen((state) { @@ -38,7 +37,6 @@ class FlutterForestBonusBehavior extends Component if (signpost.bloc.isFullyProgressed()) { bloc.add(const BonusActivated(GameBonus.dashNest)); add(BonusBallSpawningBehavior()); - animatronic.playing = true; signpost.bloc.onProgressed(); } } diff --git a/lib/game/components/flutter_forest/flutter_forest.dart b/lib/game/components/flutter_forest/flutter_forest.dart index 39783bb1..57bd24c4 100644 --- a/lib/game/components/flutter_forest/flutter_forest.dart +++ b/lib/game/components/flutter_forest/flutter_forest.dart @@ -40,7 +40,11 @@ class FlutterForest extends Component with ZIndex { BumperNoiseBehavior(), ], )..initialPosition = Vector2(21.8, -46.75), - DashAnimatronic()..position = Vector2(20, -66), + DashAnimatronic( + children: [ + AnimatronicLoopingBehavior(animationCoolDown: 4), + ], + )..position = Vector2(20, -66), FlutterForestBonusBehavior(), ], ) { diff --git a/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart b/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart index d1b8898e..3cd2fc0b 100644 --- a/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart +++ b/lib/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior.dart @@ -12,13 +12,11 @@ class SparkyComputerBonusBehavior extends Component void onMount() { super.onMount(); final sparkyComputer = parent.firstChild()!; - final animatronic = parent.firstChild()!; sparkyComputer.bloc.stream.listen((state) async { final listenWhen = state == SparkyComputerState.withBall; if (!listenWhen) return; bloc.add(const BonusActivated(GameBonus.sparkyTurboCharge)); - animatronic.playing = true; }); } } diff --git a/lib/game/components/sparky_scorch/sparky_scorch.dart b/lib/game/components/sparky_scorch/sparky_scorch.dart index da624361..c7cd71d3 100644 --- a/lib/game/components/sparky_scorch/sparky_scorch.dart +++ b/lib/game/components/sparky_scorch/sparky_scorch.dart @@ -33,7 +33,11 @@ class SparkyScorch extends Component { BumperNoiseBehavior(), ], )..initialPosition = Vector2(-3.3, -52.55), - SparkyAnimatronic()..position = Vector2(-14, -58.2), + SparkyAnimatronic( + children: [ + AnimatronicLoopingBehavior(animationCoolDown: 3), + ], + )..position = Vector2(-14, -58.2), SparkyComputer( children: [ ScoringContactBehavior(points: Points.twoHundredThousand) diff --git a/packages/pinball_components/lib/src/components/dash_animatronic.dart b/packages/pinball_components/lib/src/components/dash_animatronic.dart index bb7d983b..7202dd09 100644 --- a/packages/pinball_components/lib/src/components/dash_animatronic.dart +++ b/packages/pinball_components/lib/src/components/dash_animatronic.dart @@ -6,10 +6,11 @@ import 'package:pinball_components/pinball_components.dart'; /// {@endtemplate} class DashAnimatronic extends SpriteAnimationComponent with HasGameRef { /// {@macro dash_animatronic} - DashAnimatronic() + DashAnimatronic({Iterable? children}) : super( anchor: Anchor.center, playing: false, + children: children, ); @override @@ -37,9 +38,6 @@ class DashAnimatronic extends SpriteAnimationComponent with HasGameRef { textureSize: textureSize, loop: false, ), - )..onComplete = () { - animation?.reset(); - playing = false; - }; + ); } } diff --git a/packages/pinball_components/lib/src/components/sparky_animatronic.dart b/packages/pinball_components/lib/src/components/sparky_animatronic.dart index 2ee2803c..05a16e47 100644 --- a/packages/pinball_components/lib/src/components/sparky_animatronic.dart +++ b/packages/pinball_components/lib/src/components/sparky_animatronic.dart @@ -8,10 +8,11 @@ import 'package:pinball_flame/pinball_flame.dart'; class SparkyAnimatronic extends SpriteAnimationComponent with HasGameRef, ZIndex { /// {@macro sparky_animatronic} - SparkyAnimatronic() + SparkyAnimatronic({Iterable? children}) : super( anchor: Anchor.center, playing: false, + children: children, ) { zIndex = ZIndexes.sparkyAnimatronic; } @@ -41,9 +42,6 @@ class SparkyAnimatronic extends SpriteAnimationComponent textureSize: textureSize, loop: false, ), - )..onComplete = () { - animation?.reset(); - playing = false; - }; + ); } } diff --git a/packages/pinball_components/test/src/components/dash_animatronic_test.dart b/packages/pinball_components/test/src/components/dash_animatronic_test.dart index d64c3f07..282e9f8d 100644 --- a/packages/pinball_components/test/src/components/dash_animatronic_test.dart +++ b/packages/pinball_components/test/src/components/dash_animatronic_test.dart @@ -1,6 +1,6 @@ // ignore_for_file: cascade_invocations -import 'package:flame/extensions.dart'; +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'; @@ -56,17 +56,13 @@ void main() { }, ); - flameTester.test( - 'stops animating after animation completes', - (game) async { - final dashAnimatronic = DashAnimatronic(); - await game.ensureAdd(dashAnimatronic); - - dashAnimatronic.playing = true; - game.update(4); - - expect(dashAnimatronic.playing, isFalse); - }, - ); + flameTester.test('adds new children', (game) async { + final component = Component(); + final dashAnimatronic = DashAnimatronic( + children: [component], + ); + await game.ensureAdd(dashAnimatronic); + expect(dashAnimatronic.children, contains(component)); + }); }); } diff --git a/packages/pinball_components/test/src/components/sparky_animatronic_test.dart b/packages/pinball_components/test/src/components/sparky_animatronic_test.dart index 66c2e0a6..175c9596 100644 --- a/packages/pinball_components/test/src/components/sparky_animatronic_test.dart +++ b/packages/pinball_components/test/src/components/sparky_animatronic_test.dart @@ -1,6 +1,6 @@ // ignore_for_file: cascade_invocations -import 'package:flame/extensions.dart'; +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'; @@ -58,19 +58,13 @@ void main() { }, ); - flameTester.test( - 'stops animating after animation completes', - (game) async { - final sparkyAnimatronic = SparkyAnimatronic(); - await game.ensureAdd(sparkyAnimatronic); - - sparkyAnimatronic.playing = true; - final animationDuration = - game.firstChild()!.animation!.totalDuration(); - game.update(animationDuration); - - expect(sparkyAnimatronic.playing, isFalse); - }, - ); + flameTester.test('adds new children', (game) async { + final component = Component(); + final sparkyAnimatronic = SparkyAnimatronic( + children: [component], + ); + await game.ensureAdd(sparkyAnimatronic); + expect(sparkyAnimatronic.children, contains(component)); + }); }); } diff --git a/test/game/behaviors/animatronic_looping_behavior_test.dart b/test/game/behaviors/animatronic_looping_behavior_test.dart new file mode 100644 index 00000000..0aacd508 --- /dev/null +++ b/test/game/behaviors/animatronic_looping_behavior_test.dart @@ -0,0 +1,105 @@ +// ignore_for_file: cascade_invocations + +import 'package:flame/components.dart'; +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/game/behaviors/behaviors.dart'; +import 'package:pinball_components/pinball_components.dart'; + +class _TestGame extends Forge2DGame { + @override + Future onLoad() async { + images.prefix = ''; + await images.load(Assets.images.dash.animatronic.keyName); + } +} + +class _TestSpriteAnimationComponent extends SpriteAnimationComponent {} + +class _MockSpriteAnimation extends Mock implements SpriteAnimation {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final flameTester = FlameTester(_TestGame.new); + + group('AnimatronicLoopingBehavior', () { + test('can be instantiated', () { + expect( + AnimatronicLoopingBehavior(animationCoolDown: 1), + isA(), + ); + }); + + flameTester.test( + 'can be added', + (game) async { + final behavior = AnimatronicLoopingBehavior(animationCoolDown: 1); + final animation = _MockSpriteAnimation(); + final spriteAnimationComponent = _TestSpriteAnimationComponent() + ..animation = animation; + await game.ensureAdd(spriteAnimationComponent); + await spriteAnimationComponent.add(behavior); + await game.ready(); + + expect(game.contains(spriteAnimationComponent), isTrue); + expect(spriteAnimationComponent.contains(behavior), isTrue); + }, + ); + + flameTester.test( + 'onTick starts playing the animation', + (game) async { + final behavior = AnimatronicLoopingBehavior(animationCoolDown: 1); + final spriteAnimationComponent = _TestSpriteAnimationComponent(); + await game.ensureAdd(spriteAnimationComponent); + await spriteAnimationComponent.add(behavior); + + spriteAnimationComponent.playing = false; + game.update(behavior.timer.limit); + + expect(spriteAnimationComponent.playing, isTrue); + }, + ); + + flameTester.test( + 'animation onComplete resets and stops playing the animation', + (game) async { + final behavior = AnimatronicLoopingBehavior(animationCoolDown: 1); + final spriteAnimationComponent = DashAnimatronic(); + + await game.ensureAdd(spriteAnimationComponent); + await spriteAnimationComponent.add(behavior); + + game.update(1); + expect(spriteAnimationComponent.playing, isTrue); + + spriteAnimationComponent.animation!.onComplete!.call(); + + expect(spriteAnimationComponent.playing, isFalse); + expect(spriteAnimationComponent.animation!.currentIndex, equals(0)); + }, + ); + + flameTester.test( + 'animation onComplete resets and starts the timer', + (game) async { + final behavior = AnimatronicLoopingBehavior(animationCoolDown: 1); + final spriteAnimationComponent = DashAnimatronic(); + + await game.ensureAdd(spriteAnimationComponent); + await spriteAnimationComponent.add(behavior); + + game.update(0.5); + expect(behavior.timer.current, equals(0.5)); + + spriteAnimationComponent.animation!.onComplete!.call(); + + expect(behavior.timer.current, equals(0)); + expect(behavior.timer.isRunning(), isTrue); + }, + ); + }); +} diff --git a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart index 7fc1946b..fdef2f39 100644 --- a/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart +++ b/test/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior_test.dart @@ -16,10 +16,7 @@ class _TestGame extends Forge2DGame { @override Future onLoad() async { images.prefix = ''; - await images.loadAll([ - Assets.images.dash.animatronic.keyName, - theme.Assets.images.dash.ball.keyName, - ]); + await images.load(theme.Assets.images.dash.ball.keyName); } Future pump( @@ -67,10 +64,9 @@ void main() { DashBumper.test(bloc: DashBumperCubit()), DashBumper.test(bloc: DashBumperCubit()), ]; - final animatronic = DashAnimatronic(); final signpost = Signpost.test(bloc: SignpostCubit()); await game.pump(parent, gameBloc: gameBloc); - await parent.ensureAddAll([...bumpers, animatronic, signpost]); + await parent.ensureAddAll([...bumpers, signpost]); await parent.ensureAdd(behavior); expect(game.descendants().whereType(), equals(bumpers)); @@ -99,10 +95,9 @@ void main() { DashBumper.test(bloc: DashBumperCubit()), DashBumper.test(bloc: DashBumperCubit()), ]; - final animatronic = DashAnimatronic(); final signpost = Signpost.test(bloc: SignpostCubit()); await game.pump(parent, gameBloc: gameBloc); - await parent.ensureAddAll([...bumpers, animatronic, signpost]); + await parent.ensureAddAll([...bumpers, signpost]); await parent.ensureAdd(behavior); expect(game.descendants().whereType(), equals(bumpers)); @@ -133,10 +128,9 @@ void main() { DashBumper.test(bloc: DashBumperCubit()), DashBumper.test(bloc: DashBumperCubit()), ]; - final animatronic = DashAnimatronic(); final signpost = Signpost.test(bloc: SignpostCubit()); await game.pump(parent, gameBloc: gameBloc); - await parent.ensureAddAll([...bumpers, animatronic, signpost]); + await parent.ensureAddAll([...bumpers, signpost]); await parent.ensureAdd(behavior); expect(game.descendants().whereType(), equals(bumpers)); diff --git a/test/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior_test.dart b/test/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior_test.dart index fbfeef0b..2dfb54cf 100644 --- a/test/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior_test.dart +++ b/test/game/components/sparky_scorch/behaviors/sparky_computer_bonus_behavior_test.dart @@ -17,7 +17,6 @@ class _TestGame extends Forge2DGame { Assets.images.sparky.computer.top.keyName, Assets.images.sparky.computer.base.keyName, Assets.images.sparky.computer.glow.keyName, - Assets.images.sparky.animatronic.keyName, Assets.images.sparky.bumper.a.lit.keyName, Assets.images.sparky.bumper.a.dimmed.keyName, Assets.images.sparky.bumper.b.lit.keyName, @@ -58,18 +57,14 @@ void main() { final flameTester = FlameTester(_TestGame.new); flameTester.testGameWidget( - 'adds GameBonus.sparkyTurboCharge to the game and plays animatronic ' + 'adds GameBonus.sparkyTurboCharge to the game ' 'when SparkyComputerState.withBall is emitted', setUp: (game, tester) async { final behavior = SparkyComputerBonusBehavior(); final parent = SparkyScorch.test(); final sparkyComputer = SparkyComputer(); - final animatronic = SparkyAnimatronic(); - await parent.addAll([ - sparkyComputer, - animatronic, - ]); + await parent.add(sparkyComputer); await game.pump(parent, gameBloc: gameBloc); await parent.ensureAdd(behavior); @@ -79,7 +74,6 @@ void main() { verify( () => gameBloc.add(const BonusActivated(GameBonus.sparkyTurboCharge)), ).called(1); - expect(animatronic.playing, isTrue); }, ); });