Merge branch 'main' into refactor/moved-kicker-to-pinball-components

pull/133/head
Alejandro Santiago 4 years ago committed by GitHub
commit bb7936ebef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@ -23,6 +23,11 @@ abstract class ComponentController<T extends Component> extends Component {
); );
await super.addToParent(parent); await super.addToParent(parent);
} }
@override
Future<void> add(Component component) {
throw Exception('ComponentController cannot add other components.');
}
} }
/// Mixin that attaches a single [ComponentController] to a [Component]. /// Mixin that attaches a single [ComponentController] to a [Component].

@ -2,7 +2,7 @@ export 'board.dart';
export 'bonus_word.dart'; export 'bonus_word.dart';
export 'chrome_dino.dart'; export 'chrome_dino.dart';
export 'controlled_ball.dart'; export 'controlled_ball.dart';
export 'flipper_controller.dart'; export 'controlled_flipper.dart';
export 'flutter_forest.dart'; export 'flutter_forest.dart';
export 'jetpack_ramp.dart'; export 'jetpack_ramp.dart';
export 'launcher_ramp.dart'; export 'launcher_ramp.dart';

@ -2,45 +2,52 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flame/extensions.dart'; 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:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
/// A [Blueprint] which creates the [JetpackRamp]. /// A [Blueprint] which creates the [JetpackRamp].
class Jetpack extends Forge2DBlueprint { class Jetpack extends Forge2DBlueprint {
/// {@macro spaceship}
Jetpack();
static const int ballPriorityInsideRamp = 4;
@override @override
void build(_) { void build(_) {
final position = Vector2(
BoardDimensions.bounds.left + 40.5,
BoardDimensions.bounds.top - 31.5,
);
addAllContactCallback([ addAllContactCallback([
RampOpeningBallContactCallback<_JetpackRampOpening>(), RampOpeningBallContactCallback<_JetpackRampOpening>(),
]); ]);
final rightOpening = _JetpackRampOpening( final rightOpening = _JetpackRampOpening(
// TODO(ruimiguel): set Board priority when defined.
outsidePriority: 1,
rotation: math.pi, rotation: math.pi,
) )
..initialPosition = position + Vector2(12.9, -20) ..initialPosition = Vector2(1.7, 19)
..layer = Layer.opening; ..layer = Layer.opening;
final leftOpening = _JetpackRampOpening( final leftOpening = _JetpackRampOpening(
outsideLayer: Layer.spaceship, outsideLayer: Layer.spaceship,
outsidePriority: Spaceship.ballPriorityWhenOnSpaceship,
rotation: math.pi, rotation: math.pi,
) )
..initialPosition = position + Vector2(-2.5, -20) ..initialPosition = Vector2(-13.7, 19)
..layer = Layer.jetpack; ..layer = Layer.jetpack;
final jetpackRamp = JetpackRamp() final jetpackRamp = JetpackRamp();
..initialPosition = position + Vector2(5, -20.2)
..layer = Layer.jetpack; final jetpackRampWallFg = _JetpackRampForegroundRailing();
final baseRight = _JetpackBase()..initialPosition = Vector2(1.7, 20);
addAll([ addAll([
rightOpening, rightOpening,
leftOpening, leftOpening,
baseRight,
jetpackRamp, jetpackRamp,
jetpackRampWallFg,
]); ]);
} }
} }
@ -49,42 +56,45 @@ class Jetpack extends Forge2DBlueprint {
/// Represents the upper left blue ramp of the [Board]. /// Represents the upper left blue ramp of the [Board].
/// {@endtemplate} /// {@endtemplate}
class JetpackRamp extends BodyComponent with InitialPosition, Layered { class JetpackRamp extends BodyComponent with InitialPosition, Layered {
JetpackRamp() : super(priority: 2) { JetpackRamp() : super(priority: Jetpack.ballPriorityInsideRamp - 1) {
layer = Layer.jetpack; layer = Layer.jetpack;
paint = Paint()
..color = const Color.fromARGB(255, 8, 218, 241)
..style = PaintingStyle.stroke;
} }
/// Radius of the external arc.
static const _externalRadius = 18.0;
/// Width between walls of the ramp. /// Width between walls of the ramp.
static const width = 5.0; static const width = 5.0;
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[]; final fixturesDef = <FixtureDef>[];
final externalCurveShape = ArcShape( final outerLeftCurveShape = BezierCurveShape(
center: initialPosition, controlPoints: [
arcRadius: _externalRadius, Vector2(-30.95, 38),
angle: math.pi, Vector2(-32.5, 71.25),
rotation: math.pi, Vector2(-14.2, 71.25),
],
); );
final externalFixtureDef = FixtureDef(externalCurveShape);
fixturesDef.add(externalFixtureDef);
final internalCurveShape = externalCurveShape.copyWith( final outerLeftCurveFixtureDef = FixtureDef(outerLeftCurveShape);
arcRadius: _externalRadius - width, fixturesDef.add(outerLeftCurveFixtureDef);
final outerRightCurveShape = BezierCurveShape(
controlPoints: [
outerLeftCurveShape.vertices.last,
Vector2(4.7, 71.25),
Vector2(6.3, 40),
],
); );
final internalFixtureDef = FixtureDef(internalCurveShape);
fixturesDef.add(internalFixtureDef); final outerRightCurveFixtureDef = FixtureDef(outerRightCurveShape);
fixturesDef.add(outerRightCurveFixtureDef);
return fixturesDef; return fixturesDef;
} }
@override @override
Body createBody() { Body createBody() {
renderBody = false;
final bodyDef = BodyDef() final bodyDef = BodyDef()
..userData = this ..userData = this
..position = initialPosition; ..position = initialPosition;
@ -94,6 +104,140 @@ class JetpackRamp extends BodyComponent with InitialPosition, Layered {
return body; return body;
} }
@override
Future<void> onLoad() async {
await super.onLoad();
await _loadSprites();
}
Future<void> _loadSprites() async {
final spriteRamp = await gameRef.loadSprite(
Assets.images.components.spaceshipRamp.path,
);
final spriteRampComponent = SpriteComponent(
sprite: spriteRamp,
size: Vector2(38.1, 33.8),
anchor: Anchor.center,
position: Vector2(-12.2, -53.5),
);
final spriteRailingBg = await gameRef.loadSprite(
Assets.images.components.spaceshipRailingBg.path,
);
final spriteRailingBgComponent = SpriteComponent(
sprite: spriteRailingBg,
size: Vector2(38.3, 35.1),
anchor: Anchor.center,
position: spriteRampComponent.position + Vector2(0, -1),
);
await addAll([
spriteRailingBgComponent,
spriteRampComponent,
]);
}
}
class _JetpackRampForegroundRailing extends BodyComponent
with InitialPosition, Layered {
_JetpackRampForegroundRailing()
: super(priority: Jetpack.ballPriorityInsideRamp + 1) {
layer = Layer.jetpack;
}
List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
final innerLeftCurveShape = BezierCurveShape(
controlPoints: [
Vector2(-24.5, 38),
Vector2(-26.3, 64),
Vector2(-13.8, 64.5),
],
);
final innerLeftCurveFixtureDef = FixtureDef(innerLeftCurveShape);
fixturesDef.add(innerLeftCurveFixtureDef);
final innerRightCurveShape = BezierCurveShape(
controlPoints: [
innerLeftCurveShape.vertices.last,
Vector2(-1, 64.5),
Vector2(0.1, 39.5),
],
);
final innerRightCurveFixtureDef = FixtureDef(innerRightCurveShape);
fixturesDef.add(innerRightCurveFixtureDef);
return fixturesDef;
}
@override
Body createBody() {
renderBody = false;
final bodyDef = BodyDef()
..userData = this
..position = initialPosition;
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
@override
Future<void> onLoad() async {
await super.onLoad();
await _loadSprites();
}
Future<void> _loadSprites() async {
final spriteRailingFg = await gameRef.loadSprite(
Assets.images.components.spaceshipRailingFg.path,
);
final spriteRailingFgComponent = SpriteComponent(
sprite: spriteRailingFg,
size: Vector2(26.1, 28.3),
anchor: Anchor.center,
position: Vector2(-12.2, -52.5),
);
await add(spriteRailingFgComponent);
}
}
class _JetpackBase extends BodyComponent with InitialPosition, Layered {
_JetpackBase() {
layer = Layer.board;
}
@override
Body createBody() {
renderBody = false;
const baseWidth = 6;
final baseShape = BezierCurveShape(
controlPoints: [
Vector2(initialPosition.x - baseWidth / 2, initialPosition.y),
Vector2(initialPosition.x - baseWidth / 2, initialPosition.y) +
Vector2(2, 2),
Vector2(initialPosition.x + baseWidth / 2, initialPosition.y) +
Vector2(-2, 2),
Vector2(initialPosition.x + baseWidth / 2, initialPosition.y)
],
);
final fixtureDef = FixtureDef(baseShape);
final bodyDef = BodyDef()
..userData = this
..position = initialPosition;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
} }
/// {@template jetpack_ramp_opening} /// {@template jetpack_ramp_opening}
@ -104,12 +248,15 @@ class _JetpackRampOpening extends RampOpening {
/// {@macro jetpack_ramp_opening} /// {@macro jetpack_ramp_opening}
_JetpackRampOpening({ _JetpackRampOpening({
Layer? outsideLayer, Layer? outsideLayer,
int? outsidePriority,
required double rotation, required double rotation,
}) : _rotation = rotation, }) : _rotation = rotation,
super( super(
insideLayer: Layer.jetpack, insideLayer: Layer.jetpack,
outsideLayer: outsideLayer, outsideLayer: outsideLayer,
orientation: RampOrientation.down, orientation: RampOrientation.down,
insidePriority: Jetpack.ballPriorityInsideRamp,
outsidePriority: outsidePriority,
); );
final double _rotation; final double _rotation;
@ -117,7 +264,9 @@ class _JetpackRampOpening extends RampOpening {
static final Vector2 _size = Vector2(JetpackRamp.width / 4, .1); static final Vector2 _size = Vector2(JetpackRamp.width / 4, .1);
@override @override
Shape get shape => PolygonShape() Shape get shape {
renderBody = false;
return PolygonShape()
..setAsBox( ..setAsBox(
_size.x, _size.x,
_size.y, _size.y,
@ -125,3 +274,4 @@ class _JetpackRampOpening extends RampOpening {
_rotation, _rotation,
); );
} }
}

@ -124,6 +124,7 @@ class _LauncherRampOpening extends RampOpening {
super( super(
insideLayer: Layer.launcher, insideLayer: Layer.launcher,
orientation: RampOrientation.down, orientation: RampOrientation.down,
insidePriority: 3,
); );
final double _rotation; final double _rotation;

@ -1,10 +1,10 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui';
import 'package:flame/extensions.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets; import 'package:pinball_components/pinball_components.dart' hide Assets;
/// {@template spaceship_exit_rail} /// {@template spaceship_exit_rail}
@ -12,10 +12,10 @@ import 'package:pinball_components/pinball_components.dart' hide Assets;
/// {@endtemplate} /// {@endtemplate}
class SpaceshipExitRail extends Forge2DBlueprint { class SpaceshipExitRail extends Forge2DBlueprint {
/// {@macro spaceship_exit_rail} /// {@macro spaceship_exit_rail}
SpaceshipExitRail({required this.position}); SpaceshipExitRail();
/// The [position] where the elements will be created /// Base priority for wall while be on jetpack ramp.
final Vector2 position; static const ballPriorityWhenOnSpaceshipExitRail = 2;
@override @override
void build(_) { void build(_) {
@ -23,127 +23,101 @@ class SpaceshipExitRail extends Forge2DBlueprint {
SpaceshipExitRailEndBallContactCallback(), SpaceshipExitRailEndBallContactCallback(),
]); ]);
final spaceshipExitRailRamp = _SpaceshipExitRailRamp() final exitRailRamp = _SpaceshipExitRailRamp();
..initialPosition = position; final exitRailEnd = SpaceshipExitRailEnd();
final exitRail = SpaceshipExitRailEnd() final topBase = _SpaceshipExitRailBase(radius: 0.55)
..initialPosition = position + _SpaceshipExitRailRamp.exitPoint; ..initialPosition = Vector2(-26.15, 18.65);
final bottomBase = _SpaceshipExitRailBase(radius: 0.8)
..initialPosition = Vector2(-25.5, -12.9);
addAll([ addAll([
spaceshipExitRailRamp, exitRailRamp,
exitRail, exitRailEnd,
topBase,
bottomBase,
]); ]);
} }
} }
class _SpaceshipExitRailRamp extends BodyComponent class _SpaceshipExitRailRamp extends BodyComponent
with InitialPosition, Layered { with InitialPosition, Layered {
_SpaceshipExitRailRamp() : super(priority: 2) { _SpaceshipExitRailRamp()
: super(
priority: SpaceshipExitRail.ballPriorityWhenOnSpaceshipExitRail - 1,
) {
renderBody = false;
layer = Layer.spaceshipExitRail; layer = Layer.spaceshipExitRail;
// TODO(ruimiguel): remove color once asset is placed.
paint = Paint()
..color = const Color.fromARGB(255, 249, 65, 3)
..style = PaintingStyle.stroke;
} }
static final exitPoint = Vector2(9.2, -48.5);
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
const entranceRotationAngle = 175 * math.pi / 180;
const curveRotationAngle = 275 * math.pi / 180;
const exitRotationAngle = 340 * math.pi / 180;
const width = 5.5;
final fixturesDefs = <FixtureDef>[]; final fixturesDefs = <FixtureDef>[];
final entranceWall = ArcShape( final topArcShape = ArcShape(
center: Vector2(width / 2, 0), center: Vector2(-35.5, 30.9),
arcRadius: width / 2, arcRadius: 2.5,
angle: math.pi, angle: math.pi,
rotation: entranceRotationAngle, rotation: 2.9,
); );
final entranceFixtureDef = FixtureDef(entranceWall); final topArcFixtureDef = FixtureDef(topArcShape);
fixturesDefs.add(entranceFixtureDef); fixturesDefs.add(topArcFixtureDef);
final topLeftControlPoints = [
Vector2(0, 0),
Vector2(10, .5),
Vector2(7, 4),
Vector2(15.5, 8.3),
];
final topLeftCurveShape = BezierCurveShape( final topLeftCurveShape = BezierCurveShape(
controlPoints: topLeftControlPoints, controlPoints: [
)..rotate(curveRotationAngle); Vector2(-37.9, 30.4),
final topLeftFixtureDef = FixtureDef(topLeftCurveShape); Vector2(-38, 23.9),
fixturesDefs.add(topLeftFixtureDef); Vector2(-30.93, 18.2),
],
final topRightControlPoints = [ );
Vector2(0, width), final topLeftCurveFixtureDef = FixtureDef(topLeftCurveShape);
Vector2(10, 6.5), fixturesDefs.add(topLeftCurveFixtureDef);
Vector2(7, 10),
Vector2(15.5, 13.2), final middleLeftCurveShape = BezierCurveShape(
]; controlPoints: [
final topRightCurveShape = BezierCurveShape( Vector2(-30.93, 18.2),
controlPoints: topRightControlPoints, Vector2(-22.6, 10.3),
)..rotate(curveRotationAngle); Vector2(-30, 0.2),
final topRightFixtureDef = FixtureDef(topRightCurveShape); ],
fixturesDefs.add(topRightFixtureDef); );
final middleLeftCurveFixtureDef = FixtureDef(middleLeftCurveShape);
final mediumLeftControlPoints = [ fixturesDefs.add(middleLeftCurveFixtureDef);
topLeftControlPoints.last,
Vector2(21, 12.9),
Vector2(30, 7.1),
Vector2(32, 4.8),
];
final mediumLeftCurveShape = BezierCurveShape(
controlPoints: mediumLeftControlPoints,
)..rotate(curveRotationAngle);
final mediumLeftFixtureDef = FixtureDef(mediumLeftCurveShape);
fixturesDefs.add(mediumLeftFixtureDef);
final mediumRightControlPoints = [
topRightControlPoints.last,
Vector2(21, 17.2),
Vector2(30, 12.1),
Vector2(32, 10.2),
];
final mediumRightCurveShape = BezierCurveShape(
controlPoints: mediumRightControlPoints,
)..rotate(curveRotationAngle);
final mediumRightFixtureDef = FixtureDef(mediumRightCurveShape);
fixturesDefs.add(mediumRightFixtureDef);
final bottomLeftControlPoints = [
mediumLeftControlPoints.last,
Vector2(40, -1),
Vector2(48, 1.9),
Vector2(50.5, 2.5),
];
final bottomLeftCurveShape = BezierCurveShape( final bottomLeftCurveShape = BezierCurveShape(
controlPoints: bottomLeftControlPoints, controlPoints: [
)..rotate(curveRotationAngle); Vector2(-30, 0.2),
final bottomLeftFixtureDef = FixtureDef(bottomLeftCurveShape); Vector2(-36, -8.6),
fixturesDefs.add(bottomLeftFixtureDef); Vector2(-32.04, -18.3),
],
final bottomRightControlPoints = [ );
mediumRightControlPoints.last, final bottomLeftCurveFixtureDef = FixtureDef(bottomLeftCurveShape);
Vector2(40, 4), fixturesDefs.add(bottomLeftCurveFixtureDef);
Vector2(46, 6.5),
Vector2(48.8, 7.6), final topRightStraightShape = EdgeShape()
]; ..set(
Vector2(-33, 31.3),
Vector2(-27.2, 21.3),
);
final topRightStraightFixtureDef = FixtureDef(topRightStraightShape);
fixturesDefs.add(topRightStraightFixtureDef);
final middleRightCurveShape = BezierCurveShape(
controlPoints: [
Vector2(-27.2, 21.3),
Vector2(-16.5, 11.4),
Vector2(-25.29, -1.7),
],
);
final middleRightCurveFixtureDef = FixtureDef(middleRightCurveShape);
fixturesDefs.add(middleRightCurveFixtureDef);
final bottomRightCurveShape = BezierCurveShape( final bottomRightCurveShape = BezierCurveShape(
controlPoints: bottomRightControlPoints, controlPoints: [
)..rotate(curveRotationAngle); Vector2(-25.29, -1.7),
final bottomRightFixtureDef = FixtureDef(bottomRightCurveShape); Vector2(-29.91, -8.5),
fixturesDefs.add(bottomRightFixtureDef); Vector2(-26.8, -15.7),
],
final exitWall = ArcShape(
center: exitPoint,
arcRadius: width / 2,
angle: math.pi,
rotation: exitRotationAngle,
); );
final exitFixtureDef = FixtureDef(exitWall); final bottomRightCurveFixtureDef = FixtureDef(bottomRightCurveShape);
fixturesDefs.add(exitFixtureDef); fixturesDefs.add(bottomRightCurveFixtureDef);
return fixturesDefs; return fixturesDefs;
} }
@ -159,6 +133,52 @@ class _SpaceshipExitRailRamp extends BodyComponent
return body; return body;
} }
@override
Future<void> onLoad() async {
await super.onLoad();
await _loadSprite();
}
Future<void> _loadSprite() async {
final sprite = await gameRef.loadSprite(
Assets.images.components.spaceshipDropTube.path,
);
final spriteComponent = SpriteComponent(
sprite: sprite,
size: Vector2(17.5, 55.7),
anchor: Anchor.center,
position: Vector2(-29.4, -5.7),
);
await add(spriteComponent);
}
}
class _SpaceshipExitRailBase extends BodyComponent
with InitialPosition, Layered {
_SpaceshipExitRailBase({required this.radius})
: super(
priority: SpaceshipExitRail.ballPriorityWhenOnSpaceshipExitRail + 1,
) {
renderBody = false;
layer = Layer.board;
}
final double radius;
@override
Body createBody() {
final shape = CircleShape()..radius = radius;
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef()
..position = initialPosition
..userData = this;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
} }
/// {@template spaceship_exit_rail_end} /// {@template spaceship_exit_rail_end}
@ -171,13 +191,20 @@ class SpaceshipExitRailEnd extends RampOpening {
: super( : super(
insideLayer: Layer.spaceshipExitRail, insideLayer: Layer.spaceshipExitRail,
orientation: RampOrientation.down, orientation: RampOrientation.down,
insidePriority: 3,
) { ) {
renderBody = false;
layer = Layer.spaceshipExitRail; layer = Layer.spaceshipExitRail;
} }
@override @override
Shape get shape { Shape get shape {
return CircleShape()..radius = 1; return ArcShape(
center: Vector2(-29, -17.8),
arcRadius: 2.5,
angle: math.pi * 0.8,
rotation: -0.16,
);
} }
} }
@ -191,8 +218,7 @@ class SpaceshipExitRailEndBallContactCallback
@override @override
void begin(SpaceshipExitRailEnd exitRail, Ball ball, _) { void begin(SpaceshipExitRailEnd exitRail, Ball ball, _) {
ball ball
..priority = 1 ..sendTo(exitRail.outsidePriority)
..gameRef.reorderChildren()
..layer = exitRail.outsideLayer; ..layer = exitRail.outsideLayer;
} }
} }

