diff --git a/lib/game/behaviors/behaviors.dart b/lib/game/behaviors/behaviors.dart new file mode 100644 index 00000000..ae51fc09 --- /dev/null +++ b/lib/game/behaviors/behaviors.dart @@ -0,0 +1,2 @@ +export 'bumper_noisy_behavior.dart'; +export 'scoring_behavior.dart'; diff --git a/lib/game/behaviors/bumper_noisy_behavior.dart b/lib/game/behaviors/bumper_noisy_behavior.dart new file mode 100644 index 00000000..c837c8c5 --- /dev/null +++ b/lib/game/behaviors/bumper_noisy_behavior.dart @@ -0,0 +1,14 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball/game/pinball_game.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class BumperNoisyBehavior extends ContactBehavior with HasGameRef { + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + gameRef.audio.bumper(); + } +} diff --git a/lib/game/behaviors/scoring_behavior.dart b/lib/game/behaviors/scoring_behavior.dart new file mode 100644 index 00000000..84597838 --- /dev/null +++ b/lib/game/behaviors/scoring_behavior.dart @@ -0,0 +1,77 @@ +// ignore_for_file: avoid_renaming_method_parameters + +import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template scoring_behavior} +/// Adds [_points] to the score and shows a text effect. +/// +/// The behavior removes itself after the duration. +/// {@endtemplate} +class ScoringBehavior extends Component with HasGameRef { + /// {@macto scoring_behavior} + ScoringBehavior({ + required Points points, + required Vector2 position, + double duration = 1, + }) : _points = points, + _position = position, + _effectController = EffectController( + duration: duration, + ); + + final Points _points; + final Vector2 _position; + + final EffectController _effectController; + + @override + void update(double dt) { + super.update(dt); + if (_effectController.completed) { + removeFromParent(); + } + } + + @override + Future onLoad() async { + gameRef.read().add(Scored(points: _points.value)); + await gameRef.firstChild()!.add( + ScoreComponent( + points: _points, + position: _position, + effectController: _effectController, + ), + ); + } +} + +/// {@template scoring_contact_behavior} +/// Adds points to the score when the [Ball] contacts the [parent]. +/// {@endtemplate} +class ScoringContactBehavior extends ContactBehavior + with HasGameRef { + /// {@macro scoring_contact_behavior} + ScoringContactBehavior({ + required Points points, + }) : _points = points; + + final Points _points; + + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + if (other is! Ball) return; + + parent.add( + ScoringBehavior( + points: _points, + position: other.body.position, + ), + ); + } +} diff --git a/lib/game/components/android_acres/android_acres.dart b/lib/game/components/android_acres/android_acres.dart index d29f38d7..82b71741 100644 --- a/lib/game/components/android_acres/android_acres.dart +++ b/lib/game/components/android_acres/android_acres.dart @@ -2,8 +2,8 @@ import 'package:flame/components.dart'; import 'package:flutter/material.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template android_acres} @@ -20,22 +20,25 @@ class AndroidAcres extends Component { AndroidSpaceship(position: Vector2(-26.5, -28.5)), AndroidAnimatronic( children: [ - ScoringBehavior(points: Points.twoHundredThousand), + ScoringContactBehavior(points: Points.twoHundredThousand), ], )..initialPosition = Vector2(-26, -28.25), AndroidBumper.a( children: [ - BumperScoringBehavior(points: Points.twentyThousand), + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(-25, 1.3), AndroidBumper.b( children: [ - BumperScoringBehavior(points: Points.twentyThousand), + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(-32.8, -9.2), AndroidBumper.cow( children: [ - BumperScoringBehavior(points: Points.twentyThousand), + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(-20.5, -13.8), AndroidSpaceshipBonusBehavior(), diff --git a/lib/game/components/bottom_group.dart b/lib/game/components/bottom_group.dart index c13f21be..d7856e48 100644 --- a/lib/game/components/bottom_group.dart +++ b/lib/game/components/bottom_group.dart @@ -1,4 +1,5 @@ import 'package:flame/components.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -51,7 +52,8 @@ class _BottomGroupSide extends Component { final kicker = Kicker( side: _side, children: [ - ScoringBehavior(points: Points.fiveThousand)..applyTo(['bouncy_edge']), + ScoringContactBehavior(points: Points.fiveThousand) + ..applyTo(['bouncy_edge']), ], )..initialPosition = Vector2( (22.64 * direction) + centerXAdjustment, diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index d3a1bb71..2b132656 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -13,5 +13,4 @@ export 'google_word/google_word.dart'; export 'launcher.dart'; export 'multiballs/multiballs.dart'; export 'multipliers/multipliers.dart'; -export 'scoring_behavior.dart'; export 'sparky_scorch.dart'; diff --git a/lib/game/components/controlled_ball.dart b/lib/game/components/controlled_ball.dart index 4103bb81..9dc81135 100644 --- a/lib/game/components/controlled_ball.dart +++ b/lib/game/components/controlled_ball.dart @@ -67,7 +67,9 @@ class BallController extends ComponentController const Duration(milliseconds: 2583), ); component.resume(); - await component.boost(Vector2(40, 110)); + await component.add( + BallTurboChargingBehavior(impulse: Vector2(40, 110)), + ); } @override diff --git a/lib/game/components/dino_desert/dino_desert.dart b/lib/game/components/dino_desert/dino_desert.dart index 4d8cd7b6..9ba9c71b 100644 --- a/lib/game/components/dino_desert/dino_desert.dart +++ b/lib/game/components/dino_desert/dino_desert.dart @@ -1,6 +1,7 @@ import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/components/dino_desert/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -16,7 +17,7 @@ class DinoDesert extends Component { children: [ ChromeDino( children: [ - ScoringBehavior(points: Points.twoHundredThousand) + ScoringContactBehavior(points: Points.twoHundredThousand) ..applyTo(['inside_mouth']), ], )..initialPosition = Vector2(12.6, -6.9), diff --git a/lib/game/components/flutter_forest/flutter_forest.dart b/lib/game/components/flutter_forest/flutter_forest.dart index 0f24a3b6..259b6bb2 100644 --- a/lib/game/components/flutter_forest/flutter_forest.dart +++ b/lib/game/components/flutter_forest/flutter_forest.dart @@ -2,8 +2,8 @@ import 'package:flame/components.dart'; import 'package:flutter/material.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/components/flutter_forest/behaviors/behaviors.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -18,22 +18,26 @@ class FlutterForest extends Component with ZIndex { children: [ Signpost( children: [ - BumperScoringBehavior(points: Points.fiveThousand), + ScoringContactBehavior(points: Points.fiveThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(8.35, -58.3), DashNestBumper.main( children: [ - BumperScoringBehavior(points: Points.twoHundredThousand), + ScoringContactBehavior(points: Points.twoHundredThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(18.55, -59.35), DashNestBumper.a( children: [ - BumperScoringBehavior(points: Points.twentyThousand), + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(8.95, -51.95), DashNestBumper.b( children: [ - BumperScoringBehavior(points: Points.twentyThousand), + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(22.3, -46.75), DashAnimatronic()..position = Vector2(20, -66), diff --git a/lib/game/components/google_word/google_word.dart b/lib/game/components/google_word/google_word.dart index af1faea9..76bac244 100644 --- a/lib/game/components/google_word/google_word.dart +++ b/lib/game/components/google_word/google_word.dart @@ -1,7 +1,7 @@ import 'package:flame/components.dart'; import 'package:flutter/material.dart'; +import 'package:pinball/game/behaviors/scoring_behavior.dart'; import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; -import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -16,27 +16,27 @@ class GoogleWord extends Component with ZIndex { children: [ GoogleLetter( 0, - children: [ScoringBehavior(points: Points.fiveThousand)], + children: [ScoringContactBehavior(points: Points.fiveThousand)], )..initialPosition = position + Vector2(-13.1, 1.72), GoogleLetter( 1, - children: [ScoringBehavior(points: Points.fiveThousand)], + children: [ScoringContactBehavior(points: Points.fiveThousand)], )..initialPosition = position + Vector2(-8.33, -0.75), GoogleLetter( 2, - children: [ScoringBehavior(points: Points.fiveThousand)], + children: [ScoringContactBehavior(points: Points.fiveThousand)], )..initialPosition = position + Vector2(-2.88, -1.85), GoogleLetter( 3, - children: [ScoringBehavior(points: Points.fiveThousand)], + children: [ScoringContactBehavior(points: Points.fiveThousand)], )..initialPosition = position + Vector2(2.88, -1.85), GoogleLetter( 4, - children: [ScoringBehavior(points: Points.fiveThousand)], + children: [ScoringContactBehavior(points: Points.fiveThousand)], )..initialPosition = position + Vector2(8.33, -0.75), GoogleLetter( 5, - children: [ScoringBehavior(points: Points.fiveThousand)], + children: [ScoringContactBehavior(points: Points.fiveThousand)], )..initialPosition = position + Vector2(13.1, 1.72), GoogleWordBonusBehavior(), ], diff --git a/lib/game/components/scoring_behavior.dart b/lib/game/components/scoring_behavior.dart deleted file mode 100644 index f741e213..00000000 --- a/lib/game/components/scoring_behavior.dart +++ /dev/null @@ -1,53 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'package:flame/components.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -/// {@template scoring_behavior} -/// Adds points to the score when the ball contacts the [parent]. -/// {@endtemplate} -class ScoringBehavior extends ContactBehavior with HasGameRef { - /// {@macro scoring_behavior} - ScoringBehavior({ - required Points points, - }) : _points = points; - - final Points _points; - - @override - void beginContact(Object other, Contact contact) { - super.beginContact(other, contact); - if (other is! Ball) return; - - gameRef.read().add(Scored(points: _points.value)); - gameRef.firstChild()!.add( - ScoreComponent( - points: _points, - position: other.body.position, - ), - ); - } -} - -/// {@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(); - } -} diff --git a/lib/game/components/sparky_scorch.dart b/lib/game/components/sparky_scorch.dart index f98f71d7..5a266b4e 100644 --- a/lib/game/components/sparky_scorch.dart +++ b/lib/game/components/sparky_scorch.dart @@ -2,7 +2,8 @@ import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:pinball/game/game.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; +import 'package:pinball/game/components/components.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template sparky_scorch} @@ -16,17 +17,20 @@ class SparkyScorch extends Component { children: [ SparkyBumper.a( children: [ - BumperScoringBehavior(points: Points.twentyThousand), + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(-22.9, -41.65), SparkyBumper.b( children: [ - BumperScoringBehavior(points: Points.twentyThousand), + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(-21.25, -57.9), SparkyBumper.c( children: [ - BumperScoringBehavior(points: Points.twentyThousand), + ScoringContactBehavior(points: Points.twentyThousand), + BumperNoisyBehavior(), ], )..initialPosition = Vector2(-3.3, -52.55), SparkyComputerSensor()..initialPosition = Vector2(-13, -49.9), @@ -47,7 +51,7 @@ class SparkyComputerSensor extends BodyComponent : super( renderBody: false, children: [ - ScoringBehavior(points: Points.twentyThousand), + ScoringContactBehavior(points: Points.twentyThousand), ], ); diff --git a/packages/pinball_components/assets/images/android/bumper/cow/dimmed.png b/packages/pinball_components/assets/images/android/bumper/cow/dimmed.png index 6a8bb146..4fc09ce6 100644 Binary files a/packages/pinball_components/assets/images/android/bumper/cow/dimmed.png and b/packages/pinball_components/assets/images/android/bumper/cow/dimmed.png differ diff --git a/packages/pinball_components/assets/images/android/bumper/cow/lit.png b/packages/pinball_components/assets/images/android/bumper/cow/lit.png index 4909708b..50223a50 100644 Binary files a/packages/pinball_components/assets/images/android/bumper/cow/lit.png and b/packages/pinball_components/assets/images/android/bumper/cow/lit.png differ diff --git a/packages/pinball_components/assets/images/plunger/rocket.png b/packages/pinball_components/assets/images/plunger/rocket.png index bef65ea1..a8f89152 100644 Binary files a/packages/pinball_components/assets/images/plunger/rocket.png and b/packages/pinball_components/assets/images/plunger/rocket.png differ diff --git a/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart b/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart index 7ddabee8..edce2a78 100644 --- a/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart +++ b/packages/pinball_components/lib/src/components/android_bumper/android_bumper.dart @@ -78,11 +78,11 @@ class AndroidBumper extends BodyComponent with InitialPosition, ZIndex { AndroidBumper.cow({ Iterable? children, }) : this._( - majorRadius: 3.4, - minorRadius: 2.9, + majorRadius: 3.45, + minorRadius: 3.11, litAssetPath: Assets.images.android.bumper.cow.lit.keyName, dimmedAssetPath: Assets.images.android.bumper.cow.dimmed.keyName, - spritePosition: Vector2(0, -0.68), + spritePosition: Vector2(0, -0.35), bloc: AndroidBumperCubit(), children: [ ...?children, diff --git a/packages/pinball_components/lib/src/components/ball/ball.dart b/packages/pinball_components/lib/src/components/ball/ball.dart index 12b1c877..9234e69c 100644 --- a/packages/pinball_components/lib/src/components/ball/ball.dart +++ b/packages/pinball_components/lib/src/components/ball/ball.dart @@ -1,14 +1,13 @@ import 'dart:async'; -import 'dart:math' as math; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/widgets.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_components/src/components/ball/behaviors/ball_gravitating_behavior.dart'; -import 'package:pinball_components/src/components/ball/behaviors/ball_scaling_behavior.dart'; import 'package:pinball_flame/pinball_flame.dart'; +export 'behaviors/behaviors.dart'; + /// {@template ball} /// A solid, [BodyType.dynamic] sphere that rolls and bounces around. /// {@endtemplate} @@ -50,17 +49,13 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { @override Body createBody() { final shape = CircleShape()..radius = size.x / 2; - final fixtureDef = FixtureDef( - shape, - density: 1, - ); final bodyDef = BodyDef( position: initialPosition, - userData: this, type: BodyType.dynamic, + userData: this, ); - return world.createBody(bodyDef)..createFixture(fixtureDef); + return world.createBody(bodyDef)..createFixtureFromShape(shape, 1); } /// Immediatly and completly [stop]s the ball. @@ -81,12 +76,6 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex { void resume() { body.gravityScale = Vector2(1, 1); } - - /// Applies a boost and [_TurboChargeSpriteAnimationComponent] on this [Ball]. - Future boost(Vector2 impulse) async { - body.linearVelocity = impulse; - await add(_TurboChargeSpriteAnimationComponent()); - } } class _BallSpriteComponent extends SpriteComponent with HasGameRef { @@ -101,55 +90,3 @@ class _BallSpriteComponent extends SpriteComponent with HasGameRef { anchor = Anchor.center; } } - -class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent - with HasGameRef, ZIndex { - _TurboChargeSpriteAnimationComponent() - : super( - anchor: const Anchor(0.53, 0.72), - removeOnFinish: true, - ) { - zIndex = ZIndexes.turboChargeFlame; - } - - 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, - loop: false, - ), - ); - } - - @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; - } - } -} diff --git a/packages/pinball_components/lib/src/components/ball/behaviors/ball_turbo_charging_behavior.dart b/packages/pinball_components/lib/src/components/ball/behaviors/ball_turbo_charging_behavior.dart new file mode 100644 index 00000000..f1e5a855 --- /dev/null +++ b/packages/pinball_components/lib/src/components/ball/behaviors/ball_turbo_charging_behavior.dart @@ -0,0 +1,81 @@ +import 'dart:math' as math; + +import 'package:flame/components.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template ball_turbo_charging_behavior} +/// Puts the [Ball] in flames and [_impulse]s it. +/// {@endtemplate} +class BallTurboChargingBehavior extends TimerComponent with ParentIsA { + /// {@macro ball_turbo_charging_behavior} + BallTurboChargingBehavior({ + required Vector2 impulse, + }) : _impulse = impulse, + super(period: 5, removeOnFinish: true); + + final Vector2 _impulse; + + @override + Future onLoad() async { + await super.onLoad(); + + parent.body.linearVelocity = _impulse; + await parent.add(_TurboChargeSpriteAnimationComponent()); + } + + @override + void onRemove() { + parent + .firstChild<_TurboChargeSpriteAnimationComponent>()! + .removeFromParent(); + super.onRemove(); + } +} + +class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent + with HasGameRef, ZIndex, ParentIsA { + _TurboChargeSpriteAnimationComponent() + : super( + anchor: const Anchor(0.53, 0.72), + ) { + zIndex = ZIndexes.turboChargeFlame; + } + + late final Vector2 _textureSize; + + @override + void update(double dt) { + super.update(dt); + + final direction = -parent.body.linearVelocity.normalized(); + angle = math.atan2(direction.x, -direction.y); + size = (_textureSize / 45) * parent.body.fixtures.first.shape.radius; + } + + @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, + ), + ); + } +} diff --git a/packages/pinball_components/lib/src/components/ball/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/ball/behaviors/behaviors.dart index 038b7833..1068a20e 100644 --- a/packages/pinball_components/lib/src/components/ball/behaviors/behaviors.dart +++ b/packages/pinball_components/lib/src/components/ball/behaviors/behaviors.dart @@ -1,2 +1,3 @@ export 'ball_gravitating_behavior.dart'; export 'ball_scaling_behavior.dart'; +export 'ball_turbo_charging_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/score_component.dart b/packages/pinball_components/lib/src/components/score_component.dart index 12d198cb..5f95878a 100644 --- a/packages/pinball_components/lib/src/components/score_component.dart +++ b/packages/pinball_components/lib/src/components/score_component.dart @@ -23,16 +23,20 @@ class ScoreComponent extends SpriteComponent with HasGameRef, ZIndex { ScoreComponent({ required this.points, required Vector2 position, - }) : super( + required EffectController effectController, + }) : _effectController = effectController, + super( position: position, anchor: Anchor.center, ) { zIndex = ZIndexes.score; } + late Points points; + late final Effect _effect; - late Points points; + final EffectController _effectController; @override Future onLoad() async { @@ -46,7 +50,7 @@ class ScoreComponent extends SpriteComponent with HasGameRef, ZIndex { await add( _effect = MoveEffect.by( Vector2(0, -5), - EffectController(duration: 1), + _effectController, ), ); } diff --git a/packages/pinball_components/sandbox/lib/stories/ball/ball_booster_game.dart b/packages/pinball_components/sandbox/lib/stories/ball/ball_booster_game.dart index 7f07de97..a66459a6 100644 --- a/packages/pinball_components/sandbox/lib/stories/ball/ball_booster_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/ball/ball_booster_game.dart @@ -13,8 +13,9 @@ class BallBoosterGame extends LineGame { @override void onLine(Vector2 line) { final ball = Ball(baseColor: Colors.transparent); - add(ball); + final impulse = line * -1 * 20; + ball.add(BallTurboChargingBehavior(impulse: impulse)); - ball.mounted.then((value) => ball.boost(line * -1 * 20)); + add(ball); } } diff --git a/packages/pinball_components/sandbox/lib/stories/score/score_game.dart b/packages/pinball_components/sandbox/lib/stories/score/score_game.dart index 4bde5018..edb4fa36 100644 --- a/packages/pinball_components/sandbox/lib/stories/score/score_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/score/score_game.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:flame/effects.dart'; import 'package:flame/input.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/common/common.dart'; @@ -38,6 +39,7 @@ class ScoreGame extends AssetsGame with TapDetector { ScoreComponent( points: score, position: info.eventPosition.game..multiply(Vector2(1, -1)), + effectController: EffectController(duration: 1), ), ); } diff --git a/packages/pinball_components/test/src/components/ball/ball_test.dart b/packages/pinball_components/test/src/components/ball/ball_test.dart index 02175f16..655836a0 100644 --- a/packages/pinball_components/test/src/components/ball/ball_test.dart +++ b/packages/pinball_components/test/src/components/ball/ball_test.dart @@ -1,12 +1,10 @@ // 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'; import 'package:flutter_test/flutter_test.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_components/src/components/ball/behaviors/behaviors.dart'; import '../../../helpers/helpers.dart'; @@ -180,50 +178,5 @@ void main() { ); }); }); - - group('boost', () { - flameTester.test('applies an impulse to the ball', (game) async { - final ball = Ball(baseColor: baseColor); - await game.ensureAdd(ball); - - expect(ball.body.linearVelocity, equals(Vector2.zero())); - - await ball.boost(Vector2.all(10)); - expect(ball.body.linearVelocity.x, greaterThan(0)); - expect(ball.body.linearVelocity.y, greaterThan(0)); - }); - - flameTester.test('adds TurboChargeSpriteAnimation', (game) async { - final ball = Ball(baseColor: baseColor); - await game.ensureAdd(ball); - - await ball.boost(Vector2.all(10)); - game.update(0); - - expect( - ball.children.whereType().single, - isNotNull, - ); - }); - - flameTester.test('removes TurboChargeSpriteAnimation after it finishes', - (game) async { - final ball = Ball(baseColor: baseColor); - await game.ensureAdd(ball); - - await ball.boost(Vector2.all(10)); - game.update(0); - - final turboChargeSpriteAnimation = - ball.children.whereType().single; - - expect(ball.contains(turboChargeSpriteAnimation), isTrue); - - game.update(turboChargeSpriteAnimation.animation!.totalDuration()); - game.update(0.1); - - expect(ball.contains(turboChargeSpriteAnimation), isFalse); - }); - }); }); } diff --git a/packages/pinball_components/test/src/components/ball/behaviors/ball_gravitating_behavior_test.dart b/packages/pinball_components/test/src/components/ball/behaviors/ball_gravitating_behavior_test.dart index de291f21..d78df37a 100644 --- a/packages/pinball_components/test/src/components/ball/behaviors/ball_gravitating_behavior_test.dart +++ b/packages/pinball_components/test/src/components/ball/behaviors/ball_gravitating_behavior_test.dart @@ -6,7 +6,6 @@ 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 'package:pinball_components/src/components/ball/behaviors/behaviors.dart'; import '../../../../helpers/helpers.dart'; diff --git a/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart b/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart index cd0a0486..0aeeda98 100644 --- a/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart +++ b/packages/pinball_components/test/src/components/ball/behaviors/ball_scaling_behavior_test.dart @@ -6,7 +6,6 @@ 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 'package:pinball_components/src/components/ball/behaviors/behaviors.dart'; import '../../../../helpers/helpers.dart'; diff --git a/packages/pinball_components/test/src/components/ball/behaviors/ball_turbo_charging_behavior_test.dart b/packages/pinball_components/test/src/components/ball/behaviors/ball_turbo_charging_behavior_test.dart new file mode 100644 index 00000000..00f34832 --- /dev/null +++ b/packages/pinball_components/test/src/components/ball/behaviors/ball_turbo_charging_behavior_test.dart @@ -0,0 +1,94 @@ +// ignore_for_file: cascade_invocations + +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_components/pinball_components.dart'; + +import '../../../../helpers/helpers.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group( + 'BallTurboChargingBehavior', + () { + final assets = [Assets.images.ball.ball.keyName]; + final flameTester = FlameTester(() => TestGame(assets)); + const baseColor = Color(0xFFFFFFFF); + + test('can be instantiated', () { + expect( + BallTurboChargingBehavior(impulse: Vector2.zero()), + isA(), + ); + }); + + flameTester.test('can be loaded', (game) async { + final ball = Ball.test(baseColor: baseColor); + final behavior = BallTurboChargingBehavior(impulse: Vector2.zero()); + await ball.add(behavior); + await game.ensureAdd(ball); + expect( + ball.firstChild(), + equals(behavior), + ); + }); + + flameTester.test( + 'impulses the ball velocity when loaded', + (game) async { + final ball = Ball.test(baseColor: baseColor); + await game.ensureAdd(ball); + final impulse = Vector2.all(1); + final behavior = BallTurboChargingBehavior(impulse: impulse); + await ball.ensureAdd(behavior); + + expect( + ball.body.linearVelocity.x, + equals(impulse.x), + ); + expect( + ball.body.linearVelocity.y, + equals(impulse.y), + ); + }, + ); + + flameTester.test('adds sprite', (game) async { + final ball = Ball(baseColor: baseColor); + await game.ensureAdd(ball); + + await ball.ensureAdd( + BallTurboChargingBehavior(impulse: Vector2.zero()), + ); + + expect( + ball.children.whereType().single, + isNotNull, + ); + }); + + flameTester.test('removes sprite after it finishes', (game) async { + final ball = Ball(baseColor: baseColor); + await game.ensureAdd(ball); + + final behavior = BallTurboChargingBehavior(impulse: Vector2.zero()); + await ball.ensureAdd(behavior); + + final turboChargeSpriteAnimation = + ball.children.whereType().single; + + expect(ball.contains(turboChargeSpriteAnimation), isTrue); + + game.update(behavior.timer.limit); + game.update(0.1); + + expect(ball.contains(turboChargeSpriteAnimation), isFalse); + }); + }, + ); +} diff --git a/packages/pinball_components/test/src/components/golden/rocket.png b/packages/pinball_components/test/src/components/golden/rocket.png index 62ba4e61..f9dc36f8 100644 Binary files a/packages/pinball_components/test/src/components/golden/rocket.png and b/packages/pinball_components/test/src/components/golden/rocket.png differ diff --git a/packages/pinball_components/test/src/components/score_component_test.dart b/packages/pinball_components/test/src/components/score_component_test.dart index 69688874..f2bd52e3 100644 --- a/packages/pinball_components/test/src/components/score_component_test.dart +++ b/packages/pinball_components/test/src/components/score_component_test.dart @@ -28,6 +28,7 @@ void main() { ScoreComponent( points: Points.oneMillion, position: Vector2.zero(), + effectController: EffectController(duration: 1), ), ); }, @@ -46,6 +47,7 @@ void main() { ScoreComponent( points: Points.oneMillion, position: Vector2.zero(), + effectController: EffectController(duration: 1), ), ); @@ -67,6 +69,7 @@ void main() { ScoreComponent( points: Points.oneMillion, position: Vector2.zero(), + effectController: EffectController(duration: 1), ), ); @@ -88,6 +91,7 @@ void main() { ScoreComponent( points: Points.fiveThousand, position: Vector2.zero(), + effectController: EffectController(duration: 1), ), ); @@ -113,6 +117,7 @@ void main() { ScoreComponent( points: Points.twentyThousand, position: Vector2.zero(), + effectController: EffectController(duration: 1), ), ); @@ -138,6 +143,7 @@ void main() { ScoreComponent( points: Points.twoHundredThousand, position: Vector2.zero(), + effectController: EffectController(duration: 1), ), ); @@ -163,6 +169,7 @@ void main() { ScoreComponent( points: Points.oneMillion, position: Vector2.zero(), + effectController: EffectController(duration: 1), ), ); diff --git a/test/game/behaviors/bumper_noisy_behavior_test.dart b/test/game/behaviors/bumper_noisy_behavior_test.dart new file mode 100644 index 00000000..b288e4c6 --- /dev/null +++ b/test/game/behaviors/bumper_noisy_behavior_test.dart @@ -0,0 +1,50 @@ +// 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/game/behaviors/behaviors.dart'; +import 'package:pinball_audio/pinball_audio.dart'; + +import '../../helpers/helpers.dart'; + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() { + return world.createBody(BodyDef()); + } +} + +class _MockPinballAudio extends Mock implements PinballAudio {} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('BumperNoisyBehavior', () {}); + + late PinballAudio audio; + final flameTester = FlameTester( + () => EmptyPinballTestGame(audio: audio), + ); + + setUp(() { + audio = _MockPinballAudio(); + }); + + flameTester.testGameWidget( + 'plays bumper sound', + setUp: (game, _) async { + final behavior = BumperNoisyBehavior(); + final parent = _TestBodyComponent(); + await game.ensureAdd(parent); + await parent.ensureAdd(behavior); + behavior.beginContact(Object(), _MockContact()); + }, + verify: (_, __) async { + verify(audio.bumper).called(1); + }, + ); +} diff --git a/test/game/behaviors/scoring_behavior_test.dart b/test/game/behaviors/scoring_behavior_test.dart new file mode 100644 index 00000000..3e710641 --- /dev/null +++ b/test/game/behaviors/scoring_behavior_test.dart @@ -0,0 +1,210 @@ +// ignore_for_file: cascade_invocations + +import 'package:bloc_test/bloc_test.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/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +import '../../helpers/helpers.dart'; + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() => world.createBody(BodyDef()); +} + +class _MockBall extends Mock implements Ball {} + +class _MockBody extends Mock implements Body {} + +class _MockGameBloc extends Mock implements GameBloc {} + +class _MockContact extends Mock implements Contact {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + final assets = [ + Assets.images.score.fiveThousand.keyName, + Assets.images.score.twentyThousand.keyName, + Assets.images.score.twoHundredThousand.keyName, + Assets.images.score.oneMillion.keyName, + ]; + + late GameBloc bloc; + late Ball ball; + late BodyComponent parent; + + setUp(() { + ball = _MockBall(); + final ballBody = _MockBody(); + when(() => ball.body).thenReturn(ballBody); + when(() => ballBody.position).thenReturn(Vector2.all(4)); + + parent = _TestBodyComponent(); + }); + + final flameBlocTester = FlameBlocTester( + gameBuilder: EmptyPinballTestGame.new, + blocBuilder: () { + bloc = _MockGameBloc(); + const state = GameState( + score: 0, + multiplier: 1, + rounds: 3, + bonusHistory: [], + ); + whenListen(bloc, Stream.value(state), initialState: state); + return bloc; + }, + assets: assets, + ); + + group('ScoringBehavior', () { + test('can be instantiated', () { + expect( + ScoringBehavior( + points: Points.fiveThousand, + position: Vector2.zero(), + ), + isA(), + ); + }); + + flameBlocTester.testGameWidget( + 'can be loaded', + setUp: (game, tester) async { + final canvas = ZCanvasComponent(children: [parent]); + final behavior = ScoringBehavior( + points: Points.fiveThousand, + position: Vector2.zero(), + ); + await parent.add(behavior); + await game.ensureAdd(canvas); + + expect( + parent.firstChild(), + equals(behavior), + ); + }, + ); + + flameBlocTester.testGameWidget( + 'emits Scored event with points when added', + setUp: (game, tester) async { + const points = Points.oneMillion; + final canvas = ZCanvasComponent(children: [parent]); + await game.ensureAdd(canvas); + + final behavior = ScoringBehavior( + points: points, + position: Vector2(0, 0), + ); + await parent.ensureAdd(behavior); + + verify( + () => bloc.add( + Scored(points: points.value), + ), + ).called(1); + }, + ); + + flameBlocTester.testGameWidget( + 'correctly renders text', + setUp: (game, tester) async { + final canvas = ZCanvasComponent(children: [parent]); + await game.ensureAdd(canvas); + + const points = Points.oneMillion; + final position = Vector2.all(1); + final behavior = ScoringBehavior( + points: points, + position: position, + ); + await parent.ensureAdd(behavior); + + final scoreText = game.descendants().whereType(); + expect(scoreText.length, equals(1)); + expect( + scoreText.first.points, + equals(points), + ); + expect( + scoreText.first.position, + equals(position), + ); + }, + ); + + flameBlocTester.testGameWidget( + 'is removed after duration', + setUp: (game, tester) async { + final canvas = ZCanvasComponent(children: [parent]); + await game.ensureAdd(canvas); + + const duration = 2.0; + final behavior = ScoringBehavior( + points: Points.oneMillion, + position: Vector2(0, 0), + duration: duration, + ); + await parent.ensureAdd(behavior); + + game.update(duration); + game.update(0); + await tester.pump(); + }, + verify: (game, _) async { + expect( + game.descendants().whereType(), + isEmpty, + ); + }, + ); + }); + + group('ScoringContactBehavior', () { + flameBlocTester.testGameWidget( + 'beginContact adds a ScoringBehavior', + setUp: (game, tester) async { + final canvas = ZCanvasComponent(children: [parent]); + await game.ensureAdd(canvas); + + final behavior = ScoringContactBehavior(points: Points.oneMillion); + await parent.ensureAdd(behavior); + + behavior.beginContact(ball, _MockContact()); + await game.ready(); + + expect( + parent.firstChild(), + isNotNull, + ); + }, + ); + + flameBlocTester.testGameWidget( + "beginContact positions text at contact's position", + setUp: (game, tester) async { + final canvas = ZCanvasComponent(children: [parent]); + await game.ensureAdd(canvas); + + final behavior = ScoringContactBehavior(points: Points.oneMillion); + await parent.ensureAdd(behavior); + + behavior.beginContact(ball, _MockContact()); + await game.ready(); + + final scoreText = game.descendants().whereType(); + expect( + scoreText.first.position, + equals(ball.body.position), + ); + }, + ); + }); +} diff --git a/test/game/components/android_acres/android_acres_test.dart b/test/game/components/android_acres/android_acres_test.dart index 73025551..8434d5f8 100644 --- a/test/game/components/android_acres/android_acres_test.dart +++ b/test/game/components/android_acres/android_acres_test.dart @@ -2,6 +2,7 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/behaviors/bumper_noisy_behavior.dart'; import 'package:pinball/game/components/android_acres/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -33,11 +34,12 @@ void main() { Assets.images.android.bumper.cow.lit.keyName, Assets.images.android.bumper.cow.dimmed.keyName, ]; - final flameTester = FlameTester( - () => EmptyPinballTestGame(assets: assets), - ); group('AndroidAcres', () { + final flameTester = FlameTester( + () => EmptyPinballTestGame(assets: assets), + ); + flameTester.test('loads correctly', (game) async { final component = AndroidAcres(); await game.ensureAdd(component); @@ -99,6 +101,20 @@ void main() { ); }, ); + + flameTester.test( + 'three AndroidBumpers with BumperNoisyBehavior', + (game) async { + await game.ensureAdd(AndroidAcres()); + final bumpers = game.descendants().whereType(); + for (final bumper in bumpers) { + expect( + bumper.firstChild(), + isNotNull, + ); + } + }, + ); }); flameTester.test('adds an AndroidSpaceshipBonusBehavior', (game) async { diff --git a/test/game/components/controlled_ball_test.dart b/test/game/components/controlled_ball_test.dart index cfb3e157..d8d31b4e 100644 --- a/test/game/components/controlled_ball_test.dart +++ b/test/game/components/controlled_ball_test.dart @@ -1,6 +1,7 @@ // ignore_for_file: cascade_invocations import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -99,6 +100,7 @@ void main() { group('turboCharge', () { setUpAll(() { registerFallbackValue(Vector2.zero()); + registerFallbackValue(Component()); }); flameBlocTester.testGameWidget( @@ -124,7 +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 {}); + when(() => ball.add(any())).thenAnswer((_) async {}); await controller.turboCharge(); @@ -140,29 +142,13 @@ void main() { final controller = _WrappedBallController(ball, gameRef); when(() => gameRef.read()).thenReturn(gameBloc); when(() => ball.controller).thenReturn(controller); - when(() => ball.boost(any())).thenAnswer((_) async {}); + when(() => ball.add(any())).thenAnswer((_) async {}); await controller.turboCharge(); verify(ball.resume).called(1); }, ); - - flameBlocTester.test( - 'boosts the ball', - (game) async { - final gameRef = _MockPinballGame(); - final ball = _MockControlledBall(); - final controller = _WrappedBallController(ball, gameRef); - when(() => gameRef.read()).thenReturn(gameBloc); - when(() => ball.controller).thenReturn(controller); - when(() => ball.boost(any())).thenAnswer((_) async {}); - - await controller.turboCharge(); - - verify(() => ball.boost(any())).called(1); - }, - ); }); }); } diff --git a/test/game/components/dino_desert/dino_desert_test.dart b/test/game/components/dino_desert/dino_desert_test.dart index 20c9ad38..63e45e5b 100644 --- a/test/game/components/dino_desert/dino_desert_test.dart +++ b/test/game/components/dino_desert/dino_desert_test.dart @@ -2,6 +2,7 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/components/dino_desert/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -67,13 +68,13 @@ void main() { group('adds', () { flameTester.test( - 'ScoringBehavior to ChromeDino', + 'ScoringContactBehavior to ChromeDino', (game) async { await game.ensureAdd(DinoDesert()); final chromeDino = game.descendants().whereType().single; expect( - chromeDino.firstChild(), + chromeDino.firstChild(), isNotNull, ); }, diff --git a/test/game/components/flutter_forest/flutter_forest_test.dart b/test/game/components/flutter_forest/flutter_forest_test.dart index 5761a9eb..6dddcd7b 100644 --- a/test/game/components/flutter_forest/flutter_forest_test.dart +++ b/test/game/components/flutter_forest/flutter_forest_test.dart @@ -2,6 +2,7 @@ import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball/game/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -73,6 +74,21 @@ void main() { ); }, ); + + flameTester.test( + 'three DashNestBumpers with BumperNoisyBehavior', + (game) async { + final flutterForest = FlutterForest(); + await game.ensureAdd(ZCanvasComponent(children: [flutterForest])); + final bumpers = game.descendants().whereType(); + for (final bumper in bumpers) { + expect( + bumper.firstChild(), + isNotNull, + ); + } + }, + ); }); }); } diff --git a/test/game/components/scoring_behavior_test.dart b/test/game/components/scoring_behavior_test.dart deleted file mode 100644 index 3e0f7fb4..00000000 --- a/test/game/components/scoring_behavior_test.dart +++ /dev/null @@ -1,172 +0,0 @@ -// ignore_for_file: cascade_invocations - -import 'package:bloc_test/bloc_test.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/game.dart'; -import 'package:pinball_audio/pinball_audio.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -import '../../helpers/helpers.dart'; - -class _TestBodyComponent extends BodyComponent { - @override - Body createBody() => world.createBody(BodyDef()); -} - -class _MockPinballAudio extends Mock implements PinballAudio {} - -class _MockBall extends Mock implements Ball {} - -class _MockBody extends Mock implements Body {} - -class _MockGameBloc extends Mock implements GameBloc {} - -class _MockContact extends Mock implements Contact {} - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - final assets = [ - Assets.images.score.fiveThousand.keyName, - Assets.images.score.twentyThousand.keyName, - Assets.images.score.twoHundredThousand.keyName, - Assets.images.score.oneMillion.keyName, - ]; - - group('ScoringBehavior', () { - 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( - 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( - 'emits Scored event with points', - setUp: (game, tester) async { - const points = Points.oneMillion; - final scoringBehavior = ScoringBehavior(points: points); - await parent.add(scoringBehavior); - final canvas = ZCanvasComponent(children: [parent]); - await game.ensureAdd(canvas); - - scoringBehavior.beginContact(ball, _MockContact()); - - verify( - () => bloc.add( - Scored(points: points.value), - ), - ).called(1); - }, - ); - - flameBlocTester.testGameWidget( - "adds a ScoreComponent at Ball's position with points", - setUp: (game, tester) async { - const points = Points.oneMillion; - final scoringBehavior = ScoringBehavior(points: points); - await parent.add(scoringBehavior); - final canvas = ZCanvasComponent(children: [parent]); - await game.ensureAdd(canvas); - - scoringBehavior.beginContact(ball, _MockContact()); - await game.ready(); - - final scoreText = game.descendants().whereType(); - expect(scoreText.length, equals(1)); - expect( - scoreText.first.points, - equals(points), - ); - expect( - scoreText.first.position, - equals(ball.body.position), - ); - }, - ); - }); - }); - - 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( - 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); - }, - ); - }); - }); -} diff --git a/test/game/components/sparky_scorch_test.dart b/test/game/components/sparky_scorch_test.dart index 7d9c8c77..5df250dd 100644 --- a/test/game/components/sparky_scorch_test.dart +++ b/test/game/components/sparky_scorch_test.dart @@ -4,6 +4,7 @@ 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/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -74,6 +75,20 @@ void main() { ); }, ); + + flameTester.test( + 'three SparkyBumpers with BumperNoisyBehavior', + (game) async { + await game.ensureAdd(SparkyScorch()); + final bumpers = game.descendants().whereType(); + for (final bumper in bumpers) { + expect( + bumper.firstChild(), + isNotNull, + ); + } + }, + ); }); }); diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index f27219f7..71278d55 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -139,19 +139,16 @@ void main() { ); }); - final flameTester = FlameTester( - () => PinballTestGame(assets: assets), - ); - final debugModeFlameTester = FlameTester( - () => DebugPinballTestGame(assets: assets), - ); - - final flameBlocTester = FlameBlocTester( - gameBuilder: () => PinballTestGame(assets: assets), - blocBuilder: () => gameBloc, - ); - group('PinballGame', () { + final flameTester = FlameTester( + () => PinballTestGame(assets: assets), + ); + + final flameBlocTester = FlameBlocTester( + gameBuilder: () => PinballTestGame(assets: assets), + blocBuilder: () => gameBloc, + ); + group('components', () { // TODO(alestiago): tests that Blueprints get added once the Blueprint // class is removed. @@ -246,6 +243,8 @@ void main() { final newState = _MockGameState(); when(() => newState.isGameOver).thenReturn(false); + await game.ready(); + expect( game.descendants().whereType().length, greaterThan(0), @@ -438,6 +437,10 @@ void main() { }); group('DebugPinballGame', () { + final debugModeFlameTester = FlameTester( + () => DebugPinballTestGame(assets: assets), + ); + debugModeFlameTester.test( 'adds a ball on tap up', (game) async { @@ -451,6 +454,7 @@ void main() { when(() => tapUpEvent.eventPosition).thenReturn(eventPosition); when(() => tapUpEvent.raw).thenReturn(raw); + await game.ready(); final previousBalls = game.descendants().whereType().toList();