feat: adjusted Blueprint implementation

pull/212/head
alestiago 3 years ago
parent 58324adad6
commit 5b37577564

@ -1,6 +1,5 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
@ -11,20 +10,16 @@ import 'package:pinball_flame/pinball_flame.dart';
/// [SparkyComputer] with a [SparkyComputerController] attached. /// [SparkyComputer] with a [SparkyComputerController] attached.
/// {@endtemplate} /// {@endtemplate}
class ControlledSparkyComputer extends SparkyComputer class ControlledSparkyComputer extends SparkyComputer
with Controls<SparkyComputerController>, HasGameRef<PinballGame> { with Controls<SparkyComputerController> {
/// {@macro controlled_sparky_computer} /// {@macro controlled_sparky_computer}
ControlledSparkyComputer() { ControlledSparkyComputer()
: super(
components: [
SparkyTurboChargeSensor()..initialPosition = Vector2(-13, -49.8)
],
) {
controller = SparkyComputerController(this); 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} /// {@template sparky_computer_controller}
@ -62,6 +57,12 @@ class SparkyTurboChargeSensor extends BodyComponent with InitialPosition {
return world.createBody(bodyDef)..createFixture(fixtureDef); return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
@override
Future<void> onLoad() async {
await super.onLoad();
gameRef.addContactCallback(SparkyTurboChargeSensorBallContactCallback());
}
} }
/// {@template sparky_turbo_charge_sensor_ball_contact_callback} /// {@template sparky_turbo_charge_sensor_ball_contact_callback}

@ -7,21 +7,15 @@ import 'package:pinball_flame/pinball_flame.dart';
/// A [Blueprint] which creates the [Plunger], [RocketSpriteComponent] and /// A [Blueprint] which creates the [Plunger], [RocketSpriteComponent] and
/// [LaunchRamp]. /// [LaunchRamp].
/// {@endtemplate} /// {@endtemplate}
class Launcher extends Forge2DBlueprint { class Launcher extends Blueprint {
/// {@macro launcher} /// {@macro launcher}
Launcher(); Launcher()
: super(
/// [Plunger] to launch the [Ball] onto the board. components: [
late final Plunger plunger; ControlledPlunger(compressionDistance: 14)
..initialPosition = Vector2(40.7, 38),
@override RocketSpriteComponent()..position = Vector2(43, 62),
void build(Forge2DGame gameRef) { ],
plunger = ControlledPlunger(compressionDistance: 14) blueprints: [LaunchRamp()],
..initialPosition = Vector2(40.7, 38); );
final _rocket = RocketSpriteComponent()..position = Vector2(43, 62);
addAll([_rocket, plunger]);
addBlueprint(LaunchRamp());
}
} }

@ -55,6 +55,7 @@ class PinballGame extends Forge2DGame
unawaited(addFromBlueprint(launcher)); unawaited(addFromBlueprint(launcher));
unawaited(add(Board())); unawaited(add(Board()));
unawaited(add(AlienZone())); unawaited(add(AlienZone()));
unawaited(add(SparkyFireZone())); unawaited(add(SparkyFireZone()));
unawaited(addFromBlueprint(Slingshots())); unawaited(addFromBlueprint(Slingshots()));
unawaited(addFromBlueprint(DinoWalls())); unawaited(addFromBlueprint(DinoWalls()));
@ -69,7 +70,7 @@ class PinballGame extends Forge2DGame
); );
unawaited(addFromBlueprint(SpaceshipRail())); unawaited(addFromBlueprint(SpaceshipRail()));
controller.attachTo(launcher.plunger); controller.attachTo(launcher.components.whereType<Plunger>().first);
await super.onLoad(); await super.onLoad();
} }

