Merge branch 'main' into feat/baseboards

pull/34/head
Allison Ryan 4 years ago
commit 3947796f73

@ -1,12 +1,45 @@
import 'dart:async'; import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:flame/components.dart' show SpriteComponent; import 'package:flame/components.dart' show PositionComponent, SpriteComponent;
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
/// {@template flipper_group}
/// Loads a [Flipper.right] and a [Flipper.left].
/// {@endtemplate}
class FlipperGroup extends PositionComponent {
/// {@macro flipper_group}
FlipperGroup({
required Vector2 position,
required this.spacing,
}) : super(position: position);
/// The amount of space between the [Flipper.right] and [Flipper.left].
final double spacing;
@override
Future<void> onLoad() async {
final leftFlipper = Flipper.left(
position: Vector2(
position.x - (Flipper.width / 2) - (spacing / 2),
position.y,
),
);
await add(leftFlipper);
final rightFlipper = Flipper.right(
position: Vector2(
position.x + (Flipper.width / 2) + (spacing / 2),
position.y,
),
);
await add(rightFlipper);
}
}
/// {@template flipper} /// {@template flipper}
/// A bat, typically found in pairs at the bottom of the board. /// A bat, typically found in pairs at the bottom of the board.
/// ///
@ -76,20 +109,6 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
/// [onKeyEvent] method listens to when one of these keys is pressed. /// [onKeyEvent] method listens to when one of these keys is pressed.
final List<LogicalKeyboardKey> _keys; final List<LogicalKeyboardKey> _keys;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(spritePath);
positionComponent = SpriteComponent(
sprite: sprite,
size: size,
);
if (side == BoardSide.right) {
positionComponent?.flipHorizontally();
}
}
/// Applies downward linear velocity to the [Flipper], moving it to its /// Applies downward linear velocity to the [Flipper], moving it to its
/// resting position. /// resting position.
void _moveDown() { void _moveDown() {
@ -102,15 +121,50 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
body.linearVelocity = Vector2(0, _speed); body.linearVelocity = Vector2(0, _speed);
} }
/// Loads the sprite that renders with the [Flipper].
Future<void> _loadSprite() async {
final sprite = await gameRef.loadSprite(spritePath);
positionComponent = SpriteComponent(
sprite: sprite,
size: size,
);
if (side.isRight) {
positionComponent!.flipHorizontally();
}
}
/// Anchors the [Flipper] to the [RevoluteJoint] that controls its arc motion.
Future<void> _anchorToJoint() async {
final anchor = FlipperAnchor(flipper: this);
await add(anchor);
final jointDef = FlipperAnchorRevoluteJointDef(
flipper: this,
anchor: anchor,
);
// TODO(alestiago): Remove casting once the following is closed:
// https://github.com/flame-engine/forge2d/issues/36
final joint = world.createJoint(jointDef) as RevoluteJoint;
// FIXME(erickzanardo): when mounted the initial position is not fully
// reached.
unawaited(
mounted.whenComplete(
() => FlipperAnchorRevoluteJointDef.unlock(joint, side),
),
);
}
List<FixtureDef> _createFixtureDefs() { List<FixtureDef> _createFixtureDefs() {
final fixtures = <FixtureDef>[]; final fixtures = <FixtureDef>[];
final isLeft = side.isLeft; final isLeft = side.isLeft;
final bigCircleShape = CircleShape()..radius = height / 2; final bigCircleShape = CircleShape()..radius = size.y / 2;
bigCircleShape.position.setValues( bigCircleShape.position.setValues(
isLeft isLeft
? -(width / 2) + bigCircleShape.radius ? -(size.x / 2) + bigCircleShape.radius
: (width / 2) - bigCircleShape.radius, : (size.x / 2) - bigCircleShape.radius,
0, 0,
); );
final bigCircleFixtureDef = FixtureDef(bigCircleShape); final bigCircleFixtureDef = FixtureDef(bigCircleShape);
@ -119,8 +173,8 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
final smallCircleShape = CircleShape()..radius = bigCircleShape.radius / 2; final smallCircleShape = CircleShape()..radius = bigCircleShape.radius / 2;
smallCircleShape.position.setValues( smallCircleShape.position.setValues(
isLeft isLeft
? (width / 2) - smallCircleShape.radius ? (size.x / 2) - smallCircleShape.radius
: -(width / 2) + smallCircleShape.radius, : -(size.x / 2) + smallCircleShape.radius,
0, 0,
); );
final smallCircleFixtureDef = FixtureDef(smallCircleShape); final smallCircleFixtureDef = FixtureDef(smallCircleShape);
@ -148,6 +202,15 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
return fixtures; return fixtures;
} }
@override
Future<void> onLoad() async {
await super.onLoad();
await Future.wait([
_loadSprite(),
_anchorToJoint(),
]);
}
@override @override
Body createBody() { Body createBody() {
final bodyDef = BodyDef() final bodyDef = BodyDef()
@ -161,17 +224,6 @@ class Flipper extends PositionBodyComponent with KeyboardHandler {
return body; return body;
} }
// TODO(erickzanardo): Remove this once the issue is solved:
// https://github.com/flame-engine/flame/issues/1417
// ignore: public_member_api_docs
final Completer hasMounted = Completer<void>();
@override
void onMount() {
super.onMount();
hasMounted.complete();
}
@override @override
bool onKeyEvent( bool onKeyEvent(
RawKeyEvent event, RawKeyEvent event,
@ -204,8 +256,8 @@ class FlipperAnchor extends Anchor {
}) : super( }) : super(
position: Vector2( position: Vector2(
flipper.side.isLeft flipper.side.isLeft
? flipper.body.position.x - Flipper.width / 2 ? flipper.body.position.x - flipper.size.x / 2
: flipper.body.position.x + Flipper.width / 2, : flipper.body.position.x + flipper.size.x / 2,
flipper.body.position.y, flipper.body.position.y,
), ),
); );