@ -22,6 +22,10 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.dashBumper.main.active.keyName), images.load(components.Assets.images.dashBumper.main.active.keyName),
images.load(components.Assets.images.dashBumper.main.inactive.keyName), images.load(components.Assets.images.dashBumper.main.inactive.keyName),
images.load(Assets.images.components.background.path), images.load(Assets.images.components.background.path),
images.load(Assets.images.components.spaceshipRamp.path),
images.load(Assets.images.components.spaceshipRailingBg.path),
images.load(Assets.images.components.spaceshipRailingFg.path),
images.load(Assets.images.components.spaceshipDropTube.path),
]); ]);
} }
} }

@ -45,9 +45,7 @@ class PinballGame extends Forge2DGame
); );
unawaited( unawaited(
addFromBlueprint( addFromBlueprint(
SpaceshipExitRail( SpaceshipExitRail(),
position: Vector2(-34.3, 23.8),
),
), ),
); );
@ -94,7 +92,9 @@ class PinballGame extends Forge2DGame
} }
Future<void> _addPaths() async { Future<void> _addPaths() async {
unawaited(addFromBlueprint(Jetpack())); unawaited(
addFromBlueprint(Jetpack()),
);
unawaited(addFromBlueprint(Launcher())); unawaited(addFromBlueprint(Launcher()));
} }