@ -1,5 +1,3 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -8,14 +6,15 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@template boundaries} /// {@template boundaries}
/// A [Blueprint] which creates the [_BottomBoundary] and [_OuterBoundary]. /// A [Blueprint] which creates the [_BottomBoundary] and [_OuterBoundary].
///{@endtemplate boundaries} ///{@endtemplate boundaries}
class Boundaries extends Forge2DBlueprint { class Boundaries extends Blueprint {
@override /// {@macro boundaries}
void build(_) { Boundaries()
final bottomBoundary = _BottomBoundary(); : super(
final outerBoundary = _OuterBoundary(); components: [
_BottomBoundary(),
addAll([outerBoundary, bottomBoundary]); _OuterBoundary(),
} ],
);
} }
/// {@template bottom_boundary} /// {@template bottom_boundary}

@ -1,5 +1,3 @@
// ignore_for_file: comment_references, avoid_renaming_method_parameters
import 'dart:async'; import 'dart:async';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
@ -11,17 +9,15 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@template dinowalls} /// {@template dinowalls}
/// A [Blueprint] which creates walls for the [ChromeDino]. /// A [Blueprint] which creates walls for the [ChromeDino].
/// {@endtemplate} /// {@endtemplate}
class DinoWalls extends Forge2DBlueprint { class DinoWalls extends Blueprint {
/// {@macro dinowalls} /// {@macro dinowalls}
DinoWalls(); DinoWalls()
: super(
@override components: [
void build(_) { _DinoTopWall(),
addAll([ _DinoBottomWall(),
_DinoTopWall(), ],
_DinoBottomWall(), );
]);
}
} }
/// {@template dino_top_wall} /// {@template dino_top_wall}

