From 5019fe17019f4d2d3ba7eb00ab64062e745c6156 Mon Sep 17 00:00:00 2001 From: alestiago Date: Tue, 26 Apr 2022 10:59:35 +0100 Subject: [PATCH] feat: resolved missing ContactCallbacks with Behaviors --- .../{alien_zone => }/alien_zone.dart | 7 -- .../alien_bumper/behaviors/behaviors.dart | 2 - lib/game/components/components.dart | 4 +- lib/game/components/google_word.dart | 82 ------------------- .../google_word/behaviors/behaviors.dart | 1 + .../behaviors/bonus_behaviour.dart | 37 +++++++++ .../components/google_word/google_word.dart | 38 +++++++++ lib/game/components/sparky_fire_zone.dart | 15 ---- lib/game/pinball_game.dart | 10 +-- .../{ => alien_bumper}/alien_bumper.dart | 67 ++++----------- .../alien_bumper/behaviors/behaviors.dart | 2 + .../behaviors/contact_behavior.dart | 33 ++++++++ .../behaviors/sprite_behavior.dart | 9 +- .../cubit/alien_bumper_cubit.dart | 19 +++++ .../cubit/alien_bumper_state.dart | 10 +++ .../lib/src/components/components.dart | 8 +- .../google_letter/behaviors/behaviors.dart | 1 + .../behaviors/contact_behavior.dart | 33 ++++++++ .../cubit/google_letter_cubit.dart | 15 ++++ .../cubit/google_letter_state.dart | 7 ++ .../{ => google_letter}/google_letter.dart | 60 +++++++------- .../sparky_bumper/behaviors/behaviors.dart | 2 + .../behaviors/contact_behavior.dart | 6 +- .../behaviors/sprite_behavior.dart | 41 ++++++++++ .../cubit/sparky_bumper_cubit.dart | 19 +++++ .../cubit/sparky_bumper_state.dart | 10 +++ .../{ => sparky_bumper}/sparky_bumper.dart | 47 +++++------ .../sparky_computer.dart | 0 packages/pinball_components/pubspec.yaml | 4 +- 29 files changed, 354 insertions(+), 235 deletions(-) rename lib/game/components/{alien_zone => }/alien_zone.dart (72%) delete mode 100644 lib/game/components/alien_zone/alien_bumper/behaviors/behaviors.dart delete mode 100644 lib/game/components/google_word.dart create mode 100644 lib/game/components/google_word/behaviors/behaviors.dart create mode 100644 lib/game/components/google_word/behaviors/bonus_behaviour.dart create mode 100644 lib/game/components/google_word/google_word.dart rename packages/pinball_components/lib/src/components/{ => alien_bumper}/alien_bumper.dart (71%) create mode 100644 packages/pinball_components/lib/src/components/alien_bumper/behaviors/behaviors.dart create mode 100644 packages/pinball_components/lib/src/components/alien_bumper/behaviors/contact_behavior.dart rename lib/game/components/alien_zone/alien_bumper/behaviors/alien_bumper_sprite_behavior.dart => packages/pinball_components/lib/src/components/alien_bumper/behaviors/sprite_behavior.dart (78%) create mode 100644 packages/pinball_components/lib/src/components/alien_bumper/cubit/alien_bumper_cubit.dart create mode 100644 packages/pinball_components/lib/src/components/alien_bumper/cubit/alien_bumper_state.dart create mode 100644 packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart create mode 100644 packages/pinball_components/lib/src/components/google_letter/behaviors/contact_behavior.dart create mode 100644 packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart create mode 100644 packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart rename packages/pinball_components/lib/src/components/{ => google_letter}/google_letter.dart (62%) create mode 100644 packages/pinball_components/lib/src/components/sparky_bumper/behaviors/behaviors.dart rename lib/game/components/alien_zone/alien_bumper/behaviors/alien_bumper_contact_behavior.dart => packages/pinball_components/lib/src/components/sparky_bumper/behaviors/contact_behavior.dart (84%) create mode 100644 packages/pinball_components/lib/src/components/sparky_bumper/behaviors/sprite_behavior.dart create mode 100644 packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_cubit.dart create mode 100644 packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_state.dart rename packages/pinball_components/lib/src/components/{ => sparky_bumper}/sparky_bumper.dart (79%) rename packages/pinball_components/lib/src/components/{ => sparky_computer}/sparky_computer.dart (100%) diff --git a/lib/game/components/alien_zone/alien_zone.dart b/lib/game/components/alien_zone.dart similarity index 72% rename from lib/game/components/alien_zone/alien_zone.dart rename to lib/game/components/alien_zone.dart index eebc6cbf..13496c4f 100644 --- a/lib/game/components/alien_zone/alien_zone.dart +++ b/lib/game/components/alien_zone.dart @@ -1,7 +1,6 @@ // ignore_for_file: avoid_renaming_method_parameters import 'package:flame_forge2d/flame_forge2d.dart'; -import 'package:pinball/game/components/alien_zone/alien_bumper/behaviors/behaviors.dart'; import 'package:pinball/game/game.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; @@ -9,8 +8,6 @@ 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 an [AlienBumper], the bumper animates. /// {@endtemplate} class AlienZone extends Blueprint { /// {@macro alien_zone} @@ -20,15 +17,11 @@ class AlienZone extends Blueprint { AlienBumper.a( children: [ ScoringBehaviour(points: 20), - AlienBumperContactBehavior(), - AlienBumperSpriteBehavior(), ], )..initialPosition = Vector2(-32.52, -9.1), AlienBumper.b( children: [ ScoringBehaviour(points: 20), - AlienBumperContactBehavior(), - AlienBumperSpriteBehavior(), ], )..initialPosition = Vector2(-22.89, -17.35), ], diff --git a/lib/game/components/alien_zone/alien_bumper/behaviors/behaviors.dart b/lib/game/components/alien_zone/alien_bumper/behaviors/behaviors.dart deleted file mode 100644 index d17b3b0e..00000000 --- a/lib/game/components/alien_zone/alien_bumper/behaviors/behaviors.dart +++ /dev/null @@ -1,2 +0,0 @@ -export 'alien_bumper_contact_behavior.dart'; -export 'alien_bumper_sprite_behavior.dart'; diff --git a/lib/game/components/components.dart b/lib/game/components/components.dart index 64500501..6672eea7 100644 --- a/lib/game/components/components.dart +++ b/lib/game/components/components.dart @@ -1,4 +1,4 @@ -export 'alien_zone/alien_zone.dart'; +export 'alien_zone.dart'; export 'board.dart'; export 'camera_controller.dart'; export 'controlled_ball.dart'; @@ -6,7 +6,7 @@ export 'controlled_flipper.dart'; export 'controlled_plunger.dart'; export 'flutter_forest.dart'; export 'game_flow_controller.dart'; -export 'google_word.dart'; +export 'google_word/google_word.dart'; export 'launcher.dart'; export 'scoring_behaviour.dart'; export 'sparky_fire_zone.dart'; diff --git a/lib/game/components/google_word.dart b/lib/game/components/google_word.dart deleted file mode 100644 index fc8b9cac..00000000 --- a/lib/game/components/google_word.dart +++ /dev/null @@ -1,82 +0,0 @@ -// ignore_for_file: avoid_renaming_method_parameters - -import 'dart:async'; - -import 'package:flame/components.dart'; -import 'package:pinball/game/game.dart'; -import 'package:pinball_components/pinball_components.dart'; -import 'package:pinball_flame/pinball_flame.dart'; - -/// {@template google_word} -/// Loads all [GoogleLetter]s to compose a [GoogleWord]. -/// {@endtemplate} -class GoogleWord extends Component - with HasGameRef, Controls<_GoogleWordController> { - /// {@macro google_word} - GoogleWord({ - required Vector2 position, - }) : _position = position { - controller = _GoogleWordController(this); - } - - final Vector2 _position; - - @override - Future onLoad() async { - await super.onLoad(); - - final offsets = [ - Vector2(-12.92, 1.82), - Vector2(-8.33, -0.65), - Vector2(-2.88, -1.75), - Vector2(2.88, -1.75), - Vector2(8.33, -0.65), - Vector2(12.92, 1.82), - ]; - - final letters = []; - for (var index = 0; index < offsets.length; index++) { - letters.add( - GoogleLetter(index)..initialPosition = _position + offsets[index], - ); - } - - await addAll(letters); - } -} - -class _GoogleWordController extends ComponentController - with HasGameRef { - _GoogleWordController(GoogleWord googleWord) : super(googleWord); - - final _activatedLetters = {}; - - void activate(GoogleLetter googleLetter) { - if (!_activatedLetters.add(googleLetter)) return; - - googleLetter.activate(); - - final activatedBonus = _activatedLetters.length == 6; - if (activatedBonus) { - gameRef.audio.googleBonus(); - gameRef.read().add(const BonusActivated(GameBonus.googleWord)); - component.children.whereType().forEach( - (letter) => letter.deactivate(), - ); - _activatedLetters.clear(); - } - } -} - -/// Activates a [GoogleLetter] when it contacts with a [Ball]. -// TODO(alestiago): Add animation behaviour. -// class _GoogleLetterBallContactCallback -// extends ContactCallback { -// @override -// void begin(GoogleLetter googleLetter, _, __) { -// final parent = googleLetter.parent; -// if (parent is GoogleWord) { -// parent.controller.activate(googleLetter); -// } -// } -// } diff --git a/lib/game/components/google_word/behaviors/behaviors.dart b/lib/game/components/google_word/behaviors/behaviors.dart new file mode 100644 index 00000000..d7ab6146 --- /dev/null +++ b/lib/game/components/google_word/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'bonus_behaviour.dart'; diff --git a/lib/game/components/google_word/behaviors/bonus_behaviour.dart b/lib/game/components/google_word/behaviors/bonus_behaviour.dart new file mode 100644 index 00000000..9cab1d42 --- /dev/null +++ b/lib/game/components/google_word/behaviors/bonus_behaviour.dart @@ -0,0 +1,37 @@ +import 'dart:async'; + +import 'package:flame/components.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class BonusBehaviour extends Component + with HasGameRef, ParentIsA { + BonusBehaviour( + Iterable googleLetters, + ) : _googleLetters = googleLetters; + + Iterable _googleLetters; + + @override + Future onLoad() async { + await super.onLoad(); + + for (final letter in _googleLetters) { + letter.bloc.stream.listen((_) { + final achievedBonus = _googleLetters + .every((letter) => letter.bloc.state == GoogleLetterState.active); + + if (achievedBonus) { + gameRef.audio.googleBonus(); + gameRef + .read() + .add(const BonusActivated(GameBonus.googleWord)); + for (final letter in _googleLetters) { + letter.bloc.onReset(); + } + } + }); + } + } +} diff --git a/lib/game/components/google_word/google_word.dart b/lib/game/components/google_word/google_word.dart new file mode 100644 index 00000000..07ca845c --- /dev/null +++ b/lib/game/components/google_word/google_word.dart @@ -0,0 +1,38 @@ +// ignore_for_file: avoid_renaming_method_parameters + +import 'dart:async'; + +import 'package:flame/components.dart'; +import 'package:pinball/game/components/google_word/behaviors/behaviors.dart'; +import 'package:pinball/game/game.dart'; +import 'package:pinball_components/pinball_components.dart'; + +/// {@template google_word} +/// Loads all [GoogleLetter]s to compose a [GoogleWord]. +/// {@endtemplate} +class GoogleWord extends Component with HasGameRef { + /// {@macro google_word} + GoogleWord({ + required Vector2 position, + }) : _position = position; + + final Vector2 _position; + + @override + Future onLoad() async { + await super.onLoad(); + + final letters = [ + GoogleLetter(0)..initialPosition = _position + Vector2(-12.92, 1.82), + GoogleLetter(1)..initialPosition = _position + Vector2(-8.33, -0.65), + GoogleLetter(2)..initialPosition = _position + Vector2(-2.88, -1.75), + GoogleLetter(3)..initialPosition = _position + Vector2(2.88, -1.75), + GoogleLetter(4)..initialPosition = _position + Vector2(8.33, -0.65), + GoogleLetter(5)..initialPosition = _position + Vector2(12.92, 1.82) + ]; + await addAll([ + ...letters, + BonusBehaviour(letters), + ]); + } +} diff --git a/lib/game/components/sparky_fire_zone.dart b/lib/game/components/sparky_fire_zone.dart index a4c88e0f..07cc2003 100644 --- a/lib/game/components/sparky_fire_zone.dart +++ b/lib/game/components/sparky_fire_zone.dart @@ -40,21 +40,6 @@ class SparkyFireZone extends Blueprint { ); } -/// Listens when a [Ball] bounces bounces against a [SparkyBumper]. -// TODO(alestiago): Add animation behaviour. -// @visibleForTesting -// class SparkyBumperBallContactCallback -// extends ContactCallback { -// @override -// void begin( -// SparkyBumper sparkyBumper, -// Ball _, -// Contact __, -// ) { -// sparkyBumper.animate(); -// } -// } - /// {@template sparky_computer_sensor} /// Small sensor body used to detect when a ball has entered the /// [SparkyComputer]. diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index ea9d904f..1d483fe2 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -58,7 +58,6 @@ class PinballGame extends Forge2DGame await addFromBlueprint(SparkyFireZone()); unawaited(addFromBlueprint(Slingshots())); unawaited(addFromBlueprint(DinoWalls())); - unawaited(_addBonusWord()); unawaited(addFromBlueprint(SpaceshipRamp())); unawaited( addFromBlueprint( @@ -68,12 +67,6 @@ class PinballGame extends Forge2DGame ), ); unawaited(addFromBlueprint(SpaceshipRail())); - - controller.attachTo(launcher.components.whereType().first); - await super.onLoad(); - } - - Future _addBonusWord() async { await add( GoogleWord( position: Vector2( @@ -82,6 +75,9 @@ class PinballGame extends Forge2DGame ), ), ); + + controller.attachTo(launcher.components.whereType().first); + await super.onLoad(); } } diff --git a/packages/pinball_components/lib/src/components/alien_bumper.dart b/packages/pinball_components/lib/src/components/alien_bumper/alien_bumper.dart similarity index 71% rename from packages/pinball_components/lib/src/components/alien_bumper.dart rename to packages/pinball_components/lib/src/components/alien_bumper/alien_bumper.dart index 9555fd43..2be0b71b 100644 --- a/packages/pinball_components/lib/src/components/alien_bumper.dart +++ b/packages/pinball_components/lib/src/components/alien_bumper/alien_bumper.dart @@ -3,43 +3,15 @@ import 'dart:async'; import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/alien_bumper/behaviors/behaviors.dart'; import 'package:pinball_flame/pinball_flame.dart'; -abstract class State { - // TODO(alestiago): Investigate approaches to avoid having this as late. - late T _state; - - T get state => _state; - - set state(T value) { - if (value == _state) return; - _state = value; - _stream.sink.add(value); - } - - final StreamController _stream = StreamController.broadcast(); - - Stream get stream => _stream.stream; - - void close() { - _stream.close(); - } -} - -/// Indicates the [AlienBumper]'s current sprite state. -enum AlienBumperState { - /// A lit up bumper. - active, - - /// A dimmed bumper. - inactive, -} +export 'cubit/alien_bumper_cubit.dart'; /// {@template alien_bumper} /// Bumper for area under the [Spaceship]. /// {@endtemplate} -class AlienBumper extends BodyComponent - with InitialPosition, State { +class AlienBumper extends BodyComponent with InitialPosition { /// {@macro alien_bumper} AlienBumper._({ required double majorRadius, @@ -47,22 +19,23 @@ class AlienBumper extends BodyComponent required String onAssetPath, required String offAssetPath, Iterable? children, + required this.bloc, }) : _majorRadius = majorRadius, _minorRadius = minorRadius, super( priority: RenderPriority.alienBumper, children: [ + ContactBehavior(), + SpriteBehavior(), + _AlienBumperSpriteGroupComponent( + offAssetPath: offAssetPath, + onAssetPath: onAssetPath, + state: bloc.state, + ), if (children != null) ...children, ], renderBody: false, - ) { - _state = AlienBumperState.active; - _spriteGroupComponent = _AlienBumperSpriteGroupComponent( - offAssetPath: offAssetPath, - onAssetPath: onAssetPath, - state: state, - ); - } + ); /// {@macro alien_bumper} AlienBumper.a({ @@ -72,6 +45,7 @@ class AlienBumper extends BodyComponent minorRadius: 2.97, onAssetPath: Assets.images.alienBumper.a.active.keyName, offAssetPath: Assets.images.alienBumper.a.inactive.keyName, + bloc: AlienBumperCubit(), children: children, ); @@ -83,6 +57,7 @@ class AlienBumper extends BodyComponent minorRadius: 2.79, onAssetPath: Assets.images.alienBumper.b.active.keyName, offAssetPath: Assets.images.alienBumper.b.inactive.keyName, + bloc: AlienBumperCubit(), children: children, ); @@ -90,17 +65,12 @@ class AlienBumper extends BodyComponent final double _minorRadius; - late final _AlienBumperSpriteGroupComponent _spriteGroupComponent; - - @override - Future onLoad() async { - await super.onLoad(); - await add(_spriteGroupComponent); - } + // TODO(alestiago): Evaluate testing this. + final AlienBumperCubit bloc; @override void onRemove() { - close(); + bloc.close(); super.onRemove(); } @@ -144,6 +114,7 @@ class _AlienBumperSpriteGroupComponent @override Future onLoad() async { await super.onLoad(); + parent.bloc.stream.listen((state) => current = state); final sprites = { AlienBumperState.active: Sprite( @@ -154,7 +125,5 @@ class _AlienBumperSpriteGroupComponent }; this.sprites = sprites; size = sprites[current]!.originalSize / 10; - - parent.stream.listen((state) => current = state); } } diff --git a/packages/pinball_components/lib/src/components/alien_bumper/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/alien_bumper/behaviors/behaviors.dart new file mode 100644 index 00000000..cfedb092 --- /dev/null +++ b/packages/pinball_components/lib/src/components/alien_bumper/behaviors/behaviors.dart @@ -0,0 +1,2 @@ +export 'contact_behavior.dart'; +export 'sprite_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/alien_bumper/behaviors/contact_behavior.dart b/packages/pinball_components/lib/src/components/alien_bumper/behaviors/contact_behavior.dart new file mode 100644 index 00000000..3396284e --- /dev/null +++ b/packages/pinball_components/lib/src/components/alien_bumper/behaviors/contact_behavior.dart @@ -0,0 +1,33 @@ +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class ContactBehavior extends Component + with ContactCallbacks, ParentIsA { + @override + Future onLoad() async { + await super.onLoad(); + + // TODO(alestiago): Consider defining a generic ContactBehaviour to get + // rid of this repeated logic. + final userData = parent.body.userData; + if (userData is ContactCallbacksGroup) { + userData.addContactCallbacks(this); + } else if (userData is ContactCallbacks) { + final notifier = ContactCallbacksGroup() + ..addContactCallbacks(userData) + ..addContactCallbacks(this); + parent.body.userData = notifier; + } else { + parent.body.userData = this; + } + } + + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + if (other is! Ball) return; + parent.bloc.onBallContacted(); + } +} diff --git a/lib/game/components/alien_zone/alien_bumper/behaviors/alien_bumper_sprite_behavior.dart b/packages/pinball_components/lib/src/components/alien_bumper/behaviors/sprite_behavior.dart similarity index 78% rename from lib/game/components/alien_zone/alien_bumper/behaviors/alien_bumper_sprite_behavior.dart rename to packages/pinball_components/lib/src/components/alien_bumper/behaviors/sprite_behavior.dart index 0e2068d1..48e570b6 100644 --- a/lib/game/components/alien_zone/alien_bumper/behaviors/alien_bumper_sprite_behavior.dart +++ b/packages/pinball_components/lib/src/components/alien_bumper/behaviors/sprite_behavior.dart @@ -5,10 +5,9 @@ import 'package:pinball_flame/pinball_flame.dart'; /// {@template alien_bumper_sprite_behavior} /// /// {@endtemplate} -class AlienBumperSpriteBehavior extends TimerComponent - with ParentIsA { +class SpriteBehavior extends TimerComponent with ParentIsA { /// {@macro alien_bumper_sprite_behavior} - AlienBumperSpriteBehavior() + SpriteBehavior() : super( period: 0.05, removeOnFinish: false, @@ -31,12 +30,12 @@ class AlienBumperSpriteBehavior extends TimerComponent Future onLoad() async { await super.onLoad(); timer.stop(); - parent.stream.listen(_onNewState); + parent.bloc.stream.listen(_onNewState); } @override void onTick() { super.onTick(); - parent.state = AlienBumperState.active; + parent.bloc.onAnimated(); } } diff --git a/packages/pinball_components/lib/src/components/alien_bumper/cubit/alien_bumper_cubit.dart b/packages/pinball_components/lib/src/components/alien_bumper/cubit/alien_bumper_cubit.dart new file mode 100644 index 00000000..3395117f --- /dev/null +++ b/packages/pinball_components/lib/src/components/alien_bumper/cubit/alien_bumper_cubit.dart @@ -0,0 +1,19 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'alien_bumper_state.dart'; + +class AlienBumperCubit extends Cubit { + AlienBumperCubit() : super(AlienBumperState.active); + + void onBallContacted() { + emit(AlienBumperState.inactive); + // Future.delayed(const Duration(milliseconds: 500)).whenComplete( + // () => emit(AlienBumperState.active), + // ); + } + + void onAnimated() { + emit(AlienBumperState.active); + } +} diff --git a/packages/pinball_components/lib/src/components/alien_bumper/cubit/alien_bumper_state.dart b/packages/pinball_components/lib/src/components/alien_bumper/cubit/alien_bumper_state.dart new file mode 100644 index 00000000..cbec959e --- /dev/null +++ b/packages/pinball_components/lib/src/components/alien_bumper/cubit/alien_bumper_state.dart @@ -0,0 +1,10 @@ +part of 'alien_bumper_cubit.dart'; + +/// Indicates the [AlienBumperCubit]'s current state. +enum AlienBumperState { + /// A lit up bumper. + active, + + /// A dimmed bumper. + inactive, +} diff --git a/packages/pinball_components/lib/src/components/components.dart b/packages/pinball_components/lib/src/components/components.dart index 57e93abb..143047e4 100644 --- a/packages/pinball_components/lib/src/components/components.dart +++ b/packages/pinball_components/lib/src/components/components.dart @@ -1,4 +1,4 @@ -export 'alien_bumper.dart'; +export 'alien_bumper/alien_bumper.dart'; export 'backboard/backboard.dart'; export 'ball.dart'; export 'baseboard.dart'; @@ -12,7 +12,7 @@ export 'dash_nest_bumper.dart'; export 'dino_walls.dart'; export 'fire_effect.dart'; export 'flipper.dart'; -export 'google_letter.dart'; +export 'google_letter/google_letter.dart'; export 'initial_position.dart'; export 'joint_anchor.dart'; export 'kicker.dart'; @@ -30,5 +30,5 @@ export 'spaceship.dart'; export 'spaceship_rail.dart'; export 'spaceship_ramp.dart'; export 'sparky_animatronic.dart'; -export 'sparky_bumper.dart'; -export 'sparky_computer.dart'; +export 'sparky_bumper/sparky_bumper.dart'; +export 'sparky_computer/sparky_computer.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart new file mode 100644 index 00000000..57638e17 --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_letter/behaviors/behaviors.dart @@ -0,0 +1 @@ +export 'contact_behavior.dart'; diff --git a/packages/pinball_components/lib/src/components/google_letter/behaviors/contact_behavior.dart b/packages/pinball_components/lib/src/components/google_letter/behaviors/contact_behavior.dart new file mode 100644 index 00000000..c5193a95 --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_letter/behaviors/contact_behavior.dart @@ -0,0 +1,33 @@ +import 'package:flame/components.dart'; +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +class ContactBehavior extends Component + with ContactCallbacks, ParentIsA { + @override + Future onLoad() async { + await super.onLoad(); + + // TODO(alestiago): Consider defining a generic ContactBehaviour to get + // rid of this repeated logic. + final userData = parent.body.userData; + if (userData is ContactCallbacksGroup) { + userData.addContactCallbacks(this); + } else if (userData is ContactCallbacks) { + final notifier = ContactCallbacksGroup() + ..addContactCallbacks(userData) + ..addContactCallbacks(this); + parent.body.userData = notifier; + } else { + parent.body.userData = this; + } + } + + @override + void beginContact(Object other, Contact contact) { + super.beginContact(other, contact); + if (other is! Ball) return; + parent.bloc.onBallContacted(); + } +} diff --git a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart new file mode 100644 index 00000000..10ac0319 --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_cubit.dart @@ -0,0 +1,15 @@ +import 'package:bloc/bloc.dart'; + +part 'google_letter_state.dart'; + +class GoogleLetterCubit extends Cubit { + GoogleLetterCubit() : super(GoogleLetterState.inactive); + + void onBallContacted() { + emit(GoogleLetterState.active); + } + + void onReset() { + emit(GoogleLetterState.inactive); + } +} diff --git a/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart new file mode 100644 index 00000000..9758bc0e --- /dev/null +++ b/packages/pinball_components/lib/src/components/google_letter/cubit/google_letter_state.dart @@ -0,0 +1,7 @@ +part of 'google_letter_cubit.dart'; + +enum GoogleLetterState { + active, + + inactive, +} diff --git a/packages/pinball_components/lib/src/components/google_letter.dart b/packages/pinball_components/lib/src/components/google_letter/google_letter.dart similarity index 62% rename from packages/pinball_components/lib/src/components/google_letter.dart rename to packages/pinball_components/lib/src/components/google_letter/google_letter.dart index 43a4c113..70233140 100644 --- a/packages/pinball_components/lib/src/components/google_letter.dart +++ b/packages/pinball_components/lib/src/components/google_letter/google_letter.dart @@ -3,6 +3,10 @@ import 'package:flame/effects.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_components/src/components/google_letter/behaviors/behaviors.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +export 'cubit/google_letter_cubit.dart'; /// {@template google_letter} /// Circular sensor that represents a letter in "GOOGLE" for a given index. @@ -10,25 +14,15 @@ import 'package:pinball_components/pinball_components.dart'; class GoogleLetter extends BodyComponent with InitialPosition { /// {@macro google_letter} GoogleLetter(int index) - : _sprite = _GoogleLetterSprite( - _GoogleLetterSprite.spritePaths[index], + : super( + children: [ + ContactBehavior(), + _GoogleLetterSprite(_GoogleLetterSprite.spritePaths[index]) + ], ); - final _GoogleLetterSprite _sprite; - - /// Activates this [GoogleLetter]. - // TODO(alestiago): Improve doc comment once activate and deactivate - // are implemented with the actual assets. - Future activate() => _sprite.activate(); - - /// Deactivates this [GoogleLetter]. - Future deactivate() => _sprite.deactivate(); - - @override - Future onLoad() async { - await super.onLoad(); - await add(_sprite); - } + // TODO(alestiago): Evaluate testing this. + final GoogleLetterCubit bloc = GoogleLetterCubit(); @override Body createBody() { @@ -46,8 +40,11 @@ class GoogleLetter extends BodyComponent with InitialPosition { } } -class _GoogleLetterSprite extends SpriteComponent with HasGameRef { - _GoogleLetterSprite(String path) : _path = path; +class _GoogleLetterSprite extends SpriteComponent + with HasGameRef, ParentIsA { + _GoogleLetterSprite(String path) + : _path = path, + super(anchor: Anchor.center); static final spritePaths = [ Assets.images.googleWord.letter1.keyName, @@ -60,30 +57,29 @@ class _GoogleLetterSprite extends SpriteComponent with HasGameRef { final String _path; - // TODO(alestiago): Correctly implement activate and deactivate once the - // assets are provided. - Future activate() async { - await add( - _GoogleLetterColorEffect(color: Colors.green), - ); - } - - Future deactivate() async { - await add( - _GoogleLetterColorEffect(color: Colors.red), - ); + void _onNewState(GoogleLetterState state) { + switch (state) { + case GoogleLetterState.active: + add(_GoogleLetterColorEffect(color: Colors.green)); + break; + case GoogleLetterState.inactive: + add( + _GoogleLetterColorEffect(color: Colors.red), + ); + break; + } } @override Future onLoad() async { await super.onLoad(); + parent.bloc.stream.listen(_onNewState); // TODO(alestiago): Used cached assets. final sprite = await gameRef.loadSprite(_path); this.sprite = sprite; // TODO(alestiago): Size correctly once the assets are provided. size = sprite.originalSize / 5; - anchor = Anchor.center; } } diff --git a/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/behaviors.dart b/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/behaviors.dart new file mode 100644 index 00000000..cfedb092 --- /dev/null +++ b/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/behaviors.dart @@ -0,0 +1,2 @@ +export 'contact_behavior.dart'; +export 'sprite_behavior.dart'; diff --git a/lib/game/components/alien_zone/alien_bumper/behaviors/alien_bumper_contact_behavior.dart b/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/contact_behavior.dart similarity index 84% rename from lib/game/components/alien_zone/alien_bumper/behaviors/alien_bumper_contact_behavior.dart rename to packages/pinball_components/lib/src/components/sparky_bumper/behaviors/contact_behavior.dart index f01d45c7..7fa05529 100644 --- a/lib/game/components/alien_zone/alien_bumper/behaviors/alien_bumper_contact_behavior.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/contact_behavior.dart @@ -3,8 +3,8 @@ import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_flame/pinball_flame.dart'; -class AlienBumperContactBehavior extends Component - with ContactCallbacks, ParentIsA { +class ContactBehavior extends Component + with ContactCallbacks, ParentIsA { @override Future onLoad() async { await super.onLoad(); @@ -26,6 +26,6 @@ class AlienBumperContactBehavior extends Component void beginContact(Object other, Contact contact) { super.beginContact(other, contact); if (other is! Ball) return; - parent.state = AlienBumperState.inactive; + parent.bloc.onBallContacted(); } } diff --git a/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/sprite_behavior.dart b/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/sprite_behavior.dart new file mode 100644 index 00000000..58fff92c --- /dev/null +++ b/packages/pinball_components/lib/src/components/sparky_bumper/behaviors/sprite_behavior.dart @@ -0,0 +1,41 @@ +import 'package:flame/components.dart'; +import 'package:pinball_components/pinball_components.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +/// {@template alien_bumper_sprite_behavior} +/// +/// {@endtemplate} +class SpriteBehavior extends TimerComponent with ParentIsA { + /// {@macro alien_bumper_sprite_behavior} + SpriteBehavior() + : super( + period: 0.05, + removeOnFinish: false, + ); + + void _onNewState(SparkyBumperState state) { + switch (state) { + case SparkyBumperState.active: + timer.stop(); + break; + case SparkyBumperState.inactive: + timer + ..reset() + ..start(); + break; + } + } + + @override + Future onLoad() async { + await super.onLoad(); + timer.stop(); + parent.bloc.stream.listen(_onNewState); + } + + @override + void onTick() { + super.onTick(); + parent.bloc.onAnimated(); + } +} diff --git a/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_cubit.dart b/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_cubit.dart new file mode 100644 index 00000000..d8e8601d --- /dev/null +++ b/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_cubit.dart @@ -0,0 +1,19 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'sparky_bumper_state.dart'; + +class SparkyBumperCubit extends Cubit { + SparkyBumperCubit() : super(SparkyBumperState.active); + + void onBallContacted() { + emit(SparkyBumperState.inactive); + // Future.delayed(const Duration(milliseconds: 500)).whenComplete( + // () => emit(AlienBumperState.active), + // ); + } + + void onAnimated() { + emit(SparkyBumperState.active); + } +} diff --git a/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_state.dart b/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_state.dart new file mode 100644 index 00000000..35cc5ffa --- /dev/null +++ b/packages/pinball_components/lib/src/components/sparky_bumper/cubit/sparky_bumper_state.dart @@ -0,0 +1,10 @@ +part of 'sparky_bumper_cubit.dart'; + +/// Indicates the [SparkyBumperCubit]'s current state. +enum SparkyBumperState { + /// A lit up bumper. + active, + + /// A dimmed bumper. + inactive, +} diff --git a/packages/pinball_components/lib/src/components/sparky_bumper.dart b/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart similarity index 79% rename from packages/pinball_components/lib/src/components/sparky_bumper.dart rename to packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart index 7ef98541..6592033a 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper/sparky_bumper.dart @@ -2,8 +2,11 @@ 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'; +import 'package:pinball_components/src/components/sparky_bumper/behaviors/behaviors.dart'; +import 'package:pinball_flame/pinball_flame.dart'; + +export 'cubit/sparky_bumper_cubit.dart'; /// {@template sparky_bumper} /// Bumper for Sparky area. @@ -16,16 +19,20 @@ class SparkyBumper extends BodyComponent with InitialPosition { required String onAssetPath, required String offAssetPath, required Vector2 spritePosition, + required this.bloc, Iterable? children, }) : _majorRadius = majorRadius, _minorRadius = minorRadius, super( priority: RenderPriority.sparkyBumper, children: [ + ContactBehavior(), + SpriteBehavior(), _SparkyBumperSpriteGroupComponent( onAssetPath: onAssetPath, offAssetPath: offAssetPath, position: spritePosition, + state: bloc.state, ), if (children != null) ...children, ], @@ -41,6 +48,7 @@ class SparkyBumper extends BodyComponent with InitialPosition { onAssetPath: Assets.images.sparky.bumper.a.active.keyName, offAssetPath: Assets.images.sparky.bumper.a.inactive.keyName, spritePosition: Vector2(0, -0.25), + bloc: SparkyBumperCubit(), children: children, ); @@ -53,6 +61,7 @@ class SparkyBumper extends BodyComponent with InitialPosition { onAssetPath: Assets.images.sparky.bumper.b.active.keyName, offAssetPath: Assets.images.sparky.bumper.b.inactive.keyName, spritePosition: Vector2(0, -0.35), + bloc: SparkyBumperCubit(), children: children, ); @@ -65,12 +74,16 @@ class SparkyBumper extends BodyComponent with InitialPosition { onAssetPath: Assets.images.sparky.bumper.c.active.keyName, offAssetPath: Assets.images.sparky.bumper.c.inactive.keyName, spritePosition: Vector2(0, -0.4), + bloc: SparkyBumperCubit(), children: children, ); final double _majorRadius; final double _minorRadius; + // TODO(alestiago): Evaluate testing this. + final SparkyBumperCubit bloc; + @override Body createBody() { final shape = EllipseShape( @@ -88,37 +101,22 @@ class SparkyBumper extends BodyComponent with InitialPosition { return world.createBody(bodyDef)..createFixture(fixtureDef); } - - /// Animates the [DashNestBumper]. - Future animate() async { - final spriteGroupComponent = firstChild<_SparkyBumperSpriteGroupComponent>() - ?..current = SparkyBumperSpriteState.inactive; - await Future.delayed(const Duration(milliseconds: 50)); - spriteGroupComponent?.current = SparkyBumperSpriteState.active; - } -} - -/// 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 { + extends SpriteGroupComponent + with HasGameRef, ParentIsA { _SparkyBumperSpriteGroupComponent({ required String onAssetPath, required String offAssetPath, required Vector2 position, + required SparkyBumperState state, }) : _onAssetPath = onAssetPath, _offAssetPath = offAssetPath, super( anchor: Anchor.center, position: position, + current: state, ); final String _onAssetPath; @@ -127,15 +125,14 @@ class _SparkyBumperSpriteGroupComponent @override Future onLoad() async { await super.onLoad(); + parent.bloc.stream.listen((state) => current = state); + final sprites = { - SparkyBumperSpriteState.active: - Sprite(gameRef.images.fromCache(_onAssetPath)), - SparkyBumperSpriteState.inactive: + SparkyBumperState.active: Sprite(gameRef.images.fromCache(_onAssetPath)), + SparkyBumperState.inactive: Sprite(gameRef.images.fromCache(_offAssetPath)), }; this.sprites = sprites; - - current = SparkyBumperSpriteState.active; size = sprites[current]!.originalSize / 10; } } diff --git a/packages/pinball_components/lib/src/components/sparky_computer.dart b/packages/pinball_components/lib/src/components/sparky_computer/sparky_computer.dart similarity index 100% rename from packages/pinball_components/lib/src/components/sparky_computer.dart rename to packages/pinball_components/lib/src/components/sparky_computer/sparky_computer.dart diff --git a/packages/pinball_components/pubspec.yaml b/packages/pinball_components/pubspec.yaml index 6a9910b1..3821f280 100644 --- a/packages/pinball_components/pubspec.yaml +++ b/packages/pinball_components/pubspec.yaml @@ -7,9 +7,10 @@ environment: sdk: ">=2.16.0 <3.0.0" dependencies: + bloc: ^8.0.3 flame: ^1.1.1 flame_forge2d: - git: + git: url: https://github.com/flame-engine/flame/ path: packages/flame_forge2d/ ref: a50d4a1e7d9eaf66726ed1bb9894c9d495547d8f @@ -23,7 +24,6 @@ dependencies: pinball_theme: path: ../pinball_theme - dev_dependencies: flame_test: ^1.3.0 flutter_test: