From f5c3c5fb56033e2bd013a4add349caf51e0edb0c Mon Sep 17 00:00:00 2001 From: Allison Ryan Date: Tue, 19 Apr 2022 16:42:44 -0500 Subject: [PATCH] refactor: bumpers to use group components --- lib/game/components/flutter_forest.dart | 19 +- .../lib/src/components/alien_bumper.dart | 119 ++++++----- .../lib/src/components/dash_nest_bumper.dart | 192 +++++++++--------- .../lib/src/components/sparky_bumper.dart | 129 ++++++------ 4 files changed, 236 insertions(+), 223 deletions(-) diff --git a/lib/game/components/flutter_forest.dart b/lib/game/components/flutter_forest.dart index 3c5f5a1f..02895ab4 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 = FlutterSignPost()..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/packages/pinball_components/lib/src/components/alien_bumper.dart b/packages/pinball_components/lib/src/components/alien_bumper.dart index 9165cc15..e350ef62 100644 --- a/packages/pinball_components/lib/src/components/alien_bumper.dart +++ b/packages/pinball_components/lib/src/components/alien_bumper.dart @@ -1,36 +1,40 @@ +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( + 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.on.keyName, + offAssetPath: Assets.images.alienBumper.a.off.keyName, ); /// {@macro alien_bumper} @@ -38,32 +42,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.on.keyName, + offAssetPath: Assets.images.alienBumper.a.off.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() { @@ -84,25 +68,54 @@ 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.off; + await Future.delayed(const Duration(milliseconds: 50)); + spriteGroupComponent?.current = AlienBumperSpriteState.on; } +} - /// Activates the [AlienBumper]. - void activate() { - _spriteComponent - ..sprite = _activeSprite - ..size = _activeSprite.originalSize / 10; - } +@visibleForTesting + +/// Indicates the [AlienBumper]'s current sprite state. +enum AlienBumperSpriteState { + /// The on sprite is being displayed. + on, + + /// The off sprite is being displayed. + off, +} + +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.on: Sprite(gameRef.images.fromCache(_onAssetPath)), + AlienBumperSpriteState.off: + Sprite(gameRef.images.fromCache(_offAssetPath)), + }; + + this.sprites = sprites; + size = sprites[AlienBumperSpriteState.on]!.originalSize / 10; - /// Deactivates the [AlienBumper]. - void deactivate() { - _spriteComponent - ..sprite = _inactiveSprite - ..size = _inactiveSprite.originalSize / 10; + current = AlienBumperSpriteState.on; } } 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 a16fa376..634c3308 100644 --- a/packages/pinball_components/lib/src/components/dash_nest_bumper.dart +++ b/packages/pinball_components/lib/src/components/dash_nest_bumper.dart @@ -2,138 +2,75 @@ 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; - - 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( + 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), - ), - ); - - @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, + spritePosition: Vector2(0, -0.3), ); /// {@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, @@ -141,4 +78,59 @@ 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; + } +} + +@visibleForTesting + +/// Indicates the [DashNestBumper]'s current sprite state. +enum DashNestBumperSpriteState { + /// The active sprite is being displayed. + active, + + /// The inactive sprite is being displayed. + 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; + size = sprites[DashNestBumperSpriteState.inactive]!.originalSize / 10; + + current = DashNestBumperSpriteState.inactive; + } } diff --git a/packages/pinball_components/lib/src/components/sparky_bumper.dart b/packages/pinball_components/lib/src/components/sparky_bumper.dart index 3022ed38..d8a98360 100644 --- a/packages/pinball_components/lib/src/components/sparky_bumper.dart +++ b/packages/pinball_components/lib/src/components/sparky_bumper.dart @@ -2,37 +2,42 @@ 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( + 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.on.keyName, + offAssetPath: Assets.images.sparky.bumper.a.off.keyName, + spritePosition: Vector2(0, -0.25), ); /// {@macro sparky_bumper} @@ -40,12 +45,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.on.keyName, + offAssetPath: Assets.images.sparky.bumper.b.off.keyName, + spritePosition: Vector2(0, -0.35), ); /// {@macro sparky_bumper} @@ -53,33 +55,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.on.keyName, + offAssetPath: Assets.images.sparky.bumper.c.off.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() { @@ -101,25 +83,54 @@ 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.off; + await Future.delayed(const Duration(milliseconds: 50)); + spriteGroupComponent?.current = SparkyBumperSpriteState.on; } +} - /// Activates the [DashNestBumper]. - void activate() { - _spriteComponent - ..sprite = _activeSprite - ..size = _activeSprite.originalSize / 10; - } +@visibleForTesting + +/// Indicates the [SparkyBumper]'s current sprite state. +enum SparkyBumperSpriteState { + /// The on sprite is being displayed. + on, + + /// The off sprite is being displayed. + off, +} + +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.on: + Sprite(gameRef.images.fromCache(_onAssetPath)), + SparkyBumperSpriteState.off: + Sprite(gameRef.images.fromCache(_offAssetPath)), + }; + this.sprites = sprites; + size = sprites[SparkyBumperSpriteState.on]!.originalSize / 10; - /// Deactivates the [DashNestBumper]. - void deactivate() { - _spriteComponent - ..sprite = _inactiveSprite - ..size = _inactiveSprite.originalSize / 10; + current = SparkyBumperSpriteState.on; } }