@ -20,6 +20,22 @@ class $AssetsImagesComponentsGen {
/// File path: assets/images/components/background.png /// File path: assets/images/components/background.png
AssetGenImage get background => AssetGenImage get background =>
const AssetGenImage('assets/images/components/background.png'); const AssetGenImage('assets/images/components/background.png');
/// File path: assets/images/components/spaceship-drop-tube.png
AssetGenImage get spaceshipDropTube =>
const AssetGenImage('assets/images/components/spaceship-drop-tube.png');
/// File path: assets/images/components/spaceship_railing_bg.png
AssetGenImage get spaceshipRailingBg =>
const AssetGenImage('assets/images/components/spaceship_railing_bg.png');
/// File path: assets/images/components/spaceship_railing_fg.png
AssetGenImage get spaceshipRailingFg =>
const AssetGenImage('assets/images/components/spaceship_railing_fg.png');
/// File path: assets/images/components/spaceship_ramp.png
AssetGenImage get spaceshipRamp =>
const AssetGenImage('assets/images/components/spaceship_ramp.png');
} }
class Assets { class Assets {

@ -114,9 +114,9 @@ void main() {
Vector2(0, 0), Vector2(0, 0),
Vector2(10, 10), Vector2(10, 10),
], ],
step: 0.001, step: 0.02,
); );
expect(points.length, 1000); expect(points.length, 50);
}); });
}); });

