diff --git a/lib/game/components/controlled_sparky_computer.dart b/lib/game/components/controlled_sparky_computer.dart index 6a8e9e59..7a5dc549 100644 --- a/lib/game/components/controlled_sparky_computer.dart +++ b/lib/game/components/controlled_sparky_computer.dart @@ -1,6 +1,5 @@ // ignore_for_file: avoid_renaming_method_parameters -import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; import 'package:pinball/game/game.dart'; @@ -11,20 +10,16 @@ import 'package:pinball_flame/pinball_flame.dart'; /// [SparkyComputer] with a [SparkyComputerController] attached. /// {@endtemplate} class ControlledSparkyComputer extends SparkyComputer - with Controls, HasGameRef { + with Controls { /// {@macro controlled_sparky_computer} - ControlledSparkyComputer() { + ControlledSparkyComputer() + : super( + components: [ + SparkyTurboChargeSensor()..initialPosition = Vector2(-13, -49.8) + ], + ) { controller = SparkyComputerController(this); } - - @override - void build(Forge2DGame _) { - addContactCallback(SparkyTurboChargeSensorBallContactCallback()); - final sparkyTurboChargeSensor = SparkyTurboChargeSensor() - ..initialPosition = Vector2(-13, -49.8); - add(sparkyTurboChargeSensor); - super.build(_); - } } /// {@template sparky_computer_controller} @@ -62,6 +57,12 @@ class SparkyTurboChargeSensor extends BodyComponent with InitialPosition { return world.createBody(bodyDef)..createFixture(fixtureDef); } + + @override + Future onLoad() async { + await super.onLoad(); + gameRef.addContactCallback(SparkyTurboChargeSensorBallContactCallback()); + } } /// {@template sparky_turbo_charge_sensor_ball_contact_callback} diff --git a/lib/game/components/launcher.dart b/lib/game/components/launcher.dart index d2e69174..7aef09d2 100644 --- a/lib/game/components/launcher.dart +++ b/lib/game/components/launcher.dart @@ -7,21 +7,15 @@ import 'package:pinball_flame/pinball_flame.dart'; /// A [Blueprint] which creates the [Plunger], [RocketSpriteComponent] and /// [LaunchRamp]. /// {@endtemplate} -class Launcher extends Forge2DBlueprint { +class Launcher extends Blueprint { /// {@macro launcher} - Launcher(); - - /// [Plunger] to launch the [Ball] onto the board. - late final Plunger plunger; - - @override - void build(Forge2DGame gameRef) { - plunger = ControlledPlunger(compressionDistance: 14) - ..initialPosition = Vector2(40.7, 38); - - final _rocket = RocketSpriteComponent()..position = Vector2(43, 62); - - addAll([_rocket, plunger]); - addBlueprint(LaunchRamp()); - } + Launcher() + : super( + components: [ + ControlledPlunger(compressionDistance: 14) + ..initialPosition = Vector2(40.7, 38), + RocketSpriteComponent()..position = Vector2(43, 62), + ], + blueprints: [LaunchRamp()], + ); } diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index f28a0bf6..3ba4185a 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -55,6 +55,7 @@ class PinballGame extends Forge2DGame unawaited(addFromBlueprint(launcher)); unawaited(add(Board())); unawaited(add(AlienZone())); + unawaited(add(SparkyFireZone())); unawaited(addFromBlueprint(Slingshots())); unawaited(addFromBlueprint(DinoWalls())); @@ -69,7 +70,7 @@ class PinballGame extends Forge2DGame ); unawaited(addFromBlueprint(SpaceshipRail())); - controller.attachTo(launcher.plunger); + controller.attachTo(launcher.components.whereType().first); await super.onLoad(); } diff --git a/packages/pinball_components/lib/src/components/boundaries.dart b/packages/pinball_components/lib/src/components/boundaries.dart index 8c59d598..f4f8c495 100644 --- a/packages/pinball_components/lib/src/components/boundaries.dart +++ b/packages/pinball_components/lib/src/components/boundaries.dart @@ -1,5 +1,3 @@ -// ignore_for_file: avoid_renaming_method_parameters - import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -8,14 +6,15 @@ import 'package:pinball_flame/pinball_flame.dart'; /// {@template boundaries} /// A [Blueprint] which creates the [_BottomBoundary] and [_OuterBoundary]. ///{@endtemplate boundaries} -class Boundaries extends Forge2DBlueprint { - @override - void build(_) { - final bottomBoundary = _BottomBoundary(); - final outerBoundary = _OuterBoundary(); - - addAll([outerBoundary, bottomBoundary]); - } +class Boundaries extends Blueprint { + /// {@macro boundaries} + Boundaries() + : super( + components: [ + _BottomBoundary(), + _OuterBoundary(), + ], + ); } /// {@template bottom_boundary} diff --git a/packages/pinball_components/lib/src/components/dino_walls.dart b/packages/pinball_components/lib/src/components/dino_walls.dart index 139e391b..9ce5a523 100644 --- a/packages/pinball_components/lib/src/components/dino_walls.dart +++ b/packages/pinball_components/lib/src/components/dino_walls.dart @@ -1,5 +1,3 @@ -// ignore_for_file: comment_references, avoid_renaming_method_parameters - import 'dart:async'; import 'package:flame/components.dart'; @@ -11,17 +9,15 @@ import 'package:pinball_flame/pinball_flame.dart'; /// {@template dinowalls} /// A [Blueprint] which creates walls for the [ChromeDino]. /// {@endtemplate} -class DinoWalls extends Forge2DBlueprint { +class DinoWalls extends Blueprint { /// {@macro dinowalls} - DinoWalls(); - - @override - void build(_) { - addAll([ - _DinoTopWall(), - _DinoBottomWall(), - ]); - } + DinoWalls() + : super( + components: [ + _DinoTopWall(), + _DinoBottomWall(), + ], + ); } /// {@template dino_top_wall} diff --git a/packages/pinball_components/lib/src/components/launch_ramp.dart b/packages/pinball_components/lib/src/components/launch_ramp.dart index 2ac9ee91..7f51e3cf 100644 --- a/packages/pinball_components/lib/src/components/launch_ramp.dart +++ b/packages/pinball_components/lib/src/components/launch_ramp.dart @@ -11,30 +11,18 @@ import 'package:pinball_flame/pinball_flame.dart'; /// A [Blueprint] which creates the [_LaunchRampBase] and /// [_LaunchRampForegroundRailing]. /// {@endtemplate} -class LaunchRamp extends Forge2DBlueprint { - @override - void build(_) { - addAllContactCallback([ - LayerSensorBallContactCallback<_LaunchRampExit>(), - ]); - - final launchRampBase = _LaunchRampBase(); - - final launchRampForegroundRailing = _LaunchRampForegroundRailing(); - - final launchRampExit = _LaunchRampExit(rotation: math.pi / 2) - ..initialPosition = Vector2(0.6, -34); - - final launchRampCloseWall = _LaunchRampCloseWall() - ..initialPosition = Vector2(4, -69.5); - - addAll([ - launchRampBase, - launchRampForegroundRailing, - launchRampExit, - launchRampCloseWall, - ]); - } +class LaunchRamp extends Blueprint { + /// {@macro launch_ramp} + LaunchRamp() + : super( + components: [ + // TODO(alestiago): Is it using initialPosition? + _LaunchRampBase(), + _LaunchRampForegroundRailing(), + _LaunchRampExit()..initialPosition = Vector2(0.6, -34), + _LaunchRampCloseWall()..initialPosition = Vector2(4, -69.5), + ], + ); } /// {@template launch_ramp_base} @@ -125,6 +113,13 @@ class _LaunchRampBase extends BodyComponent with InitialPosition, Layered { return body; } + + @override + Future onLoad() async { + await super.onLoad(); + gameRef + .addContactCallback(LayerSensorBallContactCallback<_LaunchRampExit>()); + } } class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { @@ -258,10 +253,8 @@ class _LaunchRampCloseWall extends BodyComponent with InitialPosition, Layered { /// {@endtemplate} class _LaunchRampExit extends LayerSensor { /// {@macro launch_ramp_exit} - _LaunchRampExit({ - required double rotation, - }) : _rotation = rotation, - super( + _LaunchRampExit() + : super( insideLayer: Layer.launcher, outsideLayer: Layer.board, orientation: LayerEntranceOrientation.down, @@ -272,8 +265,6 @@ class _LaunchRampExit extends LayerSensor { renderBody = false; } - final double _rotation; - static final Vector2 _size = Vector2(1.6, 0.1); @override @@ -282,6 +273,6 @@ class _LaunchRampExit extends LayerSensor { _size.x, _size.y, initialPosition, - _rotation, + math.pi / 2, ); } diff --git a/packages/pinball_components/lib/src/components/slingshot.dart b/packages/pinball_components/lib/src/components/slingshot.dart index 52cdb698..4d098c7a 100644 --- a/packages/pinball_components/lib/src/components/slingshot.dart +++ b/packages/pinball_components/lib/src/components/slingshot.dart @@ -1,5 +1,3 @@ -// ignore_for_file: avoid_renaming_method_parameters - import 'package:flame/components.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:pinball_components/pinball_components.dart'; @@ -9,26 +7,23 @@ import 'package:pinball_flame/pinball_flame.dart'; /// A [Blueprint] which creates the pair of [Slingshot]s on the right side of /// the board. /// {@endtemplate} -class Slingshots extends Forge2DBlueprint { - @override - void build(_) { - final upperSlingshot = Slingshot( - length: 5.64, - angle: -0.017, - spritePath: Assets.images.slingshot.upper.keyName, - )..initialPosition = Vector2(22.3, -1.58); - - final lowerSlingshot = Slingshot( - length: 3.46, - angle: -0.468, - spritePath: Assets.images.slingshot.lower.keyName, - )..initialPosition = Vector2(24.7, 6.2); - - addAll([ - upperSlingshot, - lowerSlingshot, - ]); - } +class Slingshots extends Blueprint { + /// {@macro slingshots} + Slingshots() + : super( + components: [ + Slingshot( + length: 5.64, + angle: -0.017, + spritePath: Assets.images.slingshot.upper.keyName, + )..initialPosition = Vector2(22.3, -1.58), + Slingshot( + length: 3.46, + angle: -0.468, + spritePath: Assets.images.slingshot.lower.keyName, + )..initialPosition = Vector2(24.7, 6.2), + ], + ); } /// {@template slingshot} diff --git a/packages/pinball_components/lib/src/components/spaceship.dart b/packages/pinball_components/lib/src/components/spaceship.dart index b3d674dc..4ea4e05a 100644 --- a/packages/pinball_components/lib/src/components/spaceship.dart +++ b/packages/pinball_components/lib/src/components/spaceship.dart @@ -1,5 +1,3 @@ -// ignore_for_file: avoid_renaming_method_parameters - import 'dart:async'; import 'dart:math'; @@ -12,38 +10,28 @@ import 'package:pinball_flame/pinball_flame.dart'; /// {@template spaceship} /// A [Blueprint] which creates the spaceship feature. /// {@endtemplate} -class Spaceship extends Forge2DBlueprint { +class Spaceship extends Blueprint { /// {@macro spaceship} - Spaceship({required this.position}); + Spaceship({required Vector2 position}) + : super( + components: [ + SpaceshipSaucer()..initialPosition = position, + _SpaceshipEntrance()..initialPosition = position, + AndroidHead()..initialPosition = position, + _SpaceshipHole( + outsideLayer: Layer.spaceshipExitRail, + outsidePriority: RenderPriority.ballOnSpaceshipRail, + )..initialPosition = position - Vector2(5.2, -4.8), + _SpaceshipHole( + outsideLayer: Layer.board, + outsidePriority: RenderPriority.ballOnBoard, + )..initialPosition = position - Vector2(-7.2, -0.8), + SpaceshipWall()..initialPosition = position, + ], + ); /// Total size of the spaceship. static final size = Vector2(25, 19); - - /// The [position] where the elements will be created - final Vector2 position; - - @override - void build(_) { - addAllContactCallback([ - LayerSensorBallContactCallback<_SpaceshipEntrance>(), - LayerSensorBallContactCallback<_SpaceshipHole>(), - ]); - - addAll([ - SpaceshipSaucer()..initialPosition = position, - _SpaceshipEntrance()..initialPosition = position, - AndroidHead()..initialPosition = position, - _SpaceshipHole( - outsideLayer: Layer.spaceshipExitRail, - outsidePriority: RenderPriority.ballOnSpaceshipRail, - )..initialPosition = position - Vector2(5.2, -4.8), - _SpaceshipHole( - outsideLayer: Layer.board, - outsidePriority: RenderPriority.ballOnBoard, - )..initialPosition = position - Vector2(-7.2, -0.8), - SpaceshipWall()..initialPosition = position, - ]); - } } /// {@template spaceship_saucer} @@ -51,26 +39,28 @@ class Spaceship extends Forge2DBlueprint { /// {@endtemplate} class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { /// {@macro spaceship_saucer} - SpaceshipSaucer() : super(priority: RenderPriority.spaceshipSaucer) { + SpaceshipSaucer() + : super( + priority: RenderPriority.spaceshipSaucer, + children: [ + _SpaceshipSaucerSpriteComponent(), + ], + ) { layer = Layer.spaceship; + renderBody = false; } @override Future onLoad() async { await super.onLoad(); - final sprite = await gameRef.loadSprite( - Assets.images.spaceship.saucer.keyName, - ); - - await add( - SpriteComponent( - sprite: sprite, - size: Spaceship.size, - anchor: Anchor.center, - ), - ); - renderBody = false; + gameRef + ..addContactCallback( + LayerSensorBallContactCallback<_SpaceshipEntrance>(), + ) + ..addContactCallback( + LayerSensorBallContactCallback<_SpaceshipHole>(), + ); } @override @@ -89,6 +79,25 @@ class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { } } +class _SpaceshipSaucerSpriteComponent extends SpriteComponent with HasGameRef { + _SpaceshipSaucerSpriteComponent() + : super( + anchor: Anchor.center, + // TODO(alestiago): Refactor to use sprite orignial size instead. + size: Spaceship.size, + ); + + @override + Future onLoad() async { + await super.onLoad(); + + // TODO(alestiago): Use cached sprite. + sprite = await gameRef.loadSprite( + Assets.images.spaceship.saucer.keyName, + ); + } +} + /// {@template spaceship_bridge} /// A [BodyComponent] that provides both the collision and the rotation /// animation for the bridge. diff --git a/packages/pinball_components/lib/src/components/spaceship_rail.dart b/packages/pinball_components/lib/src/components/spaceship_rail.dart index 49fdd475..6b66c59b 100644 --- a/packages/pinball_components/lib/src/components/spaceship_rail.dart +++ b/packages/pinball_components/lib/src/components/spaceship_rail.dart @@ -1,5 +1,3 @@ -// ignore_for_file: avoid_renaming_method_parameters - import 'dart:math' as math; import 'package:flame/components.dart'; @@ -11,38 +9,32 @@ import 'package:pinball_flame/pinball_flame.dart'; /// {@template spaceship_rail} /// A [Blueprint] for the spaceship drop tube. /// {@endtemplate} -class SpaceshipRail extends Forge2DBlueprint { +class SpaceshipRail extends Blueprint { /// {@macro spaceship_rail} - SpaceshipRail(); - - @override - void build(_) { - addAllContactCallback([ - LayerSensorBallContactCallback<_SpaceshipRailExit>(), - ]); - - final railRamp = _SpaceshipRailRamp(); - final railEnd = _SpaceshipRailExit(); - final topBase = _SpaceshipRailBase(radius: 0.55) - ..initialPosition = Vector2(-26.15, -18.65); - final bottomBase = _SpaceshipRailBase(radius: 0.8) - ..initialPosition = Vector2(-25.5, 12.9); - final railForeground = _SpaceshipRailForeground(); - - addAll([ - railRamp, - railEnd, - topBase, - bottomBase, - railForeground, - ]); - } + SpaceshipRail() + : super( + components: [ + // TODO(alestiago): Investigate if it's using initial position. + _SpaceshipRailRamp(), + _SpaceshipRailExit(), + _SpaceshipRailBase(radius: 0.55) + ..initialPosition = Vector2(-26.15, -18.65), + _SpaceshipRailBase(radius: 0.8) + ..initialPosition = Vector2(-25.5, 12.9), + _SpaceshipRailForeground() + ], + ); } /// Represents the spaceship drop rail from the [Spaceship]. class _SpaceshipRailRamp extends BodyComponent with InitialPosition, Layered { - _SpaceshipRailRamp() : super(priority: RenderPriority.spaceshipRail) { + _SpaceshipRailRamp() + : super( + priority: RenderPriority.spaceshipRail, + children: [_SpaceshipRailRampSpriteComponent()], + ) { layer = Layer.spaceshipExitRail; + renderBody = false; } List _createFixtureDefs() { @@ -134,9 +126,9 @@ class _SpaceshipRailRamp extends BodyComponent with InitialPosition, Layered { @override Future onLoad() async { await super.onLoad(); - renderBody = false; - - await add(_SpaceshipRailRampSpriteComponent()); + gameRef.addContactCallback( + LayerSensorBallContactCallback<_SpaceshipRailExit>(), + ); } } diff --git a/packages/pinball_components/lib/src/components/spaceship_ramp.dart b/packages/pinball_components/lib/src/components/spaceship_ramp.dart index f2d8801c..6a976794 100644 --- a/packages/pinball_components/lib/src/components/spaceship_ramp.dart +++ b/packages/pinball_components/lib/src/components/spaceship_ramp.dart @@ -1,5 +1,3 @@ -// ignore_for_file: avoid_renaming_method_parameters - import 'dart:math' as math; import 'package:flame/components.dart'; @@ -11,56 +9,45 @@ import 'package:pinball_flame/pinball_flame.dart'; /// {@template spaceship_ramp} /// A [Blueprint] which creates the ramp leading into the [Spaceship]. /// {@endtemplate} -class SpaceshipRamp extends Forge2DBlueprint { +class SpaceshipRamp extends Blueprint { /// {@macro spaceship_ramp} - SpaceshipRamp(); - - @override - void build(_) { - addAllContactCallback([ - LayerSensorBallContactCallback<_SpaceshipRampOpening>(), - ]); - - final rightOpening = _SpaceshipRampOpening( - outsidePriority: RenderPriority.ballOnBoard, - rotation: math.pi, - ) - ..initialPosition = Vector2(1.7, -19.8) - ..layer = Layer.opening; - final leftOpening = _SpaceshipRampOpening( - outsideLayer: Layer.spaceship, - outsidePriority: RenderPriority.ballOnSpaceship, - rotation: math.pi, - ) - ..initialPosition = Vector2(-13.7, -18.6) - ..layer = Layer.spaceshipEntranceRamp; - - final spaceshipRamp = _SpaceshipRampBackground(); - - final spaceshipRampBoardOpeningSprite = - _SpaceshipRampBoardOpeningSpriteComponent() - ..position = Vector2(3.4, -39.5); - - final spaceshipRampForegroundRailing = _SpaceshipRampForegroundRailing(); - - final baseRight = _SpaceshipRampBase()..initialPosition = Vector2(1.7, -20); - - addAll([ - spaceshipRampBoardOpeningSprite, - rightOpening, - leftOpening, - baseRight, - _SpaceshipRampBackgroundRailingSpriteComponent(), - spaceshipRamp, - spaceshipRampForegroundRailing, - ]); - } + SpaceshipRamp() + : super( + components: [ + _SpaceshipRampOpening( + outsidePriority: RenderPriority.ballOnBoard, + rotation: math.pi, + ) + ..initialPosition = Vector2(1.7, -19.8) + ..layer = Layer.opening, + _SpaceshipRampOpening( + outsideLayer: Layer.spaceship, + outsidePriority: RenderPriority.ballOnSpaceship, + rotation: math.pi, + ) + ..initialPosition = Vector2(-13.7, -18.6) + ..layer = Layer.spaceshipEntranceRamp, + _SpaceshipRampBackground(), + _SpaceshipRampBoardOpeningSpriteComponent() + ..position = Vector2(3.4, -39.5), + _SpaceshipRampForegroundRailing(), + _SpaceshipRampBase()..initialPosition = Vector2(1.7, -20), + _SpaceshipRampBackgroundRailingSpriteComponent(), + ], + ); } class _SpaceshipRampBackground extends BodyComponent with InitialPosition, Layered { - _SpaceshipRampBackground() : super(priority: RenderPriority.spaceshipRamp) { + _SpaceshipRampBackground() + : super( + priority: RenderPriority.spaceshipRamp, + children: [ + _SpaceshipRampBackgroundRampSpriteComponent(), + ], + ) { layer = Layer.spaceshipEntranceRamp; + renderBody = false; } /// Width between walls of the ramp. @@ -116,16 +103,19 @@ class _SpaceshipRampBackground extends BodyComponent @override Future onLoad() async { await super.onLoad(); - renderBody = false; - - await add(_SpaceshipRampBackgroundRampSpriteComponent()); + gameRef.addContactCallback( + LayerSensorBallContactCallback<_SpaceshipRampOpening>(), + ); } } class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent with HasGameRef { _SpaceshipRampBackgroundRailingSpriteComponent() - : super(priority: RenderPriority.spaceshipRampBackgroundRailing); + : super( + priority: RenderPriority.spaceshipRampBackgroundRailing, + ); + @override Future onLoad() async { await super.onLoad(); diff --git a/packages/pinball_components/lib/src/components/sparky_computer.dart b/packages/pinball_components/lib/src/components/sparky_computer.dart index 427847ae..f75f8356 100644 --- a/packages/pinball_components/lib/src/components/sparky_computer.dart +++ b/packages/pinball_components/lib/src/components/sparky_computer.dart @@ -9,17 +9,17 @@ import 'package:pinball_flame/pinball_flame.dart'; /// A [Blueprint] which creates the [_ComputerBase] and /// [_ComputerTopSpriteComponent]. /// {@endtemplate} -class SparkyComputer extends Forge2DBlueprint { - @override - void build(_) { - final computerBase = _ComputerBase(); - final computerTop = _ComputerTopSpriteComponent(); - - addAll([ - computerBase, - computerTop, - ]); - } +class SparkyComputer extends Blueprint { + /// {@macro sparky_computer} + SparkyComputer({ + Iterable? components, + }) : super( + components: [ + _ComputerBase(), + _ComputerTopSpriteComponent(), + if (components != null) ...components, + ], + ); } class _ComputerBase extends BodyComponent with InitialPosition { diff --git a/packages/pinball_flame/lib/src/blueprint.dart b/packages/pinball_flame/lib/src/blueprint.dart index 5c2df683..5b61446e 100644 --- a/packages/pinball_flame/lib/src/blueprint.dart +++ b/packages/pinball_flame/lib/src/blueprint.dart @@ -1,101 +1,47 @@ import 'package:flame/components.dart'; import 'package:flame/game.dart'; -import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/foundation.dart'; -const _attachedErrorMessage = "Can't add to attached Blueprints"; - // TODO(erickzanardo): Keeping this inside our code base // so we can experiment with the idea, but this is a // potential upstream change on Flame. -/// A [Blueprint] is a virtual way of grouping [Component]s -/// that are related, but they need to be added directly on -/// the [FlameGame] level. +/// {@template blueprint} +/// A [Blueprint] is a virtual way of grouping [Component]s that are related, +/// but they need to be added directly on the [FlameGame] level. +/// {@endtemplate blueprint} // TODO(alestiago): refactor with feat/make-blueprint-extend-component. -abstract class Blueprint extends Component { - final List _components = []; - final List _blueprints = []; - - bool _isAttached = false; - - /// Called before the the [Component]s managed - /// by this blueprint is added to the [FlameGame] - void build(T gameRef); - - /// Attach the [Component]s built on [build] to the [game] - /// instance - @mustCallSuper - Future attach(T game) async { - build(game); - await Future.wait([ - game.addAll(_components), - ..._blueprints.map(game.addFromBlueprint).toList(), - ]); - _isAttached = true; - } - - /// Adds a single [Component] to this blueprint. - @override - Future add(Component component) async { - assert(!_isAttached, _attachedErrorMessage); - _components.add(component); +class Blueprint extends Component { + /// {@macro blueprint} + Blueprint({ + Iterable? components, + Iterable? blueprints, + }) { + if (components != null) _components.addAll(components); + if (blueprints != null) { + for (final blueprint in blueprints) { + _components.addAll(blueprint.components); + } + } } - /// Adds a list of [Blueprint]s to this blueprint. - void addAllBlueprints(List blueprints) { - assert(!_isAttached, _attachedErrorMessage); - _blueprints.addAll(blueprints); - } + final List _components = []; - /// Adds a single [Blueprint] to this blueprint. - void addBlueprint(Blueprint blueprint) { - assert(!_isAttached, _attachedErrorMessage); - _blueprints.add(blueprint); + /// Attaches children. + @mustCallSuper + Future _attach(Component parent) async { + await parent.addAll(_components); } - /// Returns a copy of the components built by this blueprint + /// Returns a copy of the components built by this blueprint. List get components => List.unmodifiable(_components); - - /// Returns a copy of the children blueprints - List get blueprints => List.unmodifiable(_blueprints); -} - -/// A [Blueprint] that provides additional -/// structures specific to flame_forge2d -abstract class Forge2DBlueprint extends Blueprint { - final List _callbacks = []; - - /// Adds a single [ContactCallback] to this blueprint - void addContactCallback(ContactCallback callback) { - assert(!_isAttached, _attachedErrorMessage); - _callbacks.add(callback); - } - - /// Adds a collection of [ContactCallback]s to this blueprint - void addAllContactCallback(List callbacks) { - assert(!_isAttached, _attachedErrorMessage); - _callbacks.addAll(callbacks); - } - - @override - Future attach(Forge2DGame game) async { - await super.attach(game); - - for (final callback in _callbacks) { - game.addContactCallback(callback); - } - } - - /// Returns a copy of the callbacks built by this blueprint - List get callbacks => List.unmodifiable(_callbacks); } -/// Adds helper methods regardin [Blueprint]s to [FlameGame] -extension FlameGameBlueprint on FlameGame { +/// Adds helper methods regarding [Blueprint]s to [FlameGame]. +extension FlameGameBlueprint on Component { /// Shortcut to attach a [Blueprint] instance to this game /// equivalent to `MyBluepinrt().attach(game)` Future addFromBlueprint(Blueprint blueprint) async { - await blueprint.attach(this); + await blueprint._attach(this); } } diff --git a/packages/pinball_flame/test/src/blueprint_test.dart b/packages/pinball_flame/test/src/blueprint_test.dart index f1c58dc9..5de977b3 100644 --- a/packages/pinball_flame/test/src/blueprint_test.dart +++ b/packages/pinball_flame/test/src/blueprint_test.dart @@ -1,138 +1,57 @@ +// ignore_for_file: cascade_invocations + import 'package:flame/components.dart'; -import 'package:flame_forge2d/contact_callbacks.dart'; +import 'package:flame/game.dart'; +import 'package:flame_test/flame_test.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:pinball_flame/pinball_flame.dart'; -import '../helpers/helpers.dart'; - -class TestContactCallback extends ContactCallback {} - -class MyBlueprint extends Blueprint { - @override - void build(_) { - add(Component()); - addAll([Component(), Component()]); - } -} - -class MyOtherBlueprint extends Blueprint { - @override - void build(_) { - add(Component()); - } -} - -class YetMyOtherBlueprint extends Blueprint { - @override - void build(_) { - add(Component()); - } -} - -class MyComposedBlueprint extends Blueprint { - @override - void build(_) { - addBlueprint(MyBlueprint()); - addAllBlueprints([MyOtherBlueprint(), YetMyOtherBlueprint()]); - } -} - -class MyForge2dBlueprint extends Forge2DBlueprint { - @override - void build(_) { - addContactCallback(MockContactCallback()); - addAllContactCallback([MockContactCallback(), MockContactCallback()]); - } -} - void main() { - group('Blueprint', () { - setUpAll(() { - registerFallbackValue(MyBlueprint()); - registerFallbackValue(Component()); - }); + TestWidgetsFlutterBinding.ensureInitialized(); - test('components can be added to it', () { - final blueprint = MyBlueprint()..build(MockForge2DGame()); - - expect(blueprint.components.length, equals(3)); - }); - - test('blueprints can be added to it', () { - final blueprint = MyComposedBlueprint()..build(MockForge2DGame()); - - expect(blueprint.blueprints.length, equals(3)); - }); - - test('adds the components to a game on attach', () { - final mockGame = MockForge2DGame(); - when(() => mockGame.addAll(any())).thenAnswer((_) async {}); - MyBlueprint().attach(mockGame); - - verify(() => mockGame.addAll(any())).called(1); - }); - - test('adds components from a child Blueprint the to a game on attach', () { - final mockGame = MockForge2DGame(); - when(() => mockGame.addAll(any())).thenAnswer((_) async {}); - MyComposedBlueprint().attach(mockGame); - - verify(() => mockGame.addAll(any())).called(4); - }); - - test( - 'throws assertion error when adding to an already attached blueprint', - () async { - final mockGame = MockForge2DGame(); - when(() => mockGame.addAll(any())).thenAnswer((_) async {}); - final blueprint = MyBlueprint(); - await blueprint.attach(mockGame); - - expect(() => blueprint.add(Component()), throwsAssertionError); - expect(() => blueprint.addAll([Component()]), throwsAssertionError); - }, - ); - }); - - group('Forge2DBlueprint', () { - setUpAll(() { - registerFallbackValue(TestContactCallback()); - }); - - test('callbacks can be added to it', () { - final blueprint = MyForge2dBlueprint()..build(MockForge2DGame()); - - expect(blueprint.callbacks.length, equals(3)); - }); - - test('adds the callbacks to a game on attach', () async { - final mockGame = MockForge2DGame(); - when(() => mockGame.addAll(any())).thenAnswer((_) async {}); - when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {}); - await MyForge2dBlueprint().attach(mockGame); - - verify(() => mockGame.addContactCallback(any())).called(3); + group('Blueprint', () { + final flameTester = FlameTester(FlameGame.new); + + flameTester.test('adds the components to parent on attach', (game) async { + final blueprint = Blueprint( + components: [ + Component(), + Component(), + ], + ); + await game.addFromBlueprint(blueprint); + expect(game.children, equals(blueprint.components)); + }); + + flameTester + .test('adds components from a child Blueprint the to a game on attach', + (game) async { + final childBlueprint = Blueprint( + components: [ + Component(), + Component(), + ], + ); + final parentBlueprint = Blueprint( + components: [ + Component(), + Component(), + ], + blueprints: [ + childBlueprint, + ], + ); + + await game.addFromBlueprint(parentBlueprint); + await game.ready(); + + expect( + game.children, + equals([ + ...parentBlueprint.components, + ...childBlueprint.components, + ]), + ); }); - - test( - 'throws assertion error when adding to an already attached blueprint', - () async { - final mockGame = MockForge2DGame(); - when(() => mockGame.addAll(any())).thenAnswer((_) async {}); - when(() => mockGame.addContactCallback(any())).thenAnswer((_) async {}); - final blueprint = MyForge2dBlueprint(); - await blueprint.attach(mockGame); - - expect( - () => blueprint.addContactCallback(MockContactCallback()), - throwsAssertionError, - ); - expect( - () => blueprint.addAllContactCallback([MockContactCallback()]), - throwsAssertionError, - ); - }, - ); }); }