@ -11,30 +11,18 @@ import 'package:pinball_flame/pinball_flame.dart';
/// A [Blueprint] which creates the [_LaunchRampBase] and /// A [Blueprint] which creates the [_LaunchRampBase] and
/// [_LaunchRampForegroundRailing]. /// [_LaunchRampForegroundRailing].
/// {@endtemplate} /// {@endtemplate}
class LaunchRamp extends Forge2DBlueprint { class LaunchRamp extends Blueprint {
@override /// {@macro launch_ramp}
void build(_) { LaunchRamp()
addAllContactCallback([ : super(
LayerSensorBallContactCallback<_LaunchRampExit>(), components: [
]); // TODO(alestiago): Is it using initialPosition?
_LaunchRampBase(),
final launchRampBase = _LaunchRampBase(); _LaunchRampForegroundRailing(),
_LaunchRampExit()..initialPosition = Vector2(0.6, -34),
final launchRampForegroundRailing = _LaunchRampForegroundRailing(); _LaunchRampCloseWall()..initialPosition = Vector2(4, -69.5),
],
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,
]);
}
} }
/// {@template launch_ramp_base} /// {@template launch_ramp_base}
@ -125,6 +113,13 @@ class _LaunchRampBase extends BodyComponent with InitialPosition, Layered {
return body; return body;
} }
@override
Future<void> onLoad() async {
await super.onLoad();
gameRef
.addContactCallback(LayerSensorBallContactCallback<_LaunchRampExit>());
}
} }
class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef { class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef {
@ -258,10 +253,8 @@ class _LaunchRampCloseWall extends BodyComponent with InitialPosition, Layered {
/// {@endtemplate} /// {@endtemplate}
class _LaunchRampExit extends LayerSensor { class _LaunchRampExit extends LayerSensor {
/// {@macro launch_ramp_exit} /// {@macro launch_ramp_exit}
_LaunchRampExit({ _LaunchRampExit()
required double rotation, : super(
}) : _rotation = rotation,
super(
insideLayer: Layer.launcher, insideLayer: Layer.launcher,
outsideLayer: Layer.board, outsideLayer: Layer.board,
orientation: LayerEntranceOrientation.down, orientation: LayerEntranceOrientation.down,
@ -272,8 +265,6 @@ class _LaunchRampExit extends LayerSensor {
renderBody = false; renderBody = false;
} }
final double _rotation;
static final Vector2 _size = Vector2(1.6, 0.1); static final Vector2 _size = Vector2(1.6, 0.1);
@override @override
@ -282,6 +273,6 @@ class _LaunchRampExit extends LayerSensor {
_size.x, _size.x,
_size.y, _size.y,
initialPosition, initialPosition,
_rotation, math.pi / 2,
); );
} }

@ -1,5 +1,3 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.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 /// A [Blueprint] which creates the pair of [Slingshot]s on the right side of
/// the board. /// the board.
/// {@endtemplate} /// {@endtemplate}
class Slingshots extends Forge2DBlueprint { class Slingshots extends Blueprint {
@override /// {@macro slingshots}
void build(_) { Slingshots()
final upperSlingshot = Slingshot( : super(
length: 5.64, components: [
angle: -0.017, Slingshot(
spritePath: Assets.images.slingshot.upper.keyName, length: 5.64,
)..initialPosition = Vector2(22.3, -1.58); angle: -0.017,
spritePath: Assets.images.slingshot.upper.keyName,
final lowerSlingshot = Slingshot( )..initialPosition = Vector2(22.3, -1.58),
length: 3.46, Slingshot(
angle: -0.468, length: 3.46,
spritePath: Assets.images.slingshot.lower.keyName, angle: -0.468,
)..initialPosition = Vector2(24.7, 6.2); spritePath: Assets.images.slingshot.lower.keyName,
)..initialPosition = Vector2(24.7, 6.2),
addAll([ ],
upperSlingshot, );
lowerSlingshot,
]);
}
} }
/// {@template slingshot} /// {@template slingshot}

@ -1,5 +1,3 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'dart:async'; import 'dart:async';
import 'dart:math'; import 'dart:math';
@ -12,38 +10,28 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@template spaceship} /// {@template spaceship}
/// A [Blueprint] which creates the spaceship feature. /// A [Blueprint] which creates the spaceship feature.
/// {@endtemplate} /// {@endtemplate}
class Spaceship extends Forge2DBlueprint { class Spaceship extends Blueprint {
/// {@macro spaceship} /// {@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. /// Total size of the spaceship.
static final size = Vector2(25, 19); 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} /// {@template spaceship_saucer}
@ -51,26 +39,28 @@ class Spaceship extends Forge2DBlueprint {
/// {@endtemplate} /// {@endtemplate}
class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered { class SpaceshipSaucer extends BodyComponent with InitialPosition, Layered {
/// {@macro spaceship_saucer} /// {@macro spaceship_saucer}
SpaceshipSaucer() : super(priority: RenderPriority.spaceshipSaucer) { SpaceshipSaucer()
: super(
priority: RenderPriority.spaceshipSaucer,
children: [
_SpaceshipSaucerSpriteComponent(),
],
) {
layer = Layer.spaceship; layer = Layer.spaceship;
renderBody = false;
} }
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); 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 @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<void> onLoad() async {
await super.onLoad();
// TODO(alestiago): Use cached sprite.
sprite = await gameRef.loadSprite(
Assets.images.spaceship.saucer.keyName,
);
}
}
/// {@template spaceship_bridge} /// {@template spaceship_bridge}
/// A [BodyComponent] that provides both the collision and the rotation /// A [BodyComponent] that provides both the collision and the rotation
/// animation for the bridge. /// animation for the bridge.

@ -1,5 +1,3 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
@ -11,38 +9,32 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@template spaceship_rail} /// {@template spaceship_rail}
/// A [Blueprint] for the spaceship drop tube. /// A [Blueprint] for the spaceship drop tube.
/// {@endtemplate} /// {@endtemplate}
class SpaceshipRail extends Forge2DBlueprint { class SpaceshipRail extends Blueprint {
/// {@macro spaceship_rail} /// {@macro spaceship_rail}
SpaceshipRail(); SpaceshipRail()
: super(
@override components: [
void build(_) { // TODO(alestiago): Investigate if it's using initial position.
addAllContactCallback([ _SpaceshipRailRamp(),
LayerSensorBallContactCallback<_SpaceshipRailExit>(), _SpaceshipRailExit(),
]); _SpaceshipRailBase(radius: 0.55)
..initialPosition = Vector2(-26.15, -18.65),
final railRamp = _SpaceshipRailRamp(); _SpaceshipRailBase(radius: 0.8)
final railEnd = _SpaceshipRailExit(); ..initialPosition = Vector2(-25.5, 12.9),
final topBase = _SpaceshipRailBase(radius: 0.55) _SpaceshipRailForeground()
..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,
]);
}
} }
/// Represents the spaceship drop rail from the [Spaceship]. /// Represents the spaceship drop rail from the [Spaceship].
class _SpaceshipRailRamp extends BodyComponent with InitialPosition, Layered { class _SpaceshipRailRamp extends BodyComponent with InitialPosition, Layered {
_SpaceshipRailRamp() : super(priority: RenderPriority.spaceshipRail) { _SpaceshipRailRamp()
: super(
priority: RenderPriority.spaceshipRail,
children: [_SpaceshipRailRampSpriteComponent()],
) {
layer = Layer.spaceshipExitRail; layer = Layer.spaceshipExitRail;
renderBody = false;
} }
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
@ -134,9 +126,9 @@ class _SpaceshipRailRamp extends BodyComponent with InitialPosition, Layered {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
renderBody = false; gameRef.addContactCallback(
LayerSensorBallContactCallback<_SpaceshipRailExit>(),
await add(_SpaceshipRailRampSpriteComponent()); );
} }
} }

@ -1,5 +1,3 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
@ -11,56 +9,45 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@template spaceship_ramp} /// {@template spaceship_ramp}
/// A [Blueprint] which creates the ramp leading into the [Spaceship]. /// A [Blueprint] which creates the ramp leading into the [Spaceship].
/// {@endtemplate} /// {@endtemplate}
class SpaceshipRamp extends Forge2DBlueprint { class SpaceshipRamp extends Blueprint {
/// {@macro spaceship_ramp} /// {@macro spaceship_ramp}
SpaceshipRamp(); SpaceshipRamp()
: super(
@override components: [
void build(_) { _SpaceshipRampOpening(
addAllContactCallback([ outsidePriority: RenderPriority.ballOnBoard,
LayerSensorBallContactCallback<_SpaceshipRampOpening>(), rotation: math.pi,
]); )
..initialPosition = Vector2(1.7, -19.8)
final rightOpening = _SpaceshipRampOpening( ..layer = Layer.opening,
outsidePriority: RenderPriority.ballOnBoard, _SpaceshipRampOpening(
rotation: math.pi, outsideLayer: Layer.spaceship,
) outsidePriority: RenderPriority.ballOnSpaceship,
..initialPosition = Vector2(1.7, -19.8) rotation: math.pi,
..layer = Layer.opening; )
final leftOpening = _SpaceshipRampOpening( ..initialPosition = Vector2(-13.7, -18.6)
outsideLayer: Layer.spaceship, ..layer = Layer.spaceshipEntranceRamp,
outsidePriority: RenderPriority.ballOnSpaceship, _SpaceshipRampBackground(),
rotation: math.pi, _SpaceshipRampBoardOpeningSpriteComponent()
) ..position = Vector2(3.4, -39.5),
..initialPosition = Vector2(-13.7, -18.6) _SpaceshipRampForegroundRailing(),
..layer = Layer.spaceshipEntranceRamp; _SpaceshipRampBase()..initialPosition = Vector2(1.7, -20),
_SpaceshipRampBackgroundRailingSpriteComponent(),
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,
]);
}
} }
class _SpaceshipRampBackground extends BodyComponent class _SpaceshipRampBackground extends BodyComponent
with InitialPosition, Layered { with InitialPosition, Layered {
_SpaceshipRampBackground() : super(priority: RenderPriority.spaceshipRamp) { _SpaceshipRampBackground()
: super(
priority: RenderPriority.spaceshipRamp,
children: [
_SpaceshipRampBackgroundRampSpriteComponent(),
],
) {
layer = Layer.spaceshipEntranceRamp; layer = Layer.spaceshipEntranceRamp;
renderBody = false;
} }
/// Width between walls of the ramp. /// Width between walls of the ramp.
@ -116,16 +103,19 @@ class _SpaceshipRampBackground extends BodyComponent
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
renderBody = false; gameRef.addContactCallback(
LayerSensorBallContactCallback<_SpaceshipRampOpening>(),
await add(_SpaceshipRampBackgroundRampSpriteComponent()); );
} }
} }
class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent class _SpaceshipRampBackgroundRailingSpriteComponent extends SpriteComponent
with HasGameRef { with HasGameRef {
_SpaceshipRampBackgroundRailingSpriteComponent() _SpaceshipRampBackgroundRailingSpriteComponent()
: super(priority: RenderPriority.spaceshipRampBackgroundRailing); : super(
priority: RenderPriority.spaceshipRampBackgroundRailing,
);
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();

@ -9,17 +9,17 @@ import 'package:pinball_flame/pinball_flame.dart';
/// A [Blueprint] which creates the [_ComputerBase] and /// A [Blueprint] which creates the [_ComputerBase] and
/// [_ComputerTopSpriteComponent]. /// [_ComputerTopSpriteComponent].
/// {@endtemplate} /// {@endtemplate}
class SparkyComputer extends Forge2DBlueprint { class SparkyComputer extends Blueprint {
@override /// {@macro sparky_computer}
void build(_) { SparkyComputer({
final computerBase = _ComputerBase(); Iterable<Component>? components,
final computerTop = _ComputerTopSpriteComponent(); }) : super(
components: [
addAll([ _ComputerBase(),
computerBase, _ComputerTopSpriteComponent(),
computerTop, if (components != null) ...components,
]); ],
} );
} }
class _ComputerBase extends BodyComponent with InitialPosition { class _ComputerBase extends BodyComponent with InitialPosition {

@ -1,101 +1,47 @@
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
const _attachedErrorMessage = "Can't add to attached Blueprints";
// TODO(erickzanardo): Keeping this inside our code base // TODO(erickzanardo): Keeping this inside our code base
// so we can experiment with the idea, but this is a // so we can experiment with the idea, but this is a
// potential upstream change on Flame. // potential upstream change on Flame.
/// A [Blueprint] is a virtual way of grouping [Component]s /// {@template blueprint}
/// that are related, but they need to be added directly on /// A [Blueprint] is a virtual way of grouping [Component]s that are related,
/// the [FlameGame] level. /// but they need to be added directly on the [FlameGame] level.
/// {@endtemplate blueprint}
// TODO(alestiago): refactor with feat/make-blueprint-extend-component. // TODO(alestiago): refactor with feat/make-blueprint-extend-component.
abstract class Blueprint<T extends FlameGame> extends Component { class Blueprint extends Component {
final List<Component> _components = []; /// {@macro blueprint}
final List<Blueprint> _blueprints = []; Blueprint({
Iterable<Component>? components,
bool _isAttached = false; Iterable<Blueprint>? blueprints,
}) {
/// Called before the the [Component]s managed if (components != null) _components.addAll(components);
/// by this blueprint is added to the [FlameGame] if (blueprints != null) {
void build(T gameRef); for (final blueprint in blueprints) {
_components.addAll(blueprint.components);
/// Attach the [Component]s built on [build] to the [game] }
/// instance }
@mustCallSuper
Future<void> 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<void> add(Component component) async {
assert(!_isAttached, _attachedErrorMessage);
_components.add(component);
} }
/// Adds a list of [Blueprint]s to this blueprint. final List<Component> _components = [];
void addAllBlueprints(List<Blueprint> blueprints) {
assert(!_isAttached, _attachedErrorMessage);
_blueprints.addAll(blueprints);
}
/// Adds a single [Blueprint] to this blueprint. /// Attaches children.
void addBlueprint(Blueprint blueprint) { @mustCallSuper
assert(!_isAttached, _attachedErrorMessage); Future<void> _attach(Component parent) async {
_blueprints.add(blueprint); 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<Component> get components => List.unmodifiable(_components); List<Component> get components => List.unmodifiable(_components);
/// Returns a copy of the children blueprints
List<Blueprint> get blueprints => List.unmodifiable(_blueprints);
}
/// A [Blueprint] that provides additional
/// structures specific to flame_forge2d
abstract class Forge2DBlueprint extends Blueprint<Forge2DGame> {
final List<ContactCallback> _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<ContactCallback> callbacks) {
assert(!_isAttached, _attachedErrorMessage);
_callbacks.addAll(callbacks);
}
@override
Future<void> 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<ContactCallback> get callbacks => List.unmodifiable(_callbacks);
} }
/// Adds helper methods regardin [Blueprint]s to [FlameGame] /// Adds helper methods regarding [Blueprint]s to [FlameGame].
extension FlameGameBlueprint on FlameGame { extension FlameGameBlueprint on Component {
/// Shortcut to attach a [Blueprint] instance to this game /// Shortcut to attach a [Blueprint] instance to this game
/// equivalent to `MyBluepinrt().attach(game)` /// equivalent to `MyBluepinrt().attach(game)`
Future<void> addFromBlueprint(Blueprint blueprint) async { Future<void> addFromBlueprint(Blueprint blueprint) async {
await blueprint.attach(this); await blueprint._attach(this);
} }
} }

@ -1,138 +1,57 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart'; 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:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
import '../helpers/helpers.dart';
class TestContactCallback extends ContactCallback<dynamic, dynamic> {}
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() { void main() {
group('Blueprint', () { TestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
registerFallbackValue(MyBlueprint());
registerFallbackValue(Component());
});
test('components can be added to it', () { group('Blueprint', () {
final blueprint = MyBlueprint()..build(MockForge2DGame()); final flameTester = FlameTester(FlameGame.new);
expect(blueprint.components.length, equals(3)); flameTester.test('adds the components to parent on attach', (game) async {
}); final blueprint = Blueprint(
components: [
test('blueprints can be added to it', () { Component(),
final blueprint = MyComposedBlueprint()..build(MockForge2DGame()); Component(),
],
expect(blueprint.blueprints.length, equals(3)); );
}); await game.addFromBlueprint(blueprint);
expect(game.children, equals(blueprint.components));
test('adds the components to a game on attach', () { });
final mockGame = MockForge2DGame();
when(() => mockGame.addAll(any())).thenAnswer((_) async {}); flameTester
MyBlueprint().attach(mockGame); .test('adds components from a child Blueprint the to a game on attach',
(game) async {
verify(() => mockGame.addAll(any())).called(1); final childBlueprint = Blueprint(
}); components: [
Component(),
test('adds components from a child Blueprint the to a game on attach', () { Component(),
final mockGame = MockForge2DGame(); ],
when(() => mockGame.addAll(any())).thenAnswer((_) async {}); );
MyComposedBlueprint().attach(mockGame); final parentBlueprint = Blueprint(
components: [
verify(() => mockGame.addAll(any())).called(4); Component(),
}); Component(),
],
test( blueprints: [
'throws assertion error when adding to an already attached blueprint', childBlueprint,
() async { ],
final mockGame = MockForge2DGame(); );
when(() => mockGame.addAll(any())).thenAnswer((_) async {});
final blueprint = MyBlueprint(); await game.addFromBlueprint(parentBlueprint);
await blueprint.attach(mockGame); await game.ready();
expect(() => blueprint.add(Component()), throwsAssertionError); expect(
expect(() => blueprint.addAll([Component()]), throwsAssertionError); game.children,
}, equals([
); ...parentBlueprint.components,
}); ...childBlueprint.components,
]),
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);
}); });
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,
);
},
);
}); });
} }

Loading…
Cancel
Save