@ -83,8 +83,9 @@ class Ball<T extends Forge2DGame> extends BodyComponent<T>
final direction = body.linearVelocity.normalized(); final direction = body.linearVelocity.normalized();
final effect = FireEffect( final effect = FireEffect(
burstPower: _boostTimer, burstPower: _boostTimer,
direction: direction, direction: -direction,
position: body.position, position: Vector2(body.position.x, -body.position.y),
priority: priority - 1,
); );
unawaited(gameRef.add(effect)); unawaited(gameRef.add(effect));

@ -1,5 +1,6 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
import 'package:flame/particles.dart'; import 'package:flame/particles.dart';
import 'package:flame_forge2d/flame_forge2d.dart' hide Particle; import 'package:flame_forge2d/flame_forge2d.dart' hide Particle;
@ -19,33 +20,24 @@ const _particleRadius = 0.25;
/// A [BodyComponent] which creates a fire trail effect using the given /// A [BodyComponent] which creates a fire trail effect using the given
/// parameters /// parameters
/// {@endtemplate} /// {@endtemplate}
class FireEffect extends BodyComponent { class FireEffect extends ParticleSystemComponent {
/// {@macro fire_effect} /// {@macro fire_effect}
FireEffect({ FireEffect({
required this.burstPower, required this.burstPower,
required this.position,
required this.direction, required this.direction,
}); Vector2? position,
int? priority,
}) : super(
position: position,
priority: priority,
);
/// A [double] value that will define how "strong" the burst of particles /// A [double] value that will define how "strong" the burst of particles
/// will be /// will be.
final double burstPower; final double burstPower;
/// The position of the burst /// Which direction the burst will aim.
final Vector2 position;
/// Which direction the burst will aim
final Vector2 direction; final Vector2 direction;
late Particle _particle;
@override
Body createBody() {
final bodyDef = BodyDef()..position = position;
final fixtureDef = FixtureDef(CircleShape()..radius = 0)..isSensor = true;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
@ -71,15 +63,15 @@ class FireEffect extends BodyComponent {
); );
}), }),
]; ];
final rng = math.Random(); final random = math.Random();
final spreadTween = Tween<double>(begin: -0.2, end: 0.2); final spreadTween = Tween<double>(begin: -0.2, end: 0.2);
_particle = Particle.generate( particle = Particle.generate(
count: (rng.nextDouble() * (burstPower * 10)).toInt(), count: math.max((random.nextDouble() * (burstPower * 10)).toInt(), 1),
generator: (_) { generator: (_) {
final spread = Vector2( final spread = Vector2(
spreadTween.transform(rng.nextDouble()), spreadTween.transform(random.nextDouble()),
spreadTween.transform(rng.nextDouble()), spreadTween.transform(random.nextDouble()),
); );
final finalDirection = Vector2(direction.x, -direction.y) + spread; final finalDirection = Vector2(direction.x, -direction.y) + spread;
final speed = finalDirection * (burstPower * 20); final speed = finalDirection * (burstPower * 20);
@ -88,26 +80,9 @@ class FireEffect extends BodyComponent {
lifespan: 5 / burstPower, lifespan: 5 / burstPower,
position: Vector2.zero(), position: Vector2.zero(),
speed: speed, speed: speed,
child: children[rng.nextInt(children.length)], child: children[random.nextInt(children.length)],
); );
}, },
); );
} }
@override
void update(double dt) {
super.update(dt);
_particle.update(dt);
if (_particle.shouldRemove) {
removeFromParent();
}
}
@override
void render(Canvas canvas) {
super.render(canvas);
_particle.render(canvas);
}
} }