@ -26,8 +26,6 @@ class PinballGame extends Forge2DGame
_addContactCallbacks(); _addContactCallbacks();
await _addGameBoundaries(); await _addGameBoundaries();
unawaited(_addFlippers());
unawaited(_addBaseboards());
unawaited(_addPlunger()); unawaited(_addPlunger());
// Corner wall above plunger so the ball deflects into the rest of the // Corner wall above plunger so the ball deflects into the rest of the
@ -49,6 +47,23 @@ class PinballGame extends Forge2DGame
), ),
), ),
); );
final flippersPosition = screenToWorld(
Vector2(
camera.viewport.effectiveSize.x / 2,
camera.viewport.effectiveSize.y / 1.1,
),
);
unawaited(
add(
FlipperGroup(
position: flippersPosition,
spacing: 2,
),
),
);
unawaited(_addBaseboards());
} }
void spawnBall() { void spawnBall() {
@ -65,68 +80,27 @@ class PinballGame extends Forge2DGame
createBoundaries(this).forEach(add); createBoundaries(this).forEach(add);
} }
Future<void> _addFlippers() async { Future<void> _addPlunger() async {
final flippersPosition = screenToWorld( late PlungerAnchor plungerAnchor;
Vector2( final compressionDistance = camera.viewport.effectiveSize.y / 12;
camera.viewport.effectiveSize.x / 2,
camera.viewport.effectiveSize.y / 1.1,
),
);
const spaceBetweenFlippers = 2;
final leftFlipper = Flipper.left(
position: Vector2(
flippersPosition.x - (Flipper.width / 2) - (spaceBetweenFlippers / 2),
flippersPosition.y,
),
);
await add(leftFlipper);
final leftFlipperAnchor = FlipperAnchor(flipper: leftFlipper);
await add(leftFlipperAnchor);
final leftFlipperRevoluteJointDef = FlipperAnchorRevoluteJointDef(
flipper: leftFlipper,
anchor: leftFlipperAnchor,
);
// TODO(alestiago): Remove casting once the following is closed:
// https://github.com/flame-engine/forge2d/issues/36
final leftFlipperRevoluteJoint =
world.createJoint(leftFlipperRevoluteJointDef) as RevoluteJoint;
final rightFlipper = Flipper.right( await add(
position: Vector2( plunger = Plunger(
flippersPosition.x + (Flipper.width / 2) + (spaceBetweenFlippers / 2), position: screenToWorld(
flippersPosition.y, Vector2(
camera.viewport.effectiveSize.x / 1.035,
camera.viewport.effectiveSize.y - compressionDistance,
), ),
);
await add(rightFlipper);
final rightFlipperAnchor = FlipperAnchor(flipper: rightFlipper);
await add(rightFlipperAnchor);
final rightFlipperRevoluteJointDef = FlipperAnchorRevoluteJointDef(
flipper: rightFlipper,
anchor: rightFlipperAnchor,
);
// TODO(alestiago): Remove casting once the following is closed:
// https://github.com/flame-engine/forge2d/issues/36
final rightFlipperRevoluteJoint =
world.createJoint(rightFlipperRevoluteJointDef) as RevoluteJoint;
// TODO(erickzanardo): Clean this once the issue is solved:
// https://github.com/flame-engine/flame/issues/1417
// FIXME(erickzanardo): when mounted the initial position is not fully
// reached.
unawaited(
leftFlipper.hasMounted.future.whenComplete(
() => FlipperAnchorRevoluteJointDef.unlock(
leftFlipperRevoluteJoint,
leftFlipper.side,
), ),
compressionDistance: compressionDistance,
), ),
); );
unawaited( await add(plungerAnchor = PlungerAnchor(plunger: plunger));
rightFlipper.hasMounted.future.whenComplete(
() => FlipperAnchorRevoluteJointDef.unlock( world.createJoint(
rightFlipperRevoluteJoint, PlungerAnchorPrismaticJointDef(
rightFlipper.side, plunger: plunger,
), anchor: plungerAnchor,
), ),
); );
} }
@ -155,31 +129,6 @@ class PinballGame extends Forge2DGame
); );
await add(rightBaseboard); await add(rightBaseboard);
} }
Future<void> _addPlunger() async {
late PlungerAnchor plungerAnchor;
final compressionDistance = camera.viewport.effectiveSize.y / 12;
await add(
plunger = Plunger(
position: screenToWorld(
Vector2(
camera.viewport.effectiveSize.x / 1.035,
camera.viewport.effectiveSize.y - compressionDistance,
),
),
compressionDistance: compressionDistance,
),
);
await add(plungerAnchor = PlungerAnchor(plunger: plunger));
world.createJoint(
PlungerAnchorPrismaticJointDef(
plunger: plunger,
anchor: plungerAnchor,
),
);
}
} }
class DebugPinballGame extends PinballGame with TapDetector { class DebugPinballGame extends PinballGame with TapDetector {

@ -2,6 +2,7 @@
import 'dart:collection'; import 'dart:collection';
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -13,6 +14,105 @@ import '../../helpers/helpers.dart';
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(PinballGameTest.create); final flameTester = FlameTester(PinballGameTest.create);
group('FlipperGroup', () {
flameTester.test(
'loads correctly',
(game) async {
final flipperGroup = FlipperGroup(
position: Vector2.zero(),
spacing: 0,
);
await game.ensureAdd(flipperGroup);
expect(game.contains(flipperGroup), isTrue);
},
);
group('constructor', () {
flameTester.test(
'positions correctly',
(game) async {
final position = Vector2.all(10);
final flipperGroup = FlipperGroup(
position: position,
spacing: 0,
);
await game.ensureAdd(flipperGroup);
expect(flipperGroup.position, equals(position));
},
);
});
group('children', () {
bool Function(Component) flipperSelector(BoardSide side) =>
(component) => component is Flipper && component.side == side;
flameTester.test(
'has only one left Flipper',
(game) async {
final flipperGroup = FlipperGroup(
position: Vector2.zero(),
spacing: 0,
);
await game.ensureAdd(flipperGroup);
expect(
() => flipperGroup.children.singleWhere(
flipperSelector(BoardSide.left),
),
returnsNormally,
);
},
);
flameTester.test(
'has only one right Flipper',
(game) async {
final flipperGroup = FlipperGroup(
position: Vector2.zero(),
spacing: 0,
);
await game.ensureAdd(flipperGroup);
expect(
() => flipperGroup.children.singleWhere(
flipperSelector(BoardSide.right),
),
returnsNormally,
);
},
);
flameTester.test(
'spaced correctly',
(game) async {
final flipperGroup = FlipperGroup(
position: Vector2.zero(),
spacing: 2,
);
await game.ready();
await game.ensureAdd(flipperGroup);
final leftFlipper = flipperGroup.children.singleWhere(
flipperSelector(BoardSide.left),
) as Flipper;
final rightFlipper = flipperGroup.children.singleWhere(
flipperSelector(BoardSide.right),
) as Flipper;
expect(
leftFlipper.body.position.x +
leftFlipper.size.x +
flipperGroup.spacing,
equals(rightFlipper.body.position.x),
);
},
);
});
});
group( group(
'Flipper', 'Flipper',
() { () {
@ -21,9 +121,11 @@ void main() {
(game) async { (game) async {
final leftFlipper = Flipper.left(position: Vector2.zero()); final leftFlipper = Flipper.left(position: Vector2.zero());
final rightFlipper = Flipper.right(position: Vector2.zero()); final rightFlipper = Flipper.right(position: Vector2.zero());
await game.ready();
await game.ensureAddAll([leftFlipper, rightFlipper]); await game.ensureAddAll([leftFlipper, rightFlipper]);
expect(game.contains(leftFlipper), isTrue); expect(game.contains(leftFlipper), isTrue);
expect(game.contains(rightFlipper), isTrue);
}, },
); );

@ -18,7 +18,9 @@ void main() {
// [BallScorePointsCallback] once the following issue is resolved: // [BallScorePointsCallback] once the following issue is resolved:
// https://github.com/flame-engine/flame/issues/1416 // https://github.com/flame-engine/flame/issues/1416
group('components', () { group('components', () {
group('Walls', () { bool Function(Component) componentSelector<T>() =>
(component) => component is T;
flameTester.test( flameTester.test(
'has three Walls', 'has three Walls',
(game) async { (game) async {
@ -41,69 +43,46 @@ void main() {
expect( expect(
() => game.children.singleWhere( () => game.children.singleWhere(
(component) => component is BottomWall, componentSelector<BottomWall>(),
), ),
returnsNormally, returnsNormally,
); );
}, },
); );
});
group('Flippers', () {
bool Function(Component) flipperSelector(BoardSide side) =>
(component) => component is Flipper && component.side == side;
flameTester.test( flameTester.test(
'has only one left Flipper', 'has only one Plunger',
(game) async { (game) async {
await game.ready(); await game.ready();
expect( expect(
() => game.children.singleWhere( () => game.children.singleWhere(
flipperSelector(BoardSide.left), (component) => component is Plunger,
), ),
returnsNormally, returnsNormally,
); );
}, },
); );
flameTester.test( flameTester.test('has only one FlipperGroup', (game) async {
'has only one right Flipper',
(game) async {
await game.ready(); await game.ready();
expect( expect(
() => game.children.singleWhere( () => game.children.singleWhere(
flipperSelector(BoardSide.right), (component) => component is FlipperGroup,
), ),
returnsNormally, returnsNormally,
); );
},
);
}); });
flameTester.test( flameTester.test(
'Baseboards has two Baseboards', 'has two Baseboards',
(game) async { (game) async {
await game.ready(); await game.ready();
final baseboards = game.children.whereType<Baseboard>().toList(); final baseboards = game.children.whereType<Baseboard>().toList();
expect(baseboards.length, 2); expect(baseboards.length, 2);
}, },
); );
flameTester.test(
'Plunger has only one Plunger',
(game) async {
await game.ready();
expect(
() => game.children.singleWhere(
(component) => component is Plunger,
),
returnsNormally,
);
},
);
}); });
debugModeFlameTester.test('adds a ball on tap up', (game) async { debugModeFlameTester.test('adds a ball on tap up', (game) async {

Loading…
Cancel
Save