diff --git a/lib/game/components/alien_zone.dart b/lib/game/components/alien_zone.dart index 30d7bbd5..720c1180 100644 --- a/lib/game/components/alien_zone.dart +++ b/lib/game/components/alien_zone.dart @@ -5,14 +5,12 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; /// {@template alien_zone} /// Area positioned below [Spaceship] where the [Ball] /// can bounce off [AlienBumper]s. /// -/// When a [Ball] hits [AlienBumper]s, they toggle between activated and -/// deactivated states. +/// When a [Ball] hits an [AlienBumper], the bumper animates. /// {@endtemplate} class AlienZone extends Component with HasGameRef { /// {@macro alien_zone} @@ -22,12 +20,12 @@ class AlienZone extends Component with HasGameRef { Future onLoad() async { await super.onLoad(); - gameRef.addContactCallback(_ControlledAlienBumperBallContactCallback()); + gameRef.addContactCallback(AlienBumperBallContactCallback()); - final lowerBumper = ControlledAlienBumper.a() - ..initialPosition = Vector2(-32.52, -9.34); - final upperBumper = ControlledAlienBumper.b() - ..initialPosition = Vector2(-22.89, -17.43); + final lowerBumper = _AlienBumper.a() + ..initialPosition = Vector2(-32.52, -9.1); + final upperBumper = _AlienBumper.b() + ..initialPosition = Vector2(-22.89, -17.35); await addAll([ lowerBumper, @@ -36,60 +34,27 @@ class AlienZone extends Component with HasGameRef { } } -/// {@template controlled_alien_bumper} -/// [AlienBumper] with [_AlienBumperController] attached. -/// {@endtemplate} -@visibleForTesting -class ControlledAlienBumper extends AlienBumper - with Controls<_AlienBumperController>, ScorePoints { - /// {@macro controlled_alien_bumper} - ControlledAlienBumper.a() : super.a() { - controller = _AlienBumperController(this); - } +// TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D +// ContactCallback process is enhanced. +class _AlienBumper extends AlienBumper with ScorePoints { + _AlienBumper.a() : super.a(); - /// {@macro controlled_alien_bumper} - ControlledAlienBumper.b() : super.b() { - controller = _AlienBumperController(this); - } + _AlienBumper.b() : super.b(); @override - // TODO(ruimiguel): change points when get final points map. int get points => 20; } -/// {@template alien_bumper_controller} -/// Controls a [AlienBumper]. -/// {@endtemplate} -class _AlienBumperController extends ComponentController - with HasGameRef { - /// {@macro alien_bumper_controller} - _AlienBumperController(AlienBumper alienBumper) : super(alienBumper); - - /// Flag for activated state of the [AlienBumper]. - /// - /// Used to toggle [AlienBumper]s' state between activated and deactivated. - bool isActivated = false; - - /// Registers when a [AlienBumper] is hit by a [Ball]. - void hit() { - if (isActivated) { - component.deactivate(); - } else { - component.activate(); - } - isActivated = !isActivated; - } -} - -/// Listens when a [Ball] bounces bounces against a [AlienBumper]. -class _ControlledAlienBumperBallContactCallback - extends ContactCallback, Ball> { +/// Listens when a [Ball] bounces against an [AlienBumper]. +@visibleForTesting +class AlienBumperBallContactCallback + extends ContactCallback { @override void begin( - Controls<_AlienBumperController> controlledAlienBumper, + AlienBumper alienBumper, Ball _, Contact __, ) { - controlledAlienBumper.controller.hit(); + alienBumper.animate(); } } diff --git a/lib/game/components/flutter_forest.dart b/lib/game/components/flutter_forest.dart index 5149ad81..8fc6cfbd 100644 --- a/lib/game/components/flutter_forest.dart +++ b/lib/game/components/flutter_forest.dart @@ -11,7 +11,7 @@ import 'package:pinball_flame/pinball_flame.dart'; /// can bounce off [DashNestBumper]s. /// /// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest] -/// is awarded, and the [BigDashNestBumper] releases a new [Ball]. +/// is awarded, and the [DashNestBumper.main] releases a new [Ball]. /// {@endtemplate} class FlutterForest extends Component with Controls<_FlutterForestController>, HasGameRef { @@ -27,11 +27,11 @@ class FlutterForest extends Component final signpost = Signpost()..initialPosition = Vector2(8.35, -58.3); - final bigNest = _BigDashNestBumper() + final bigNest = _DashNestBumper.main() ..initialPosition = Vector2(18.55, -59.35); - final smallLeftNest = _SmallDashNestBumper.a() + final smallLeftNest = _DashNestBumper.a() ..initialPosition = Vector2(8.95, -51.95); - final smallRightNest = _SmallDashNestBumper.b() + final smallRightNest = _DashNestBumper.b() ..initialPosition = Vector2(23.3, -46.75); final dashAnimatronic = DashAnimatronic()..position = Vector2(20, -66); @@ -81,15 +81,12 @@ class _FlutterForestController extends ComponentController // TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D // ContactCallback process is enhanced. -class _BigDashNestBumper extends BigDashNestBumper with ScorePoints { - @override - int get points => 20; -} +class _DashNestBumper extends DashNestBumper with ScorePoints { + _DashNestBumper.main() : super.main(); -class _SmallDashNestBumper extends SmallDashNestBumper with ScorePoints { - _SmallDashNestBumper.a() : super.a(); + _DashNestBumper.a() : super.a(); - _SmallDashNestBumper.b() : super.b(); + _DashNestBumper.b() : super.b(); @override int get points => 20; diff --git a/lib/game/components/sparky_fire_zone.dart b/lib/game/components/sparky_fire_zone.dart index 0a5abe88..6d78d32b 100644 --- a/lib/game/components/sparky_fire_zone.dart +++ b/lib/game/components/sparky_fire_zone.dart @@ -5,14 +5,12 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; /// {@template sparky_fire_zone} /// Area positioned at the top left of the [Board] where the [Ball] /// can bounce off [SparkyBumper]s. /// -/// When a [Ball] hits [SparkyBumper]s, they toggle between activated and -/// deactivated states. +/// When a [Ball] hits [SparkyBumper]s, the bumper animates. /// {@endtemplate} class SparkyFireZone extends Component with HasGameRef { /// {@macro sparky_fire_zone} @@ -22,14 +20,14 @@ class SparkyFireZone extends Component with HasGameRef { Future onLoad() async { await super.onLoad(); - gameRef.addContactCallback(_ControlledSparkyBumperBallContactCallback()); + gameRef.addContactCallback(SparkyBumperBallContactCallback()); - final lowerLeftBumper = ControlledSparkyBumper.a() - ..initialPosition = Vector2(-23.15, -41.65); - final upperLeftBumper = ControlledSparkyBumper.b() - ..initialPosition = Vector2(-21.25, -58.15); - final rightBumper = ControlledSparkyBumper.c() - ..initialPosition = Vector2(-3.56, -53.051); + final lowerLeftBumper = _SparkyBumper.a() + ..initialPosition = Vector2(-22.9, -41.65); + final upperLeftBumper = _SparkyBumper.b() + ..initialPosition = Vector2(-21.25, -57.9); + final rightBumper = _SparkyBumper.c() + ..initialPosition = Vector2(-3.3, -52.55); await addAll([ lowerLeftBumper, @@ -39,65 +37,29 @@ class SparkyFireZone extends Component with HasGameRef { } } -/// {@template controlled_sparky_bumper} -/// [SparkyBumper] with [_SparkyBumperController] attached. -/// {@endtemplate} -@visibleForTesting -class ControlledSparkyBumper extends SparkyBumper - with Controls<_SparkyBumperController>, ScorePoints { - ///{@macro controlled_sparky_bumper} - ControlledSparkyBumper.a() : super.a() { - controller = _SparkyBumperController(this); - } +// TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D +// ContactCallback process is enhanced. +class _SparkyBumper extends SparkyBumper with ScorePoints { + _SparkyBumper.a() : super.a(); - ///{@macro controlled_sparky_bumper} - ControlledSparkyBumper.b() : super.b() { - controller = _SparkyBumperController(this); - } + _SparkyBumper.b() : super.b(); - ///{@macro controlled_sparky_bumper} - ControlledSparkyBumper.c() : super.c() { - controller = _SparkyBumperController(this); - } + _SparkyBumper.c() : super.c(); @override int get points => 20; } -/// {@template sparky_bumper_controller} -/// Controls a [SparkyBumper]. -/// {@endtemplate} -class _SparkyBumperController extends ComponentController - with HasGameRef { - /// {@macro sparky_bumper_controller} - _SparkyBumperController(ControlledSparkyBumper controlledSparkyBumper) - : super(controlledSparkyBumper); - - /// Flag for activated state of the [SparkyBumper]. - /// - /// Used to toggle [SparkyBumper]s' state between activated and deactivated. - bool isActivated = false; - - /// Registers when a [SparkyBumper] is hit by a [Ball]. - void hit() { - if (isActivated) { - component.deactivate(); - } else { - component.activate(); - } - isActivated = !isActivated; - } -} - /// Listens when a [Ball] bounces bounces against a [SparkyBumper]. -class _ControlledSparkyBumperBallContactCallback - extends ContactCallback, Ball> { +@visibleForTesting +class SparkyBumperBallContactCallback + extends ContactCallback { @override void begin( - Controls<_SparkyBumperController> controlledSparkyBumper, + SparkyBumper sparkyBumper, Ball _, Contact __, ) { - controlledSparkyBumper.controller.hit(); + sparkyBumper.animate(); } } diff --git a/lib/game/game_assets.dart b/lib/game/game_assets.dart index 1e966a42..361bbeaf 100644 --- a/lib/game/game_assets.dart +++ b/lib/game/game_assets.dart @@ -51,6 +51,10 @@ extension PinballGameAssetsX on PinballGame { ), images.load(components.Assets.images.spaceship.rail.main.keyName), images.load(components.Assets.images.spaceship.rail.foreground.keyName), + images.load(components.Assets.images.alienBumper.a.active.keyName), + images.load(components.Assets.images.alienBumper.a.inactive.keyName), + images.load(components.Assets.images.alienBumper.b.active.keyName), + images.load(components.Assets.images.alienBumper.b.inactive.keyName), images.load(components.Assets.images.chromeDino.mouth.keyName), images.load(components.Assets.images.chromeDino.head.keyName), images.load(components.Assets.images.plunger.plunger.keyName), diff --git a/packages/pinball_components/assets/images/alien_bumper/a/active.png b/packages/pinball_components/assets/images/alien_bumper/a/active.png index 92943dfc..4bb38a74 100644 Binary files a/packages/pinball_components/assets/images/alien_bumper/a/active.png and b/packages/pinball_components/assets/images/alien_bumper/a/active.png differ diff --git a/packages/pinball_components/assets/images/alien_bumper/a/inactive.png b/packages/pinball_components/assets/images/alien_bumper/a/inactive.png index 99745fcf..d693f1c1 100644 Binary files a/packages/pinball_components/assets/images/alien_bumper/a/inactive.png and b/packages/pinball_components/assets/images/alien_bumper/a/inactive.png differ diff --git a/packages/pinball_components/assets/images/alien_bumper/b/active.png b/packages/pinball_components/assets/images/alien_bumper/b/active.png index ec7dc0cb..fbfd36e1 100644 Binary files a/packages/pinball_components/assets/images/alien_bumper/b/active.png and b/packages/pinball_components/assets/images/alien_bumper/b/active.png differ diff --git a/packages/pinball_components/assets/images/alien_bumper/b/inactive.png b/packages/pinball_components/assets/images/alien_bumper/b/inactive.png index 23f7f4b7..fd23693d 100644 Binary files a/packages/pinball_components/assets/images/alien_bumper/b/inactive.png and b/packages/pinball_components/assets/images/alien_bumper/b/inactive.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/a/active.png b/packages/pinball_components/assets/images/dash/bumper/a/active.png index feeee11f..57330eb4 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/a/active.png and b/packages/pinball_components/assets/images/dash/bumper/a/active.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/a/inactive.png b/packages/pinball_components/assets/images/dash/bumper/a/inactive.png index 58ab8c56..aead95ec 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/a/inactive.png and b/packages/pinball_components/assets/images/dash/bumper/a/inactive.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/b/active.png b/packages/pinball_components/assets/images/dash/bumper/b/active.png index 4bc2897f..fe871847 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/b/active.png and b/packages/pinball_components/assets/images/dash/bumper/b/active.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/b/inactive.png b/packages/pinball_components/assets/images/dash/bumper/b/inactive.png index eddc7693..3d53b743 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/b/inactive.png and b/packages/pinball_components/assets/images/dash/bumper/b/inactive.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/main/active.png b/packages/pinball_components/assets/images/dash/bumper/main/active.png index bef56684..9508b56c 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/main/active.png and b/packages/pinball_components/assets/images/dash/bumper/main/active.png differ diff --git a/packages/pinball_components/assets/images/dash/bumper/main/inactive.png b/packages/pinball_components/assets/images/dash/bumper/main/inactive.png index e6f15b38..b1d0ae7d 100644 Binary files a/packages/pinball_components/assets/images/dash/bumper/main/inactive.png and b/packages/pinball_components/assets/images/dash/bumper/main/inactive.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/a/active.png b/packages/pinball_components/assets/images/sparky/bumper/a/active.png index 6e84d8ef..81a91a52 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/a/active.png and b/packages/pinball_components/assets/images/sparky/bumper/a/active.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/a/inactive.png b/packages/pinball_components/assets/images/sparky/bumper/a/inactive.png index 9157a73f..a81c7915 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/a/inactive.png and b/packages/pinball_components/assets/images/sparky/bumper/a/inactive.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/b/active.png b/packages/pinball_components/assets/images/sparky/bumper/b/active.png index 02371ce1..a00f3f33 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/b/active.png and b/packages/pinball_components/assets/images/sparky/bumper/b/active.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/b/inactive.png b/packages/pinball_components/assets/images/sparky/bumper/b/inactive.png index 20b4f092..0eac905c 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/b/inactive.png and b/packages/pinball_components/assets/images/sparky/bumper/b/inactive.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/c/active.png b/packages/pinball_components/assets/images/sparky/bumper/c/active.png index 85748375..265f35aa 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/c/active.png and b/packages/pinball_components/assets/images/sparky/bumper/c/active.png differ diff --git a/packages/pinball_components/assets/images/sparky/bumper/c/inactive.png b/packages/pinball_components/assets/images/sparky/bumper/c/inactive.png index b5b3584d..50a69d54 100644 Binary files a/packages/pinball_components/assets/images/sparky/bumper/c/inactive.png and b/packages/pinball_components/assets/images/sparky/bumper/c/inactive.png differ diff --git a/packages/pinball_components/lib/src/components/alien_bumper.dart b/packages/pinball_components/lib/src/components/alien_bumper.dart index b78c6adc..1f96d214 100644 --- a/packages/pinball_components/lib/src/components/alien_bumper.dart +++ b/packages/pinball_components/lib/src/components/alien_bumper.dart @@ -1,37 +1,41 @@ +import 'dart:async'; + import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template alien_bumper} -/// Bumper for Alien area. +/// Bumper for area under the [Spaceship]. /// {@endtemplate} -// TODO(ruimiguel): refactor later to unify with DashBumpers. class AlienBumper extends BodyComponent with InitialPosition { /// {@macro alien_bumper} AlienBumper._({ required double majorRadius, required double minorRadius, - required String activeAssetPath, - required String inactiveAssetPath, - required SpriteComponent spriteComponent, + required String onAssetPath, + required String offAssetPath, }) : _majorRadius = majorRadius, _minorRadius = minorRadius, - _activeAssetPath = activeAssetPath, - _inactiveAssetPath = inactiveAssetPath, - _spriteComponent = spriteComponent, - super(priority: RenderPriority.alienBumper); + super( + priority: RenderPriority.alienBumper, + children: [ + _AlienBumperSpriteGroupComponent( + onAssetPath: onAssetPath, + offAssetPath: offAssetPath, + ), + ], + ) { + renderBody = false; + } /// {@macro alien_bumper} AlienBumper.a() : this._( majorRadius: 3.52, minorRadius: 2.97, - activeAssetPath: Assets.images.alienBumper.a.active.keyName, - inactiveAssetPath: Assets.images.alienBumper.a.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.1), - ), + onAssetPath: Assets.images.alienBumper.a.active.keyName, + offAssetPath: Assets.images.alienBumper.a.inactive.keyName, ); /// {@macro alien_bumper} @@ -39,32 +43,12 @@ class AlienBumper extends BodyComponent with InitialPosition { : this._( majorRadius: 3.19, minorRadius: 2.79, - activeAssetPath: Assets.images.alienBumper.b.active.keyName, - inactiveAssetPath: Assets.images.alienBumper.b.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.1), - ), + onAssetPath: Assets.images.alienBumper.b.active.keyName, + offAssetPath: Assets.images.alienBumper.b.inactive.keyName, ); final double _majorRadius; final double _minorRadius; - final String _activeAssetPath; - late final Sprite _activeSprite; - final String _inactiveAssetPath; - late final Sprite _inactiveSprite; - final SpriteComponent _spriteComponent; - - @override - Future onLoad() async { - await super.onLoad(); - renderBody = false; - - await _loadSprites(); - - deactivate(); - await add(_spriteComponent); - } @override Body createBody() { @@ -85,25 +69,53 @@ class AlienBumper extends BodyComponent with InitialPosition { return world.createBody(bodyDef)..createFixture(fixtureDef); } - Future _loadSprites() async { - // TODO(alestiago): I think ideally we would like to do: - // Sprite(path).load so we don't require to store the activeAssetPath and - // the inactive assetPath. - _inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath); - _activeSprite = await gameRef.loadSprite(_activeAssetPath); + /// Animates the [AlienBumper]. + Future animate() async { + final spriteGroupComponent = firstChild<_AlienBumperSpriteGroupComponent>() + ?..current = AlienBumperSpriteState.inactive; + await Future.delayed(const Duration(milliseconds: 50)); + spriteGroupComponent?.current = AlienBumperSpriteState.active; } +} - /// Activates the [AlienBumper]. - void activate() { - _spriteComponent - ..sprite = _activeSprite - ..size = _activeSprite.originalSize / 10; - } +/// Indicates the [AlienBumper]'s current sprite state. +@visibleForTesting +enum AlienBumperSpriteState { + /// A lit up bumper. + active, + + /// A dimmed bumper. + inactive, +} + +class _AlienBumperSpriteGroupComponent + extends SpriteGroupComponent with HasGameRef { + _AlienBumperSpriteGroupComponent({ + required String onAssetPath, + required String offAssetPath, + }) : _onAssetPath = onAssetPath, + _offAssetPath = offAssetPath, + super( + anchor: Anchor.center, + position: Vector2(0, -0.1), + ); + + final String _onAssetPath; + final String _offAssetPath; + + @override + Future onLoad() async { + await super.onLoad(); + + final sprites = { + AlienBumperSpriteState.active: + Sprite(gameRef.images.fromCache(_onAssetPath)), + AlienBumperSpriteState.inactive: + Sprite(gameRef.images.fromCache(_offAssetPath)), + }; + this.sprites = sprites; - /// Deactivates the [AlienBumper]. - void deactivate() { - _spriteComponent - ..sprite = _inactiveSprite - ..size = _inactiveSprite.originalSize / 10; + current = AlienBumperSpriteState.active; + size = sprites[current]!.originalSize / 10; } } diff --git a/packages/pinball_components/lib/src/components/dash_animatronic.dart b/packages/pinball_components/lib/src/components/dash_animatronic.dart index bc66b184..634c5574 100644 --- a/packages/pinball_components/lib/src/components/dash_animatronic.dart +++ b/packages/pinball_components/lib/src/components/dash_animatronic.dart @@ -2,7 +2,7 @@ import 'package:flame/components.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template dash_animatronic} -/// Animated Dash that sits on top of the [BigDashNestBumper]. +/// Animated Dash that sits on top of the [DashNestBumper.main]. /// {@endtemplate} class DashAnimatronic extends SpriteAnimationComponent with HasGameRef { /// {@macro dash_animatronic} diff --git a/packages/pinball_components/lib/src/components/dash_nest_bumper.dart b/packages/pinball_components/lib/src/components/dash_nest_bumper.dart index b796b9d1..46f96b37 100644 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper.dart +++ b/packages/pinball_components/lib/src/components/dash_nest_bumper.dart @@ -2,139 +2,76 @@ import 'dart:math' as math; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template dash_nest_bumper} /// Bumper with a nest appearance. /// {@endtemplate} -abstract class DashNestBumper extends BodyComponent with InitialPosition { +class DashNestBumper extends BodyComponent with InitialPosition { /// {@macro dash_nest_bumper} DashNestBumper._({ + required double majorRadius, + required double minorRadius, required String activeAssetPath, required String inactiveAssetPath, - required SpriteComponent spriteComponent, - }) : _activeAssetPath = activeAssetPath, - _inactiveAssetPath = inactiveAssetPath, - _spriteComponent = spriteComponent, - super(priority: RenderPriority.dashBumper); - - final String _activeAssetPath; - late final Sprite _activeSprite; - final String _inactiveAssetPath; - late final Sprite _inactiveSprite; - final SpriteComponent _spriteComponent; - - Future _loadSprites() async { - // TODO(alestiago): I think ideally we would like to do: - // Sprite(path).load so we don't require to store the activeAssetPath and - // the inactive assetPath. - _inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath); - _activeSprite = await gameRef.loadSprite(_activeAssetPath); - } - - /// Activates the [DashNestBumper]. - void activate() { - _spriteComponent - ..sprite = _activeSprite - ..size = _activeSprite.originalSize / 10; + required Vector2 spritePosition, + }) : _majorRadius = majorRadius, + _minorRadius = minorRadius, + super( + priority: RenderPriority.dashBumper, + children: [ + _DashNestBumperSpriteGroupComponent( + activeAssetPath: activeAssetPath, + inactiveAssetPath: inactiveAssetPath, + position: spritePosition, + ), + ], + ) { + renderBody = false; } - /// Deactivates the [DashNestBumper]. - void deactivate() { - _spriteComponent - ..sprite = _inactiveSprite - ..size = _inactiveSprite.originalSize / 10; - } - - @override - Future onLoad() async { - await super.onLoad(); - await _loadSprites(); - - // TODO(erickzanardo): Look into using onNewState instead. - // Currently doing: onNewState(gameRef.read()) will throw an - // `Exception: build context is not available yet` - deactivate(); - await add(_spriteComponent); - } -} - -/// {@macro dash_nest_bumper} -class BigDashNestBumper extends DashNestBumper { /// {@macro dash_nest_bumper} - BigDashNestBumper() - : super._( + DashNestBumper.main() + : this._( + majorRadius: 5.1, + minorRadius: 3.75, activeAssetPath: Assets.images.dash.bumper.main.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.main.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.3), - ), + spritePosition: Vector2(0, -0.3), ); - @override - Body createBody() { - final shape = EllipseShape( - center: Vector2.zero(), - majorRadius: 5.1, - minorRadius: 3.75, - )..rotate(math.pi / 1.9); - final fixtureDef = FixtureDef(shape); - final bodyDef = BodyDef( - position: initialPosition, - userData: this, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } -} - -/// {@macro dash_nest_bumper} -class SmallDashNestBumper extends DashNestBumper { /// {@macro dash_nest_bumper} - SmallDashNestBumper._({ - required String activeAssetPath, - required String inactiveAssetPath, - required SpriteComponent spriteComponent, - }) : super._( - activeAssetPath: activeAssetPath, - inactiveAssetPath: inactiveAssetPath, - spriteComponent: spriteComponent, - ); - - /// {@macro dash_nest_bumper} - SmallDashNestBumper.a() + DashNestBumper.a() : this._( + majorRadius: 3, + minorRadius: 2.5, activeAssetPath: Assets.images.dash.bumper.a.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.a.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0.35, -1.2), - ), + spritePosition: Vector2(0.35, -1.2), ); /// {@macro dash_nest_bumper} - SmallDashNestBumper.b() + DashNestBumper.b() : this._( + majorRadius: 3, + minorRadius: 2.5, activeAssetPath: Assets.images.dash.bumper.b.active.keyName, inactiveAssetPath: Assets.images.dash.bumper.b.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0.35, -1.2), - ), + spritePosition: Vector2(0.35, -1.2), ); + final double _majorRadius; + final double _minorRadius; + @override Body createBody() { final shape = EllipseShape( center: Vector2.zero(), - majorRadius: 3, - minorRadius: 2.25, - )..rotate(math.pi / 2); - final fixtureDef = FixtureDef( - shape, - restitution: 4, - ); + majorRadius: _majorRadius, + minorRadius: _minorRadius, + )..rotate(math.pi / 1.9); + final fixtureDef = FixtureDef(shape, restitution: 4); final bodyDef = BodyDef( position: initialPosition, userData: this, @@ -142,4 +79,58 @@ class SmallDashNestBumper extends DashNestBumper { return world.createBody(bodyDef)..createFixture(fixtureDef); } + + /// Activates the [DashNestBumper]. + void activate() { + firstChild<_DashNestBumperSpriteGroupComponent>()?.current = + DashNestBumperSpriteState.active; + } + + /// Deactivates the [DashNestBumper]. + void deactivate() { + firstChild<_DashNestBumperSpriteGroupComponent>()?.current = + DashNestBumperSpriteState.inactive; + } +} + +/// Indicates the [DashNestBumper]'s current sprite state. +@visibleForTesting +enum DashNestBumperSpriteState { + /// A lit up bumper. + active, + + /// A dimmed bumper. + inactive, +} + +class _DashNestBumperSpriteGroupComponent + extends SpriteGroupComponent with HasGameRef { + _DashNestBumperSpriteGroupComponent({ + required String activeAssetPath, + required String inactiveAssetPath, + required Vector2 position, + }) : _activeAssetPath = activeAssetPath, + _inactiveAssetPath = inactiveAssetPath, + super( + anchor: Anchor.center, + position: position, + ); + + final String _activeAssetPath; + final String _inactiveAssetPath; + + @override + Future onLoad() async { + await super.onLoad(); + final sprites = { + DashNestBumperSpriteState.active: + Sprite(gameRef.images.fromCache(_activeAssetPath)), + DashNestBumperSpriteState.inactive: + Sprite(gameRef.images.fromCache(_inactiveAssetPath)), + }; + this.sprites = sprites; + + current = DashNestBumperSpriteState.inactive; + size = sprites[current]!.originalSize / 10; + } } diff --git a/packages/pinball_components/lib/src/components/signpost.dart b/packages/pinball_components/lib/src/components/signpost.dart index 665c2cbb..175c3382 100644 --- a/packages/pinball_components/lib/src/components/signpost.dart +++ b/packages/pinball_components/lib/src/components/signpost.dart @@ -88,12 +88,9 @@ class _SignpostSpriteComponent extends SpriteGroupComponent final sprites = {}; this.sprites = sprites; for (final spriteState in SignpostSpriteState.values) { - // TODO(allisonryan0002): Support caching - // https://github.com/VGVentures/pinball/pull/204 - // sprites[spriteState] = Sprite( - // gameRef.images.fromCache(spriteState.path), - // ); - sprites[spriteState] = await gameRef.loadSprite(spriteState.path); + sprites[spriteState] = Sprite( + gameRef.images.fromCache(spriteState.path), + ); } current = SignpostSpriteState.inactive; diff --git a/packages/pinball_components/lib/src/components/sparky_bumper.dart b/packages/pinball_components/lib/src/components/sparky_bumper.dart index e4d9ebef..becac26b 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper.dart @@ -2,38 +2,43 @@ import 'dart:math' as math; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; /// {@template sparky_bumper} /// Bumper for Sparky area. /// {@endtemplate} -// TODO(ruimiguel): refactor later to unify with DashBumpers. class SparkyBumper extends BodyComponent with InitialPosition { /// {@macro sparky_bumper} SparkyBumper._({ required double majorRadius, required double minorRadius, - required String activeAssetPath, - required String inactiveAssetPath, - required SpriteComponent spriteComponent, + required String onAssetPath, + required String offAssetPath, + required Vector2 spritePosition, }) : _majorRadius = majorRadius, _minorRadius = minorRadius, - _activeAssetPath = activeAssetPath, - _inactiveAssetPath = inactiveAssetPath, - _spriteComponent = spriteComponent, - super(priority: RenderPriority.sparkyBumper); + super( + priority: RenderPriority.sparkyBumper, + children: [ + _SparkyBumperSpriteGroupComponent( + onAssetPath: onAssetPath, + offAssetPath: offAssetPath, + position: spritePosition, + ), + ], + ) { + renderBody = false; + } /// {@macro sparky_bumper} SparkyBumper.a() : this._( majorRadius: 2.9, minorRadius: 2.1, - activeAssetPath: Assets.images.sparky.bumper.a.active.keyName, - inactiveAssetPath: Assets.images.sparky.bumper.a.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.25), - ), + onAssetPath: Assets.images.sparky.bumper.a.active.keyName, + offAssetPath: Assets.images.sparky.bumper.a.inactive.keyName, + spritePosition: Vector2(0, -0.25), ); /// {@macro sparky_bumper} @@ -41,12 +46,9 @@ class SparkyBumper extends BodyComponent with InitialPosition { : this._( majorRadius: 2.85, minorRadius: 2, - activeAssetPath: Assets.images.sparky.bumper.b.active.keyName, - inactiveAssetPath: Assets.images.sparky.bumper.b.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.35), - ), + onAssetPath: Assets.images.sparky.bumper.b.active.keyName, + offAssetPath: Assets.images.sparky.bumper.b.inactive.keyName, + spritePosition: Vector2(0, -0.35), ); /// {@macro sparky_bumper} @@ -54,33 +56,13 @@ class SparkyBumper extends BodyComponent with InitialPosition { : this._( majorRadius: 3, minorRadius: 2.2, - activeAssetPath: Assets.images.sparky.bumper.c.active.keyName, - inactiveAssetPath: Assets.images.sparky.bumper.c.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - position: Vector2(0, -0.4), - ), + onAssetPath: Assets.images.sparky.bumper.c.active.keyName, + offAssetPath: Assets.images.sparky.bumper.c.inactive.keyName, + spritePosition: Vector2(0, -0.4), ); final double _majorRadius; final double _minorRadius; - final String _activeAssetPath; - late final Sprite _activeSprite; - final String _inactiveAssetPath; - late final Sprite _inactiveSprite; - final SpriteComponent _spriteComponent; - - @override - Future onLoad() async { - await super.onLoad(); - await _loadSprites(); - - // TODO(erickzanardo): Look into using onNewState instead. - // Currently doing: onNewState(gameRef.read()) will throw an - // `Exception: build context is not available yet` - deactivate(); - await add(_spriteComponent); - } @override Body createBody() { @@ -102,25 +84,53 @@ class SparkyBumper extends BodyComponent with InitialPosition { return world.createBody(bodyDef)..createFixture(fixtureDef); } - Future _loadSprites() async { - // TODO(alestiago): I think ideally we would like to do: - // Sprite(path).load so we don't require to store the activeAssetPath and - // the inactive assetPath. - _inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath); - _activeSprite = await gameRef.loadSprite(_activeAssetPath); + /// Animates the [DashNestBumper]. + Future animate() async { + final spriteGroupComponent = firstChild<_SparkyBumperSpriteGroupComponent>() + ?..current = SparkyBumperSpriteState.inactive; + await Future.delayed(const Duration(milliseconds: 50)); + spriteGroupComponent?.current = SparkyBumperSpriteState.active; } +} - /// Activates the [DashNestBumper]. - void activate() { - _spriteComponent - ..sprite = _activeSprite - ..size = _activeSprite.originalSize / 10; - } +/// Indicates the [SparkyBumper]'s current sprite state. +@visibleForTesting +enum SparkyBumperSpriteState { + /// A lit up bumper. + active, + + /// A dimmed bumper. + inactive, +} + +class _SparkyBumperSpriteGroupComponent + extends SpriteGroupComponent with HasGameRef { + _SparkyBumperSpriteGroupComponent({ + required String onAssetPath, + required String offAssetPath, + required Vector2 position, + }) : _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 = { + SparkyBumperSpriteState.active: + Sprite(gameRef.images.fromCache(_onAssetPath)), + SparkyBumperSpriteState.inactive: + Sprite(gameRef.images.fromCache(_offAssetPath)), + }; + this.sprites = sprites; - /// Deactivates the [DashNestBumper]. - void deactivate() { - _spriteComponent - ..sprite = _inactiveSprite - ..size = _inactiveSprite.originalSize / 10; + current = SparkyBumperSpriteState.active; + size = sprites[current]!.originalSize / 10; } } diff --git a/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_a_game.dart b/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_a_game.dart index 007d67e1..fec10fe0 100644 --- a/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_a_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_a_game.dart @@ -17,6 +17,11 @@ class AlienBumperAGame extends BasicBallGame { Future onLoad() async { await super.onLoad(); + await images.loadAll([ + Assets.images.alienBumper.a.active.keyName, + Assets.images.alienBumper.a.inactive.keyName, + ]); + final center = screenToWorld(camera.viewport.canvasSize! / 2); final alienBumperA = AlienBumper.a() ..initialPosition = Vector2(center.x - 20, center.y - 20) diff --git a/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_b_game.dart b/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_b_game.dart index fada1dd9..0218127b 100644 --- a/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_b_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/alien_zone/alien_bumper_b_game.dart @@ -17,6 +17,11 @@ class AlienBumperBGame extends BasicBallGame { Future onLoad() async { await super.onLoad(); + await images.loadAll([ + Assets.images.alienBumper.b.active.keyName, + Assets.images.alienBumper.b.inactive.keyName, + ]); + final center = screenToWorld(camera.viewport.canvasSize! / 2); final alienBumperB = AlienBumper.b() ..initialPosition = Vector2(center.x - 10, center.y + 10) diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart index e74abe35..1ccd7e8e 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/big_dash_nest_bumper_game.dart @@ -15,9 +15,14 @@ class BigDashNestBumperGame extends BasicBallGame with Traceable { @override Future onLoad() async { await super.onLoad(); + + await images.loadAll([ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + ]); + camera.followVector2(Vector2.zero()); - await add(BigDashNestBumper()..priority = 1); - await traceAllBodies(); + await add(DashNestBumper.main()..priority = 1); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart index b7c11cf2..fd7ce93c 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/signpost_game.dart @@ -17,8 +17,15 @@ class SignpostGame extends BasicBallGame with Traceable, TapDetector { Future onLoad() async { await super.onLoad(); + await images.loadAll([ + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]); + camera.followVector2(Vector2.zero()); - await add(Signpost()..priority = 1); + await add(Signpost()); await traceAllBodies(); } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart index 4ef99b21..b0a621fa 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_a_game.dart @@ -15,9 +15,14 @@ class SmallDashNestBumperAGame extends BasicBallGame with Traceable { @override Future onLoad() async { await super.onLoad(); + + await images.loadAll([ + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + ]); + camera.followVector2(Vector2.zero()); - await add(SmallDashNestBumper.a()..priority = 1); - await traceAllBodies(); + await add(DashNestBumper.a()..priority = 1); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart b/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart index 625b8e5c..5512d4c9 100644 --- a/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/flutter_forest/small_dash_nest_bumper_b_game.dart @@ -15,9 +15,14 @@ class SmallDashNestBumperBGame extends BasicBallGame with Traceable { @override Future onLoad() async { await super.onLoad(); + + await images.loadAll([ + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + ]); + camera.followVector2(Vector2.zero()); - await add(SmallDashNestBumper.b()..priority = 1); - await traceAllBodies(); + await add(DashNestBumper.b()..priority = 1); await traceAllBodies(); } } diff --git a/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart b/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart index 37537952..265b6246 100644 --- a/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart +++ b/packages/pinball_components/sandbox/lib/stories/sparky_bumper/sparky_bumper_game.dart @@ -16,12 +16,21 @@ class SparkyBumperGame extends BasicBallGame with Traceable { Future onLoad() async { await super.onLoad(); + await images.loadAll([ + Assets.images.sparky.bumper.a.active.keyName, + Assets.images.sparky.bumper.a.inactive.keyName, + Assets.images.sparky.bumper.b.active.keyName, + Assets.images.sparky.bumper.b.inactive.keyName, + Assets.images.sparky.bumper.c.active.keyName, + Assets.images.sparky.bumper.c.inactive.keyName, + ]); + final center = screenToWorld(camera.viewport.canvasSize! / 2); final sparkyBumperA = SparkyBumper.a() - ..initialPosition = Vector2(center.x - 20, center.y - 20) + ..initialPosition = Vector2(center.x - 20, center.y + 20) ..priority = 1; final sparkyBumperB = SparkyBumper.b() - ..initialPosition = Vector2(center.x - 10, center.y + 10) + ..initialPosition = Vector2(center.x - 10, center.y - 10) ..priority = 1; final sparkyBumperC = SparkyBumper.c() ..initialPosition = Vector2(center.x + 20, center.y) diff --git a/packages/pinball_components/test/helpers/test_game.dart b/packages/pinball_components/test/helpers/test_game.dart index 5bd4b30d..1faf75e8 100644 --- a/packages/pinball_components/test/helpers/test_game.dart +++ b/packages/pinball_components/test/helpers/test_game.dart @@ -2,9 +2,19 @@ import 'package:flame/input.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; class TestGame extends Forge2DGame { - TestGame() { + TestGame([List? assets]) : _assets = assets { images.prefix = ''; } + + final List? _assets; + + @override + Future onLoad() async { + if (_assets != null) { + await images.loadAll(_assets!); + } + await super.onLoad(); + } } class KeyboardTestGame extends TestGame with HasKeyboardHandlerComponents {} diff --git a/packages/pinball_components/test/src/components/alien_bumper_test.dart b/packages/pinball_components/test/src/components/alien_bumper_test.dart index cd55b62e..c6384759 100644 --- a/packages/pinball_components/test/src/components/alien_bumper_test.dart +++ b/packages/pinball_components/test/src/components/alien_bumper_test.dart @@ -9,7 +9,13 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); + final assets = [ + Assets.images.alienBumper.a.active.keyName, + Assets.images.alienBumper.a.inactive.keyName, + Assets.images.alienBumper.b.active.keyName, + Assets.images.alienBumper.b.inactive.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); group('AlienBumper', () { flameTester.test('"a" loads correctly', (game) async { @@ -25,43 +31,30 @@ void main() { expect(game.contains(bumper), isTrue); }); - flameTester.test('activate returns normally', (game) async { + flameTester.test('animate switches between on and off sprites', + (game) async { final bumper = AlienBumper.a(); await game.ensureAdd(bumper); - expect(bumper.activate, returnsNormally); - }); - - flameTester.test('deactivate returns normally', (game) async { - final bumper = AlienBumper.a(); - await game.ensureAdd(bumper); - - expect(bumper.deactivate, returnsNormally); - }); + final spriteGroupComponent = bumper.firstChild()!; - flameTester.test('changes sprite', (game) async { - final bumper = AlienBumper.a(); - await game.ensureAdd(bumper); - - final spriteComponent = bumper.firstChild()!; - - final deactivatedSprite = spriteComponent.sprite; - bumper.activate(); expect( - spriteComponent.sprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(AlienBumperSpriteState.active), ); - final activatedSprite = spriteComponent.sprite; - bumper.deactivate(); + final future = bumper.animate(); + expect( - spriteComponent.sprite, - isNot(equals(activatedSprite)), + spriteGroupComponent.current, + equals(AlienBumperSpriteState.inactive), ); + await future; + expect( - activatedSprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(AlienBumperSpriteState.active), ); }); }); diff --git a/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart b/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart index 2c6bb00c..ac036ef4 100644 --- a/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart +++ b/packages/pinball_components/test/src/components/dash_nest_bumper_test.dart @@ -9,107 +9,68 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); - group('BigDashNestBumper', () { - flameTester.test('loads correctly', (game) async { - final bumper = BigDashNestBumper(); + group('DashNestBumper', () { + final assets = [ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); + + flameTester.test('"main" loads correctly', (game) async { + final bumper = DashNestBumper.main(); await game.ensureAdd(bumper); expect(game.contains(bumper), isTrue); }); - flameTester.test('activate returns normally', (game) async { - final bumper = BigDashNestBumper(); + flameTester.test('"a" loads correctly', (game) async { + final bumper = DashNestBumper.a(); await game.ensureAdd(bumper); - expect(bumper.activate, returnsNormally); + expect(game.contains(bumper), isTrue); }); - flameTester.test('deactivate returns normally', (game) async { - final bumper = BigDashNestBumper(); + flameTester.test('"b" loads correctly', (game) async { + final bumper = DashNestBumper.b(); await game.ensureAdd(bumper); - - expect(bumper.deactivate, returnsNormally); + expect(game.contains(bumper), isTrue); }); - flameTester.test('changes sprite', (game) async { - final bumper = BigDashNestBumper(); + flameTester.test('activate switches to active sprite', (game) async { + final bumper = DashNestBumper.main(); await game.ensureAdd(bumper); - final spriteComponent = bumper.firstChild()!; + final spriteGroupComponent = bumper.firstChild()!; - final deactivatedSprite = spriteComponent.sprite; - bumper.activate(); expect( - spriteComponent.sprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(DashNestBumperSpriteState.inactive), ); - final activatedSprite = spriteComponent.sprite; - bumper.deactivate(); - expect( - spriteComponent.sprite, - isNot(equals(activatedSprite)), - ); + bumper.activate(); expect( - activatedSprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(DashNestBumperSpriteState.active), ); }); - }); - - group('SmallDashNestBumper', () { - flameTester.test('"a" loads correctly', (game) async { - final bumper = SmallDashNestBumper.a(); - await game.ensureAdd(bumper); - - expect(game.contains(bumper), isTrue); - }); - - flameTester.test('"b" loads correctly', (game) async { - final bumper = SmallDashNestBumper.b(); - await game.ensureAdd(bumper); - expect(game.contains(bumper), isTrue); - }); - - flameTester.test('activate returns normally', (game) async { - final bumper = SmallDashNestBumper.a(); - await game.ensureAdd(bumper); - - expect(bumper.activate, returnsNormally); - }); - - flameTester.test('deactivate returns normally', (game) async { - final bumper = SmallDashNestBumper.a(); - await game.ensureAdd(bumper); - - expect(bumper.deactivate, returnsNormally); - }); - flameTester.test('changes sprite', (game) async { - final bumper = SmallDashNestBumper.a(); + flameTester.test('deactivate switches to inactive sprite', (game) async { + final bumper = DashNestBumper.main(); await game.ensureAdd(bumper); - final spriteComponent = bumper.firstChild()!; + final spriteGroupComponent = bumper.firstChild()! + ..current = DashNestBumperSpriteState.active; - final deactivatedSprite = spriteComponent.sprite; - bumper.activate(); - expect( - spriteComponent.sprite, - isNot(equals(deactivatedSprite)), - ); - - final activatedSprite = spriteComponent.sprite; bumper.deactivate(); - expect( - spriteComponent.sprite, - isNot(equals(activatedSprite)), - ); expect( - activatedSprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(DashNestBumperSpriteState.inactive), ); }); }); diff --git a/packages/pinball_components/test/src/components/signpost_test.dart b/packages/pinball_components/test/src/components/signpost_test.dart index 33844994..018c1bee 100644 --- a/packages/pinball_components/test/src/components/signpost_test.dart +++ b/packages/pinball_components/test/src/components/signpost_test.dart @@ -9,7 +9,13 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); + final assets = [ + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); group('Signpost', () { flameTester.test( @@ -27,8 +33,10 @@ void main() { flameTester.testGameWidget( 'inactive sprite', setUp: (game, tester) async { + await game.images.loadAll(assets); final signpost = Signpost(); await game.ensureAdd(signpost); + await tester.pump(); expect( signpost.firstChild()!.current, @@ -48,9 +56,11 @@ void main() { flameTester.testGameWidget( 'active1 sprite', setUp: (game, tester) async { + await game.images.loadAll(assets); final signpost = Signpost(); await game.ensureAdd(signpost); signpost.progress(); + await tester.pump(); expect( signpost.firstChild()!.current, @@ -70,11 +80,13 @@ void main() { flameTester.testGameWidget( 'active2 sprite', setUp: (game, tester) async { + await game.images.loadAll(assets); final signpost = Signpost(); await game.ensureAdd(signpost); signpost ..progress() ..progress(); + await tester.pump(); expect( signpost.firstChild()!.current, @@ -94,12 +106,14 @@ void main() { flameTester.testGameWidget( 'active3 sprite', setUp: (game, tester) async { + await game.images.loadAll(assets); final signpost = Signpost(); await game.ensureAdd(signpost); signpost ..progress() ..progress() ..progress(); + await tester.pump(); expect( signpost.firstChild()!.current, diff --git a/packages/pinball_components/test/src/components/sparky_bumper_test.dart b/packages/pinball_components/test/src/components/sparky_bumper_test.dart index 470c254b..a2fcc5ed 100644 --- a/packages/pinball_components/test/src/components/sparky_bumper_test.dart +++ b/packages/pinball_components/test/src/components/sparky_bumper_test.dart @@ -9,7 +9,15 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(TestGame.new); + final assets = [ + Assets.images.sparky.bumper.a.active.keyName, + Assets.images.sparky.bumper.a.inactive.keyName, + Assets.images.sparky.bumper.b.active.keyName, + Assets.images.sparky.bumper.b.inactive.keyName, + Assets.images.sparky.bumper.c.active.keyName, + Assets.images.sparky.bumper.c.inactive.keyName, + ]; + final flameTester = FlameTester(() => TestGame(assets)); group('SparkyBumper', () { flameTester.test('"a" loads correctly', (game) async { @@ -31,43 +39,30 @@ void main() { expect(game.contains(bumper), isTrue); }); - flameTester.test('activate returns normally', (game) async { + flameTester.test('animate switches between on and off sprites', + (game) async { final bumper = SparkyBumper.a(); await game.ensureAdd(bumper); - expect(bumper.activate, returnsNormally); - }); - - flameTester.test('deactivate returns normally', (game) async { - final bumper = SparkyBumper.a(); - await game.ensureAdd(bumper); - - expect(bumper.deactivate, returnsNormally); - }); + final spriteGroupComponent = bumper.firstChild()!; - flameTester.test('changes sprite', (game) async { - final bumper = SparkyBumper.a(); - await game.ensureAdd(bumper); - - final spriteComponent = bumper.firstChild()!; - - final deactivatedSprite = spriteComponent.sprite; - bumper.activate(); expect( - spriteComponent.sprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(SparkyBumperSpriteState.active), ); - final activatedSprite = spriteComponent.sprite; - bumper.deactivate(); + final future = bumper.animate(); + expect( - spriteComponent.sprite, - isNot(equals(activatedSprite)), + spriteGroupComponent.current, + equals(SparkyBumperSpriteState.inactive), ); + await future; + expect( - activatedSprite, - isNot(equals(deactivatedSprite)), + spriteGroupComponent.current, + equals(SparkyBumperSpriteState.active), ); }); }); diff --git a/test/game/components/alien_zone_test.dart b/test/game/components/alien_zone_test.dart index 863bef31..de4e58fc 100644 --- a/test/game/components/alien_zone_test.dart +++ b/test/game/components/alien_zone_test.dart @@ -13,13 +13,18 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(EmptyPinballTestGame.new); + final assets = [ + Assets.images.alienBumper.a.active.keyName, + Assets.images.alienBumper.a.inactive.keyName, + Assets.images.alienBumper.b.active.keyName, + Assets.images.alienBumper.b.inactive.keyName, + ]; + final flameTester = FlameTester(() => EmptyPinballTestGame(assets)); group('AlienZone', () { flameTester.test( 'loads correctly', (game) async { - await game.ready(); final alienZone = AlienZone(); await game.ensureAdd(alienZone); @@ -31,7 +36,6 @@ void main() { flameTester.test( 'two AlienBumper', (game) async { - await game.ready(); final alienZone = AlienZone(); await game.ensureAdd(alienZone); @@ -44,55 +48,40 @@ void main() { }); group('bumpers', () { - late ControlledAlienBumper controlledAlienBumper; late GameBloc gameBloc; setUp(() { gameBloc = MockGameBloc(); + whenListen( + gameBloc, + const Stream.empty(), + initialState: const GameState.initial(), + ); }); - final flameBlocTester = FlameBlocTester( + final flameBlocTester = FlameBlocTester( gameBuilder: EmptyPinballTestGame.new, blocBuilder: () => gameBloc, + assets: assets, ); - flameTester.testGameWidget( - 'activate when deactivated bumper is hit', - setUp: (game, tester) async { - controlledAlienBumper = ControlledAlienBumper.a(); - await game.ensureAdd(controlledAlienBumper); + flameTester.test('call animate on contact', (game) async { + final contactCallback = AlienBumperBallContactCallback(); + final bumper = MockAlienBumper(); + final ball = MockBall(); - controlledAlienBumper.controller.hit(); - }, - verify: (game, tester) async { - expect(controlledAlienBumper.controller.isActivated, isTrue); - }, - ); + when(bumper.animate).thenAnswer((_) async {}); - flameTester.testGameWidget( - 'deactivate when activated bumper is hit', - setUp: (game, tester) async { - controlledAlienBumper = ControlledAlienBumper.a(); - await game.ensureAdd(controlledAlienBumper); + contactCallback.begin(bumper, ball, MockContact()); - controlledAlienBumper.controller.hit(); - controlledAlienBumper.controller.hit(); - }, - verify: (game, tester) async { - expect(controlledAlienBumper.controller.isActivated, isFalse); - }, - ); + verify(bumper.animate).called(1); + }); flameBlocTester.testGameWidget( 'add Scored event', setUp: (game, tester) async { final ball = Ball(baseColor: const Color(0xFF00FFFF)); final alienZone = AlienZone(); - whenListen( - gameBloc, - const Stream.empty(), - initialState: const GameState.initial(), - ); await game.ensureAdd(alienZone); await game.ensureAdd(ball); diff --git a/test/game/components/board_test.dart b/test/game/components/board_test.dart index 0a1928ab..3ecbe6e7 100644 --- a/test/game/components/board_test.dart +++ b/test/game/components/board_test.dart @@ -9,7 +9,19 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(EmptyPinballTestGame.new); + final assets = [ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]; + final flameTester = FlameTester(() => EmptyPinballTestGame(assets)); group('Board', () { flameTester.test( diff --git a/test/game/components/flutter_forest_test.dart b/test/game/components/flutter_forest_test.dart index a318d342..73259afd 100644 --- a/test/game/components/flutter_forest_test.dart +++ b/test/game/components/flutter_forest_test.dart @@ -12,7 +12,19 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(EmptyPinballTestGame.new); + final assets = [ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + ]; + final flameTester = FlameTester(() => EmptyPinballTestGame(assets)); group('FlutterForest', () { flameTester.test( @@ -53,27 +65,14 @@ void main() { ); flameTester.test( - 'a BigDashNestBumper', + 'three DashNestBumper', (game) async { final flutterForest = FlutterForest(); await game.ensureAdd(flutterForest); expect( - flutterForest.descendants().whereType().length, - equals(1), - ); - }, - ); - - flameTester.test( - 'two SmallDashNestBumper', - (game) async { - final flutterForest = FlutterForest(); - await game.ensureAdd(flutterForest); - - expect( - flutterForest.descendants().whereType().length, - equals(2), + flutterForest.descendants().whereType().length, + equals(3), ); }, ); @@ -88,13 +87,14 @@ void main() { }); final flameBlocTester = FlameBlocTester( - gameBuilder: EmptyPinballTestGame.new, + gameBuilder: () => EmptyPinballTestGame(assets), blocBuilder: () { gameBloc = MockGameBloc(); const state = GameState.initial(); whenListen(gameBloc, Stream.value(state), initialState: state); return gameBloc; }, + assets: assets, ); flameBlocTester.testGameWidget( diff --git a/test/game/components/sparky_fire_zone_test.dart b/test/game/components/sparky_fire_zone_test.dart index 692af291..4c3fcdb9 100644 --- a/test/game/components/sparky_fire_zone_test.dart +++ b/test/game/components/sparky_fire_zone_test.dart @@ -13,13 +13,20 @@ import '../../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(EmptyPinballTestGame.new); + final assets = [ + Assets.images.sparky.bumper.a.active.keyName, + Assets.images.sparky.bumper.a.inactive.keyName, + Assets.images.sparky.bumper.b.active.keyName, + Assets.images.sparky.bumper.b.inactive.keyName, + Assets.images.sparky.bumper.c.active.keyName, + Assets.images.sparky.bumper.c.inactive.keyName, + ]; + final flameTester = FlameTester(() => EmptyPinballTestGame(assets)); group('SparkyFireZone', () { flameTester.test( 'loads correctly', (game) async { - await game.ready(); final sparkyFireZone = SparkyFireZone(); await game.ensureAdd(sparkyFireZone); @@ -31,7 +38,6 @@ void main() { flameTester.test( 'three SparkyBumper', (game) async { - await game.ready(); final sparkyFireZone = SparkyFireZone(); await game.ensureAdd(sparkyFireZone); @@ -44,12 +50,9 @@ void main() { }); group('bumpers', () { - late ControlledSparkyBumper controlledSparkyBumper; - late Ball ball; late GameBloc gameBloc; setUp(() { - ball = Ball(baseColor: const Color(0xFF00FFFF)); gameBloc = MockGameBloc(); whenListen( gameBloc, @@ -58,41 +61,28 @@ void main() { ); }); - final flameBlocTester = FlameBlocTester( + final flameBlocTester = FlameBlocTester( gameBuilder: EmptyPinballTestGame.new, blocBuilder: () => gameBloc, + assets: assets, ); - flameTester.testGameWidget( - 'activate when deactivated bumper is hit', - setUp: (game, tester) async { - controlledSparkyBumper = ControlledSparkyBumper.a(); - await game.ensureAdd(controlledSparkyBumper); + flameTester.test('call animate on contact', (game) async { + final contactCallback = SparkyBumperBallContactCallback(); + final bumper = MockSparkyBumper(); + final ball = MockBall(); - controlledSparkyBumper.controller.hit(); - }, - verify: (game, tester) async { - expect(controlledSparkyBumper.controller.isActivated, isTrue); - }, - ); + when(bumper.animate).thenAnswer((_) async {}); - flameTester.testGameWidget( - 'deactivate when activated bumper is hit', - setUp: (game, tester) async { - controlledSparkyBumper = ControlledSparkyBumper.a(); - await game.ensureAdd(controlledSparkyBumper); + contactCallback.begin(bumper, ball, MockContact()); - controlledSparkyBumper.controller.hit(); - controlledSparkyBumper.controller.hit(); - }, - verify: (game, tester) async { - expect(controlledSparkyBumper.controller.isActivated, isFalse); - }, - ); + verify(bumper.animate).called(1); + }); flameBlocTester.testGameWidget( 'add Scored event', setUp: (game, tester) async { + final ball = Ball(baseColor: const Color(0xFF00FFFF)); final sparkyFireZone = SparkyFireZone(); await game.ensureAdd(sparkyFireZone); await game.ensureAdd(ball); diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index c29ee315..2dbe3308 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -12,8 +12,30 @@ import '../helpers/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(PinballTestGame.new); - final debugModeFlameTester = FlameTester(DebugPinballTestGame.new); + final assets = [ + Assets.images.dash.bumper.main.active.keyName, + Assets.images.dash.bumper.main.inactive.keyName, + Assets.images.dash.bumper.a.active.keyName, + Assets.images.dash.bumper.a.inactive.keyName, + Assets.images.dash.bumper.b.active.keyName, + Assets.images.dash.bumper.b.inactive.keyName, + Assets.images.signpost.inactive.keyName, + Assets.images.signpost.active1.keyName, + Assets.images.signpost.active2.keyName, + Assets.images.signpost.active3.keyName, + Assets.images.alienBumper.a.active.keyName, + Assets.images.alienBumper.a.inactive.keyName, + Assets.images.alienBumper.b.active.keyName, + Assets.images.alienBumper.b.inactive.keyName, + Assets.images.sparky.bumper.a.active.keyName, + Assets.images.sparky.bumper.a.inactive.keyName, + Assets.images.sparky.bumper.b.active.keyName, + Assets.images.sparky.bumper.b.inactive.keyName, + Assets.images.sparky.bumper.c.active.keyName, + Assets.images.sparky.bumper.c.inactive.keyName, + ]; + final flameTester = FlameTester(() => PinballTestGame(assets)); + final debugModeFlameTester = FlameTester(() => DebugPinballTestGame(assets)); group('PinballGame', () { // TODO(alestiago): test if [PinballGame] registers @@ -90,6 +112,7 @@ void main() { final flameBlocTester = FlameBlocTester( gameBuilder: EmptyPinballTestGame.new, blocBuilder: () => gameBloc, + // assets: assets, ); flameBlocTester.testGameWidget( @@ -208,6 +231,7 @@ void main() { FlameBlocTester( gameBuilder: DebugPinballTestGame.new, blocBuilder: () => gameBloc, + assets: assets, ); debugModeFlameBlocTester.testGameWidget( diff --git a/test/game/view/pinball_game_page_test.dart b/test/game/view/pinball_game_page_test.dart index 59a0ef88..3c54197a 100644 --- a/test/game/view/pinball_game_page_test.dart +++ b/test/game/view/pinball_game_page_test.dart @@ -16,7 +16,8 @@ void main() { late ThemeCubit themeCubit; late GameBloc gameBloc; - setUp(() { + setUp(() async { + await Future.wait(game.preLoadAssets()); themeCubit = MockThemeCubit(); gameBloc = MockGameBloc(); diff --git a/test/helpers/builders.dart b/test/helpers/builders.dart index d0eea644..2c23e3fe 100644 --- a/test/helpers/builders.dart +++ b/test/helpers/builders.dart @@ -1,16 +1,23 @@ -import 'package:flame/src/game/flame_game.dart'; +import 'package:flame/game.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; class FlameBlocTester> extends FlameTester { FlameBlocTester({ required GameCreateFunction gameBuilder, required B Function() blocBuilder, + // TODO(allisonryan0002): find alternative for testGameWidget. Loading + // assets in onLoad fails because the game loads after + List? assets, List Function()? repositories, }) : super( gameBuilder, pumpWidget: (gameWidget, tester) async { + if (assets != null) { + await Future.wait(assets.map(gameWidget.game.images.load)); + } await tester.pumpWidget( BlocProvider.value( value: blocBuilder(), diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index fd01a509..e9af4408 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -82,3 +82,7 @@ class MockActiveOverlaysNotifier extends Mock implements ActiveOverlaysNotifier {} class MockGameFlowController extends Mock implements GameFlowController {} + +class MockAlienBumper extends Mock implements AlienBumper {} + +class MockSparkyBumper extends Mock implements SparkyBumper {} diff --git a/test/helpers/test_games.dart b/test/helpers/test_games.dart index 3747a231..10caa768 100644 --- a/test/helpers/test_games.dart +++ b/test/helpers/test_games.dart @@ -1,5 +1,7 @@ // ignore_for_file: must_call_super +import 'dart:async'; + import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball/game/game.dart'; @@ -14,26 +16,53 @@ class TestGame extends Forge2DGame with FlameBloc { } class PinballTestGame extends PinballGame { - PinballTestGame() - : super( + PinballTestGame([List? assets]) + : _assets = assets, + super( audio: MockPinballAudio(), theme: const PinballTheme( characterTheme: DashTheme(), ), ); + final List? _assets; + + @override + Future onLoad() async { + if (_assets != null) { + await images.loadAll(_assets!); + } + await super.onLoad(); + } } class DebugPinballTestGame extends DebugPinballGame { - DebugPinballTestGame() - : super( + DebugPinballTestGame([List? assets]) + : _assets = assets, + super( audio: MockPinballAudio(), theme: const PinballTheme( characterTheme: DashTheme(), ), ); + + final List? _assets; + + @override + Future onLoad() async { + if (_assets != null) { + await images.loadAll(_assets!); + } + await super.onLoad(); + } } class EmptyPinballTestGame extends PinballTestGame { + EmptyPinballTestGame([List? assets]) : super(assets); + @override - Future onLoad() async {} + Future onLoad() async { + if (_assets != null) { + await images.loadAll(_assets!); + } + } }