diff --git a/lib/game/components/controlled_ball.dart b/lib/game/components/controlled_ball.dart index 67d75daf..c4b10305 100644 --- a/lib/game/components/controlled_ball.dart +++ b/lib/game/components/controlled_ball.dart @@ -66,9 +66,8 @@ class BallController extends ComponentController // given animations. component.stop(); await Future.delayed(const Duration(seconds: 1)); - component - ..resume() - ..boost(Vector2(200, 500)); + component.resume(); + await component.boost(Vector2(40, 110)); } @override diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index e7c7a343..a480e38f 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -7,7 +7,8 @@ extension PinballGameAssetsX on PinballGame { /// Returns a list of assets to be loaded List preLoadAssets() { return [ - images.load(components.Assets.images.ball.keyName), + images.load(components.Assets.images.ball.ball.keyName), + images.load(components.Assets.images.ball.flameEffect.keyName), images.load(components.Assets.images.flutterSignPost.keyName), images.load(components.Assets.images.flipper.left.keyName), images.load(components.Assets.images.flipper.right.keyName), diff --git a/packages/pinball_components/assets/images/ball.png b/packages/pinball_components/assets/images/ball/ball.png similarity index 100% rename from packages/pinball_components/assets/images/ball.png rename to packages/pinball_components/assets/images/ball/ball.png diff --git a/packages/pinball_components/assets/images/ball/flame_effect.png b/packages/pinball_components/assets/images/ball/flame_effect.png new file mode 100644 index 00000000..03a6fca6 Binary files /dev/null and b/packages/pinball_components/assets/images/ball/flame_effect.png differ diff --git a/packages/pinball_components/lib/gen/assets.gen.dart b/packages/pinball_components/lib/gen/assets.gen.dart index f45543b0..34e45739 100644 --- a/packages/pinball_components/lib/gen/assets.gen.dart +++ b/packages/pinball_components/lib/gen/assets.gen.dart @@ -13,10 +13,7 @@ class $AssetsImagesGen { $AssetsImagesAlienBumperGen get alienBumper => const $AssetsImagesAlienBumperGen(); $AssetsImagesBackboardGen get backboard => const $AssetsImagesBackboardGen(); - - /// File path: assets/images/ball.png - AssetGenImage get ball => const AssetGenImage('assets/images/ball.png'); - + $AssetsImagesBallGen get ball => const $AssetsImagesBallGen(); $AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen(); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesChromeDinoGen get chromeDino => @@ -61,6 +58,17 @@ class $AssetsImagesBackboardGen { const AssetGenImage('assets/images/backboard/display.png'); } +class $AssetsImagesBallGen { + const $AssetsImagesBallGen(); + + /// File path: assets/images/ball/ball.png + AssetGenImage get ball => const AssetGenImage('assets/images/ball/ball.png'); + + /// File path: assets/images/ball/flame_effect.png + AssetGenImage get flameEffect => + const AssetGenImage('assets/images/ball/flame_effect.png'); +} + class $AssetsImagesBaseboardGen { const $AssetsImagesBaseboardGen(); @@ -354,8 +362,11 @@ class $AssetsImagesDashBumperMainGen { class $AssetsImagesSparkyBumperAGen { const $AssetsImagesSparkyBumperAGen(); + /// File path: assets/images/sparky/bumper/a/active.png AssetGenImage get active => const AssetGenImage('assets/images/sparky/bumper/a/active.png'); + + /// File path: assets/images/sparky/bumper/a/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/sparky/bumper/a/inactive.png'); } @@ -363,8 +374,11 @@ class $AssetsImagesSparkyBumperAGen { class $AssetsImagesSparkyBumperBGen { const $AssetsImagesSparkyBumperBGen(); + /// File path: assets/images/sparky/bumper/b/active.png AssetGenImage get active => const AssetGenImage('assets/images/sparky/bumper/b/active.png'); + + /// File path: assets/images/sparky/bumper/b/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/sparky/bumper/b/inactive.png'); } @@ -372,8 +386,11 @@ class $AssetsImagesSparkyBumperBGen { class $AssetsImagesSparkyBumperCGen { const $AssetsImagesSparkyBumperCGen(); + /// File path: assets/images/sparky/bumper/c/active.png AssetGenImage get active => const AssetGenImage('assets/images/sparky/bumper/c/active.png'); + + /// File path: assets/images/sparky/bumper/c/inactive.png AssetGenImage get inactive => const AssetGenImage('assets/images/sparky/bumper/c/inactive.png'); } diff --git a/packages/pinball_components/lib/src/components/ball.dart b/packages/pinball_components/lib/src/components/ball.dart index f0fb2e7c..41d90e95 100644 --- a/packages/pinball_components/lib/src/components/ball.dart +++ b/packages/pinball_components/lib/src/components/ball.dart @@ -44,9 +44,6 @@ class Ball extends BodyComponent /// The base [Color] used to tint this [Ball]. final Color baseColor; - double _boostTimer = 0; - static const _boostDuration = 2.0; - final _BallSpriteComponent _spriteComponent = _BallSpriteComponent(); @override @@ -96,32 +93,20 @@ class Ball extends BodyComponent body.gravityScale = Vector2(0, 1); } + /// Applies a boost and [_FlameEffect] on this [Ball]. + Future boost(Vector2 impulse) async { + body.linearVelocity = impulse; + await add(_FlameEffect()); + } + @override void update(double dt) { super.update(dt); - if (_boostTimer > 0) { - _boostTimer -= dt; - final direction = body.linearVelocity.normalized(); - final effect = FireEffect( - burstPower: _boostTimer, - direction: direction, - position: Vector2(body.position.x, body.position.y), - priority: priority - 1, - ); - - unawaited(gameRef.add(effect)); - } _rescaleSize(); _setPositionalGravity(); } - /// Applies a boost on this [Ball]. - void boost(Vector2 impulse) { - body.linearVelocity = impulse; - _boostTimer = _boostDuration; - } - void _rescaleSize() { final boardHeight = BoardDimensions.bounds.height; const maxShrinkValue = BoardDimensions.perspectiveShrinkFactor; @@ -159,10 +144,65 @@ class _BallSpriteComponent extends SpriteComponent with HasGameRef { Future onLoad() async { await super.onLoad(); final sprite = await gameRef.loadSprite( - Assets.images.ball.keyName, + Assets.images.ball.ball.keyName, ); this.sprite = sprite; size = sprite.originalSize / 10; anchor = Anchor.center; } } + +class _FlameEffect extends SpriteAnimationComponent with HasGameRef { + _FlameEffect() + : super( + anchor: const Anchor(0.53, 0.72), + priority: Ball.boardPriority + 1, + ); + + var _duration = 2.0; + late final Vector2 _textureSize; + + @override + Future onLoad() async { + await super.onLoad(); + + final spriteSheet = await gameRef.images.load( + Assets.images.ball.flameEffect.keyName, + ); + + const amountPerRow = 8; + const amountPerColumn = 4; + _textureSize = Vector2( + spriteSheet.width / amountPerRow, + spriteSheet.height / amountPerColumn, + ); + + animation = SpriteAnimation.fromFrameData( + spriteSheet, + SpriteAnimationData.sequenced( + amount: amountPerRow * amountPerColumn, + amountPerRow: amountPerRow, + stepTime: 1 / 24, + textureSize: _textureSize, + ), + ); + } + + @override + void update(double dt) { + super.update(dt); + + if (parent != null) { + final body = (parent! as BodyComponent).body; + final direction = -body.linearVelocity.normalized(); + angle = math.atan2(direction.x, -direction.y); + size = (_textureSize / 45) * body.fixtures.first.shape.radius; + } + + _duration -= dt; + + if (_duration < 0) { + removeFromParent(); + } + } +} diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index d27084f1..e049db2b 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -37,6 +37,7 @@ flutter: assets: - assets/images/ + - assets/images/ball/ - assets/images/baseboard/ - assets/images/boundary/ - assets/images/dino/ diff --git a/packages/pinball_components/test/src/components/ball_test.dart b/packages/pinball_components/test/src/components/ball_test.dart index a0a73e2b..376abebb 100644 --- a/packages/pinball_components/test/src/components/ball_test.dart +++ b/packages/pinball_components/test/src/components/ball_test.dart @@ -1,5 +1,6 @@ // 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/material.dart'; @@ -169,20 +170,40 @@ void main() { expect(ball.body.linearVelocity, equals(Vector2.zero())); - ball.boost(Vector2.all(10)); + await ball.boost(Vector2.all(10)); expect(ball.body.linearVelocity.x, greaterThan(0)); expect(ball.body.linearVelocity.y, greaterThan(0)); }); - flameTester.test('adds fire effect components to the game', (game) async { + flameTester.test('adds FlameEffect', (game) async { final ball = Ball(baseColor: Colors.blue); await game.ensureAdd(ball); - ball.boost(Vector2.all(10)); + await ball.boost(Vector2.all(10)); game.update(0); + + expect( + ball.children.whereType().single, + isNotNull, + ); + }); + + flameTester.test('removes FlameEffect after a duration', (game) async { + final ball = Ball(baseColor: Colors.blue); + await game.ensureAdd(ball); + + await ball.boost(Vector2.all(10)); + game.update(0); + + final flameEffect = + ball.children.whereType().single; + + expect(ball.contains(flameEffect), isTrue); + + game.update(3); await game.ready(); - expect(game.children.whereType().length, greaterThan(0)); + expect(ball.contains(flameEffect), isFalse); }); }); }); diff --git a/test/game/components/controlled_ball_test.dart b/test/game/components/controlled_ball_test.dart index 96c67dd4..e615d508 100644 --- a/test/game/components/controlled_ball_test.dart +++ b/test/game/components/controlled_ball_test.dart @@ -94,6 +94,7 @@ void main() { final controller = WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); + when(() => ball.boost(any())).thenAnswer((_) async {}); await controller.turboCharge(); @@ -109,6 +110,7 @@ void main() { final controller = WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); + when(() => ball.boost(any())).thenAnswer((_) async {}); await controller.turboCharge(); @@ -124,6 +126,7 @@ void main() { final controller = WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); + when(() => ball.boost(any())).thenAnswer((_) async {}); await controller.turboCharge();