diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index 4d4cda7d..7ae5cd45 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -26,6 +26,7 @@ class $AssetsImagesGen { $AssetsImagesKickerGen get kicker => const $AssetsImagesKickerGen(); $AssetsImagesLaunchRampGen get launchRamp => const $AssetsImagesLaunchRampGen(); + $AssetsImagesMultiballGen get multiball => const $AssetsImagesMultiballGen(); $AssetsImagesPlungerGen get plunger => const $AssetsImagesPlungerGen(); $AssetsImagesSignpostGen get signpost => const $AssetsImagesSignpostGen(); $AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen(); @@ -197,6 +198,15 @@ class $AssetsImagesLaunchRampGen { const AssetGenImage('assets/images/launch_ramp/ramp.png'); } +class $AssetsImagesMultiballGen { + const $AssetsImagesMultiballGen(); + + $AssetsImagesMultiballAGen get a => const $AssetsImagesMultiballAGen(); + $AssetsImagesMultiballBGen get b => const $AssetsImagesMultiballBGen(); + $AssetsImagesMultiballCGen get c => const $AssetsImagesMultiballCGen(); + $AssetsImagesMultiballDGen get d => const $AssetsImagesMultiballDGen(); +} + class $AssetsImagesPlungerGen { const $AssetsImagesPlungerGen(); @@ -261,8 +271,10 @@ class $AssetsImagesSpaceshipGen { class $AssetsImagesSparkyGen { const $AssetsImagesSparkyGen(); + /// File path: assets/images/sparky/animatronic.png AssetGenImage get animatronic => const AssetGenImage('assets/images/sparky/animatronic.png'); + $AssetsImagesSparkyBumperGen get bumper => const $AssetsImagesSparkyBumperGen(); $AssetsImagesSparkyComputerGen get computer => @@ -302,6 +314,54 @@ class $AssetsImagesDashBumperGen { const $AssetsImagesDashBumperMainGen(); } +class $AssetsImagesMultiballAGen { + const $AssetsImagesMultiballAGen(); + + /// File path: assets/images/multiball/a/active.png + AssetGenImage get active => + const AssetGenImage('assets/images/multiball/a/active.png'); + + /// File path: assets/images/multiball/a/inactive.png + AssetGenImage get inactive => + const AssetGenImage('assets/images/multiball/a/inactive.png'); +} + +class $AssetsImagesMultiballBGen { + const $AssetsImagesMultiballBGen(); + + /// File path: assets/images/multiball/b/active.png + AssetGenImage get active => + const AssetGenImage('assets/images/multiball/b/active.png'); + + /// File path: assets/images/multiball/b/inactive.png + AssetGenImage get inactive => + const AssetGenImage('assets/images/multiball/b/inactive.png'); +} + +class $AssetsImagesMultiballCGen { + const $AssetsImagesMultiballCGen(); + + /// File path: assets/images/multiball/c/active.png + AssetGenImage get active => + const AssetGenImage('assets/images/multiball/c/active.png'); + + /// File path: assets/images/multiball/c/inactive.png + AssetGenImage get inactive => + const AssetGenImage('assets/images/multiball/c/inactive.png'); +} + +class $AssetsImagesMultiballDGen { + const $AssetsImagesMultiballDGen(); + + /// File path: assets/images/multiball/d/active.png + AssetGenImage get active => + const AssetGenImage('assets/images/multiball/d/active.png'); + + /// File path: assets/images/multiball/d/inactive.png + AssetGenImage get inactive => + const AssetGenImage('assets/images/multiball/d/inactive.png'); +} + class $AssetsImagesSpaceshipRailGen { const $AssetsImagesSpaceshipRailGen(); diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 57e93abb..54319262 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -19,6 +19,7 @@ export 'kicker.dart'; export 'launch_ramp.dart'; export 'layer.dart'; export 'layer_sensor.dart'; +export 'multiball.dart'; export 'plunger.dart'; export 'render_priority.dart'; export 'rocket.dart'; diff --git a/packages/pinball_components/lib/src/components/multiball.dart b/packages/pinball_components/lib/src/components/multiball.dart new file mode 100644 index 00000000..52fe8bd5 --- /dev/null +++ b/packages/pinball_components/lib/src/components/multiball.dart @@ -0,0 +1,115 @@ +import 'package:flame/components.dart'; +import 'package:flutter/material.dart'; +import 'package:pinball_components/gen/assets.gen.dart'; + +/// {@template multiball} +/// A [Component] for the multiball over the board. +/// {@endtemplate} +class Multiball extends Component { + /// {@macro multiball} + Multiball._({ + required Vector2 position, + required String onAssetPath, + required String offAssetPath, + double rotation = 0, + }) : super( + children: [ + MultiballSpriteGroupComponent( + position: position, + onAssetPath: onAssetPath, + offAssetPath: offAssetPath, + )..angle = rotation, + ], + ); + + /// {@macro multiball} + Multiball.a() + : this._( + position: Vector2(0, 0), + onAssetPath: Assets.images.multiball.a.active.keyName, + offAssetPath: Assets.images.multiball.a.inactive.keyName, + rotation: 0, + ); + + /// {@macro multiball} + Multiball.b() + : this._( + position: Vector2(0, 0), + onAssetPath: Assets.images.multiball.b.active.keyName, + offAssetPath: Assets.images.multiball.b.inactive.keyName, + rotation: 0, + ); + + /// {@macro multiball} + Multiball.c() + : this._( + position: Vector2(0, 0), + onAssetPath: Assets.images.multiball.c.active.keyName, + offAssetPath: Assets.images.multiball.c.inactive.keyName, + rotation: 0, + ); + + /// {@macro multiball} + Multiball.d() + : this._( + position: Vector2(0, 0), + onAssetPath: Assets.images.multiball.d.active.keyName, + offAssetPath: Assets.images.multiball.d.inactive.keyName, + rotation: 0, + ); + + /// Animates the [Multiball]. + Future animate() async { + final spriteGroupComponent = firstChild() + ?..current = MultiballSpriteState.active; + await Future.delayed(const Duration(milliseconds: 50)); + spriteGroupComponent?.current = MultiballSpriteState.inactive; + } +} + +/// Indicates the current sprite state of the multiball. +enum MultiballSpriteState { + /// A lit up multiball. + active, + + /// A dimmed multiball. + inactive, +} + +/// {@template multiball_sprite_group_component} +/// A [SpriteGroupComponent] for the multiball over the board. +/// {@endtemplate} +@visibleForTesting +class MultiballSpriteGroupComponent + extends SpriteGroupComponent with HasGameRef { + /// {@macro multiball_sprite_group_component} + MultiballSpriteGroupComponent({ + required Vector2 position, + required String onAssetPath, + required String offAssetPath, + }) : _onAssetPath = onAssetPath, + _offAssetPath = offAssetPath, + super( + anchor: Anchor.center, + position: position, + ); + + final String _onAssetPath; + final String _offAssetPath; + + @override + Future onLoad() async { + await super.onLoad(); + + final sprites = { + MultiballSpriteState.active: + Sprite(gameRef.images.fromCache(_onAssetPath)), + MultiballSpriteState.inactive: + Sprite(gameRef.images.fromCache(_offAssetPath)), + }; + this.sprites = sprites; + + current = MultiballSpriteState.inactive; + size = sprites[current]!.originalSize / 10; + } +} diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index a7cb8367..f6fcdc3c 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -69,6 +69,10 @@ flutter: - assets/images/backboard/ - assets/images/google_word/ - assets/images/signpost/ + - assets/images/multiball/a/ + - assets/images/multiball/b/ + - assets/images/multiball/c/ + - assets/images/multiball/d/ flutter_gen: line_length: 80 diff --git a/packages/pinball_components/test/src/components/multiball_test.dart b/packages/pinball_components/test/src/components/multiball_test.dart new file mode 100644 index 00000000..7f47c9a2 --- /dev/null +++ b/packages/pinball_components/test/src/components/multiball_test.dart @@ -0,0 +1,79 @@ +// ignore_for_file: cascade_invocations + +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 '../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.multiball.a.active.keyName, + Assets.images.multiball.a.inactive.keyName, + Assets.images.multiball.b.active.keyName, + Assets.images.multiball.b.inactive.keyName, + Assets.images.multiball.c.active.keyName, + Assets.images.multiball.c.inactive.keyName, + Assets.images.multiball.d.active.keyName, + Assets.images.multiball.d.inactive.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + group('Multiball', () { + flameTester.test('"a" loads correctly', (game) async { + final multiball = Multiball.a(); + await game.ensureAdd(multiball); + + expect(game.contains(multiball), isTrue); + }); + + flameTester.test('"b" loads correctly', (game) async { + final multiball = Multiball.b(); + await game.ensureAdd(multiball); + expect(game.contains(multiball), isTrue); + }); + + flameTester.test('"c" loads correctly', (game) async { + final multiball = Multiball.c(); + await game.ensureAdd(multiball); + + expect(game.contains(multiball), isTrue); + }); + + flameTester.test('"d" loads correctly', (game) async { + final multiball = Multiball.d(); + await game.ensureAdd(multiball); + expect(game.contains(multiball), isTrue); + }); + + flameTester.test('animate switches between on and off sprites', + (game) async { + final multiball = Multiball.a(); + await game.ensureAdd(multiball); + + final spriteGroupComponent = + multiball.firstChild()!; + + expect( + spriteGroupComponent.current, + equals(MultiballSpriteState.inactive), + ); + + final future = multiball.animate(); + + expect( + spriteGroupComponent.current, + equals(MultiballSpriteState.active), + ); + + await future; + + expect( + spriteGroupComponent.current, + equals(MultiballSpriteState.inactive), + ); + }); + }); +}