@ -185,8 +185,9 @@ class SpaceshipHole extends RampOpening {
: super( : super(
insideLayer: Layer.spaceship, insideLayer: Layer.spaceship,
outsideLayer: outsideLayer, outsideLayer: outsideLayer,
outsidePriority: outsidePriority,
orientation: RampOrientation.up, orientation: RampOrientation.up,
insidePriority: 4,
outsidePriority: outsidePriority,
) { ) {
renderBody = false; renderBody = false;
layer = Layer.spaceship; layer = Layer.spaceship;
@ -195,8 +196,8 @@ class SpaceshipHole extends RampOpening {
@override @override
Shape get shape { Shape get shape {
return ArcShape( return ArcShape(
center: Vector2(0, 4.2), center: Vector2(0, 3.2),
arcRadius: 6, arcRadius: 5,
angle: 1, angle: 1,
rotation: 60 * pi / 180, rotation: 60 * pi / 180,
); );

@ -34,7 +34,6 @@ class _EffectEmitter extends Component {
add( add(
FireEffect( FireEffect(
burstPower: (_timer / _timerLimit) * _force, burstPower: (_timer / _timerLimit) * _force,
position: Vector2.zero(),
direction: _direction, direction: _direction,
), ),
); );

@ -1,21 +1,39 @@
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class BasicFlipperGame extends BasicGame { import 'package:sandbox/stories/ball/basic.dart';
class BasicFlipperGame extends BasicBallGame with KeyboardEvents {
BasicFlipperGame() : super(color: Colors.blue);
static const info = ''' static const info = '''
Basic example of how a Flipper works. Basic example of how a Flipper works.
'''; ''';
static const _leftFlipperKeys = [
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.keyA,
];
static const _rightFlipperKeys = [
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.keyD,
];
late Flipper leftFlipper;
late Flipper rightFlipper;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final center = screenToWorld(camera.viewport.canvasSize! / 2); final center = screenToWorld(camera.viewport.canvasSize! / 2);
final leftFlipper = Flipper(side: BoardSide.left) leftFlipper = Flipper(side: BoardSide.left)
..initialPosition = center - Vector2(Flipper.size.x, 0); ..initialPosition = center - Vector2(Flipper.size.x, 0);
final rightFlipper = Flipper(side: BoardSide.right) rightFlipper = Flipper(side: BoardSide.right)
..initialPosition = center + Vector2(Flipper.size.x, 0); ..initialPosition = center + Vector2(Flipper.size.x, 0);
await addAll([ await addAll([
@ -23,4 +41,32 @@ class BasicFlipperGame extends BasicGame {
rightFlipper, rightFlipper,
]); ]);
} }
@override
KeyEventResult onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
final movedLeftFlipper = _leftFlipperKeys.contains(event.logicalKey);
if (movedLeftFlipper) {
if (event is RawKeyDownEvent) {
leftFlipper.moveUp();
} else if (event is RawKeyUpEvent) {
leftFlipper.moveDown();
}
}
final movedRightFlipper = _rightFlipperKeys.contains(event.logicalKey);
if (movedRightFlipper) {
if (event is RawKeyDownEvent) {
rightFlipper.moveUp();
} else if (event is RawKeyUpEvent) {
rightFlipper.moveDown();
}
}
return movedLeftFlipper || movedRightFlipper
? KeyEventResult.handled
: KeyEventResult.ignored;
}
} }

@ -3,10 +3,9 @@ import 'dart:async';
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:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:sandbox/stories/flipper/basic.dart';
import 'package:sandbox/common/common.dart';
class FlipperTracingGame extends BasicGame { class FlipperTracingGame extends BasicFlipperGame {
static const info = ''' static const info = '''
Basic example of how the Flipper body overlays the sprite. Basic example of how the Flipper body overlays the sprite.
'''; ''';
@ -14,17 +13,6 @@ class FlipperTracingGame extends BasicGame {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final center = screenToWorld(camera.viewport.canvasSize! / 2);
final leftFlipper = Flipper(side: BoardSide.left)
..initialPosition = center - Vector2(Flipper.size.x, 0);
final rightFlipper = Flipper(side: BoardSide.right)
..initialPosition = center + Vector2(Flipper.size.x, 0);
await addAll([
leftFlipper,
rightFlipper,
]);
leftFlipper.trace(); leftFlipper.trace();
rightFlipper.trace(); rightFlipper.trace();
} }
@ -37,7 +25,6 @@ extension on BodyComponent {
body.joints.whereType<RevoluteJoint>().forEach( body.joints.whereType<RevoluteJoint>().forEach(
(joint) => joint.setLimits(0, 0), (joint) => joint.setLimits(0, 0),
); );
body.setType(BodyType.static);
unawaited( unawaited(
mounted.whenComplete(() { mounted.whenComplete(() {

@ -1,12 +1,8 @@
import 'dart:ui';
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:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
class MockCanvas extends Mock implements Canvas {}
class MockFilter extends Mock implements Filter {} class MockFilter extends Mock implements Filter {}
class MockFixture extends Mock implements Fixture {} class MockFixture extends Mock implements Fixture {}

@ -1,11 +1,8 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'dart:ui';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_test/flame_test.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_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart'; import '../../helpers/helpers.dart';
@ -14,43 +11,16 @@ void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new); final flameTester = FlameTester(TestGame.new);
setUpAll(() { flameTester.test(
registerFallbackValue(Offset.zero); 'loads correctly',
registerFallbackValue(Paint()); (game) async {
}); final fireEffect = FireEffect(
group('FireEffect', () {
flameTester.test('is removed once its particles are done', (game) async {
await game.ensureAdd(
FireEffect(
burstPower: 1, burstPower: 1,
position: Vector2.zero(), direction: Vector2.zero(),
direction: Vector2.all(2),
),
); );
await game.ready(); await game.ensureAdd(fireEffect);
expect(game.children.whereType<FireEffect>().length, equals(1));
game.update(5);
await game.ready();
expect(game.children.whereType<FireEffect>().length, equals(0));
});
flameTester.test('render circles on the canvas', (game) async {
final effect = FireEffect(
burstPower: 1,
position: Vector2.zero(),
direction: Vector2.all(2),
);
await game.ensureAdd(effect);
await game.ready();
final canvas = MockCanvas();
effect.render(canvas);
verify(() => canvas.drawCircle(any(), any(), any())).called( expect(game.contains(fireEffect), isTrue);
greaterThan(0), },
); );
});
});
} }

@ -31,6 +31,7 @@ void main() {
); );
}, },
); );
flameTester.test( flameTester.test(
'throws AssertionError when not attached to controlled component', 'throws AssertionError when not attached to controlled component',
(game) async { (game) async {
@ -44,6 +45,35 @@ void main() {
); );
}, },
); );
flameTester.test(
'throws Exception when adding a component',
(game) async {
final component = ControlledComponent();
final controller = TestComponentController(component);
await expectLater(
() async => controller.add(Component()),
throwsException,
);
},
);
flameTester.test(
'throws Exception when adding multiple components',
(game) async {
final component = ControlledComponent();
final controller = TestComponentController(component);
await expectLater(
() async => controller.addAll([
Component(),
Component(),
]),
throwsException,
);
},
);
}); });
group('Controls', () { group('Controls', () {

@ -196,10 +196,10 @@ void main() {
group('bonus letter activation', () { group('bonus letter activation', () {
late GameBloc gameBloc; late GameBloc gameBloc;
final tester = flameBlocTester<PinballGame>( final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
// TODO(alestiago): Use TestGame once BonusLetter has controller. // TODO(alestiago): Use TestGame once BonusLetter has controller.
game: PinballGameTest.create, gameBuilder: PinballGameTest.create,
gameBloc: () => gameBloc, blocBuilder: () => gameBloc,
); );
setUp(() { setUp(() {
@ -211,7 +211,7 @@ void main() {
); );
}); });
tester.testGameWidget( flameBlocTester.testGameWidget(
'adds BonusLetterActivated to GameBloc when not activated', 'adds BonusLetterActivated to GameBloc when not activated',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.ready(); await game.ready();
@ -225,7 +225,7 @@ void main() {
}, },
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
"doesn't add BonusLetterActivated to GameBloc when already activated", "doesn't add BonusLetterActivated to GameBloc when already activated",
setUp: (game, tester) async { setUp: (game, tester) async {
const state = GameState( const state = GameState(
@ -253,7 +253,7 @@ void main() {
}, },
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'adds a ColorEffect', 'adds a ColorEffect',
setUp: (game, tester) async { setUp: (game, tester) async {
const state = GameState( const state = GameState(
@ -284,7 +284,7 @@ void main() {
}, },
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'only listens when there is a change on the letter status', 'only listens when there is a change on the letter status',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.ready(); await game.ready();

@ -66,12 +66,12 @@ void main() {
); );
}); });
final tester = flameBlocTester<PinballGame>( final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
game: PinballGameTest.create, gameBuilder: PinballGameTest.create,
gameBloc: () => gameBloc, blocBuilder: () => gameBloc,
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'lost adds BallLost to GameBloc', 'lost adds BallLost to GameBloc',
setUp: (game, tester) async { setUp: (game, tester) async {
final controller = LaunchedBallController(ball); final controller = LaunchedBallController(ball);
@ -86,7 +86,7 @@ void main() {
); );
group('listenWhen', () { group('listenWhen', () {
tester.testGameWidget( flameBlocTester.testGameWidget(
'listens when a ball has been lost', 'listens when a ball has been lost',
setUp: (game, tester) async { setUp: (game, tester) async {
final controller = LaunchedBallController(ball); final controller = LaunchedBallController(ball);
@ -107,7 +107,7 @@ void main() {
}, },
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'does not listen when a ball has not been lost', 'does not listen when a ball has not been lost',
setUp: (game, tester) async { setUp: (game, tester) async {
final controller = LaunchedBallController(ball); final controller = LaunchedBallController(ball);
@ -130,7 +130,7 @@ void main() {
}); });
group('onNewState', () { group('onNewState', () {
tester.testGameWidget( flameBlocTester.testGameWidget(
'removes ball', 'removes ball',
setUp: (game, tester) async { setUp: (game, tester) async {
final controller = LaunchedBallController(ball); final controller = LaunchedBallController(ball);
@ -147,7 +147,7 @@ void main() {
}, },
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'spawns a new ball when the ball is not the last one', 'spawns a new ball when the ball is not the last one',
setUp: (game, tester) async { setUp: (game, tester) async {
final controller = LaunchedBallController(ball); final controller = LaunchedBallController(ball);
@ -168,7 +168,7 @@ void main() {
}, },
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'does not spawn a new ball is the last one', 'does not spawn a new ball is the last one',
setUp: (game, tester) async { setUp: (game, tester) async {
final controller = LaunchedBallController(ball); final controller = LaunchedBallController(ball);

@ -86,12 +86,12 @@ void main() {
group('controller', () { group('controller', () {
group('listenWhen', () { group('listenWhen', () {
final gameBloc = MockGameBloc(); final gameBloc = MockGameBloc();
final tester = flameBlocTester( final flameBlocTester = FlameBlocTester<TestGame, GameBloc>(
game: TestGame.new, gameBuilder: TestGame.new,
gameBloc: () => gameBloc, blocBuilder: () => gameBloc,
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'listens when a Bonus.dashNest is added', 'listens when a Bonus.dashNest is added',
verify: (game, tester) async { verify: (game, tester) async {
final flutterForest = FlutterForest(); final flutterForest = FlutterForest();
@ -145,12 +145,12 @@ void main() {
); );
}); });
final tester = flameBlocTester<PinballGame>( final flameBlocTester = FlameBlocTester<PinballGame, GameBloc>(
game: PinballGameTest.create, gameBuilder: PinballGameTest.create,
gameBloc: () => gameBloc, blocBuilder: () => gameBloc,
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'add DashNestActivated event', 'add DashNestActivated event',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.ready(); await game.ready();
@ -171,7 +171,7 @@ void main() {
}, },
); );
tester.testGameWidget( flameBlocTester.testGameWidget(
'add Scored event', 'add Scored event',
setUp: (game, tester) async { setUp: (game, tester) async {
final flutterForest = FlutterForest(); final flutterForest = FlutterForest();

@ -31,29 +31,33 @@ void main() {
when(() => fixture.filterData).thenReturn(filterData); when(() => fixture.filterData).thenReturn(filterData);
}); });
// TODO(alestiago): Make ContactCallback private and use `beginContact`
// instead.
group('SpaceshipExitHoleBallContactCallback', () { group('SpaceshipExitHoleBallContactCallback', () {
test('changes the ball priority on contact', () { setUp(() {
when(() => ball.priority).thenReturn(1);
when(() => exitRailEnd.outsideLayer).thenReturn(Layer.board); when(() => exitRailEnd.outsideLayer).thenReturn(Layer.board);
when(() => exitRailEnd.outsidePriority).thenReturn(0);
});
test('changes the ball priority on contact', () {
SpaceshipExitRailEndBallContactCallback().begin( SpaceshipExitRailEndBallContactCallback().begin(
exitRailEnd, exitRailEnd,
ball, ball,
MockContact(), MockContact(),
); );
verify(() => ball.priority = 1).called(1); verify(() => ball.sendTo(exitRailEnd.outsidePriority)).called(1);
}); });
test('reorders the game children', () { test('changes the ball layer on contact', () {
when(() => exitRailEnd.outsideLayer).thenReturn(Layer.board);
SpaceshipExitRailEndBallContactCallback().begin( SpaceshipExitRailEndBallContactCallback().begin(
exitRailEnd, exitRailEnd,
ball, ball,
MockContact(), MockContact(),
); );
verify(game.reorderChildren).called(1); verify(() => ball.layer = exitRailEnd.outsideLayer).called(1);
}); });
}); });
}); });

@ -1,18 +1,18 @@
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame/src/game/flame_game.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
FlameTester<T> flameBlocTester<T extends Forge2DGame>({ class FlameBlocTester<T extends FlameGame, B extends Bloc<dynamic, dynamic>>
required T Function() game, extends FlameTester<T> {
required GameBloc Function() gameBloc, FlameBlocTester({
}) { required GameCreateFunction<T> gameBuilder,
return FlameTester<T>( required B Function() blocBuilder,
game, }) : super(
gameBuilder,
pumpWidget: (gameWidget, tester) async { pumpWidget: (gameWidget, tester) async {
await tester.pumpWidget( await tester.pumpWidget(
BlocProvider.value( BlocProvider.value(
value: gameBloc(), value: blocBuilder(),
child: gameWidget, child: gameWidget,
), ),
); );

Loading…
Cancel
Save