refactor: `Plunger` controls (#152)

* feat: added plunger asset and move

* feat: fixed plunger assets and position

* fix: set limits to plunger compression correctly

* chore: unused import

* fix: placed plunger correctly

* test: refactor test game

* refactor: changed spawnBall initialPosition

* chore: plunger golden test

* refactor: moved plunger to pinball_components

* feat: plunger story

* refactor: plunger assets preload

* chore: assets for plunger

* refactor: added key events to plunger game

* refactor: make pull and release public to call from outside plunger

* chore: update to retry tests

* refactor: added Traceable to Plunger sandbox story

* refactor: add Traceable

* fix: removed body from PlungerAnchor

* refactor: removed keyevents from plunger

* test: plunger tests refactored without keyevents

* refactor: removed unused keyevents

* refactor: plunger spritecomponent

* refactor: keyevents to gamecontroller

* feat: added plunger controller

* test: tests for plunger controller

* test: tested plunger controller

* fix: assets gen

* chore: removed export from barrel

* refactor: removed unnecessary keyhandler

* refactor: sprite size

* test: anchor at setup

* refactor: plunger const keys

* Update lib/game/components/controlled_plunger.dart

Co-authored-by: Alejandro Santiago <dev@alestiago.com>

Co-authored-by: Alejandro Santiago <dev@alestiago.com>
pull/183/head
Rui Miguel Alonso 3 years ago committed by GitHub
parent 59c7c8b0ff
commit bf0596846b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,10 +3,10 @@ export 'bonus_word.dart';
export 'camera_controller.dart';
export 'controlled_ball.dart';
export 'controlled_flipper.dart';
export 'controlled_plunger.dart';
export 'controlled_sparky_computer.dart';
export 'flutter_forest.dart';
export 'game_flow_controller.dart';
export 'plunger.dart';
export 'score_effect_controller.dart';
export 'score_points.dart';
export 'sparky_fire_zone.dart';

@ -0,0 +1,49 @@
import 'package:flame/components.dart';
import 'package:flutter/services.dart';
import 'package:pinball/flame/flame.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template controlled_plunger}
/// A [Plunger] with a [PlungerController] attached.
/// {@endtemplate}
class ControlledPlunger extends Plunger with Controls<PlungerController> {
/// {@macro controlled_plunger}
ControlledPlunger({required double compressionDistance})
: super(compressionDistance: compressionDistance) {
controller = PlungerController(this);
}
}
/// {@template plunger_controller}
/// A [ComponentController] that controls a [Plunger]s movement.
/// {@endtemplate}
class PlungerController extends ComponentController<Plunger>
with KeyboardHandler {
/// {@macro plunger_controller}
PlungerController(Plunger plunger) : super(plunger);
/// The [LogicalKeyboardKey]s that will control the [Flipper].
///
/// [onKeyEvent] method listens to when one of these keys is pressed.
static const List<LogicalKeyboardKey> _keys = [
LogicalKeyboardKey.arrowDown,
LogicalKeyboardKey.space,
LogicalKeyboardKey.keyS,
];
@override
bool onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
if (!_keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) {
component.pull();
} else if (event is RawKeyUpEvent) {
component.release();
}
return false;
}
}

@ -46,6 +46,7 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.spaceship.rail.foreground.keyName),
images.load(components.Assets.images.chromeDino.mouth.keyName),
images.load(components.Assets.images.chromeDino.head.keyName),
images.load(components.Assets.images.plunger.plunger.keyName),
images.load(components.Assets.images.sparky.computer.base.keyName),
images.load(components.Assets.images.sparky.computer.top.keyName),
images.load(components.Assets.images.sparky.bumper.a.active.keyName),

@ -48,7 +48,7 @@ class PinballGame extends Forge2DGame
unawaited(addFromBlueprint(LaunchRamp()));
unawaited(addFromBlueprint(ControlledSparkyComputer()));
final plunger = Plunger(compressionDistance: 29)
final plunger = ControlledPlunger(compressionDistance: 29)
..initialPosition = Vector2(38, -19);
await add(plunger);

@ -3,6 +3,8 @@
/// FlutterGen
/// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
import 'package:flutter/widgets.dart';
class $AssetsImagesGen {
@ -15,8 +17,11 @@ class $AssetsImagesGen {
class $AssetsImagesComponentsGen {
const $AssetsImagesComponentsGen();
/// File path: assets/images/components/background.png
AssetGenImage get background =>
const AssetGenImage('assets/images/components/background.png');
/// File path: assets/images/components/plunger.png
AssetGenImage get plunger =>
const AssetGenImage('assets/images/components/plunger.png');
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -33,6 +33,7 @@ class $AssetsImagesGen {
$AssetsImagesKickerGen get kicker => const $AssetsImagesKickerGen();
$AssetsImagesLaunchRampGen get launchRamp =>
const $AssetsImagesLaunchRampGen();
$AssetsImagesPlungerGen get plunger => const $AssetsImagesPlungerGen();
$AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen();
$AssetsImagesSpaceshipGen get spaceship => const $AssetsImagesSpaceshipGen();
$AssetsImagesSparkyGen get sparky => const $AssetsImagesSparkyGen();
@ -171,6 +172,14 @@ class $AssetsImagesLaunchRampGen {
const AssetGenImage('assets/images/launch_ramp/ramp.png');
}
class $AssetsImagesPlungerGen {
const $AssetsImagesPlungerGen();
/// File path: assets/images/plunger/plunger.png
AssetGenImage get plunger =>
const AssetGenImage('assets/images/plunger/plunger.png');
}
class $AssetsImagesSlingshotGen {
const $AssetsImagesSlingshotGen();

@ -3,9 +3,14 @@
/// FlutterGen
/// *****************************************************
// ignore_for_file: directives_ordering,unnecessary_import
class FontFamily {
FontFamily._();
/// Font family: PixeloidMono
static const String pixeloidMono = 'PixeloidMono';
/// Font family: PixeloidSans
static const String pixeloidSans = 'PixeloidSans';
}

@ -17,6 +17,7 @@ export 'joint_anchor.dart';
export 'kicker.dart';
export 'launch_ramp.dart';
export 'layer.dart';
export 'plunger.dart';
export 'ramp_opening.dart';
export 'score_text.dart';
export 'shapes/shapes.dart';

@ -1,16 +1,14 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/services.dart';
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_components/pinball_components.dart';
/// {@template plunger}
/// [Plunger] serves as a spring, that shoots the ball on the right side of the
/// playfield.
///
/// [Plunger] ignores gravity so the player controls its downward [_pull].
/// [Plunger] ignores gravity so the player controls its downward [pull].
/// {@endtemplate}
class Plunger extends BodyComponent with KeyboardHandler, InitialPosition {
class Plunger extends BodyComponent with InitialPosition {
/// {@macro plunger}
Plunger({
required this.compressionDistance,
@ -43,7 +41,7 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition {
}
/// Set a constant downward velocity on the [Plunger].
void _pull() {
void pull() {
body.linearVelocity = Vector2(0, -7);
}
@ -51,32 +49,11 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition {
///
/// The velocity's magnitude depends on how far the [Plunger] has been pulled
/// from its original [initialPosition].
void _release() {
void release() {
final velocity = (initialPosition.y - body.position.y) * 5;
body.linearVelocity = Vector2(0, velocity);
}
@override
bool onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
final keys = [
LogicalKeyboardKey.space,
LogicalKeyboardKey.arrowDown,
LogicalKeyboardKey.keyS,
];
if (!keys.contains(event.logicalKey)) return true;
if (event is RawKeyDownEvent) {
_pull();
} else if (event is RawKeyUpEvent) {
_release();
}
return false;
}
/// Anchors the [Plunger] to the [PrismaticJoint] that controls its vertical
/// motion.
Future<void> _anchorToJoint() async {
@ -97,26 +74,24 @@ class Plunger extends BodyComponent with KeyboardHandler, InitialPosition {
Future<void> onLoad() async {
await super.onLoad();
await _anchorToJoint();
renderBody = false;
await _loadSprite();
await add(_PlungerSpriteComponent());
}
}
Future<void> _loadSprite() async {
class _PlungerSpriteComponent extends SpriteComponent with HasGameRef {
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.components.plunger.path,
Assets.images.plunger.plunger.keyName,
);
await add(
SpriteComponent(
sprite: sprite,
size: Vector2(5.5, 40),
anchor: Anchor.center,
position: Vector2(2, 19),
angle: -0.033,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(2, 19);
angle = -0.033;
}
}
@ -133,14 +108,6 @@ class PlungerAnchor extends JointAnchor {
-plunger.compressionDistance,
);
}
@override
Body createBody() {
final bodyDef = BodyDef()
..position = initialPosition
..type = BodyType.static;
return world.createBody(bodyDef);
}
}
/// {@template plunger_anchor_prismatic_joint_def}

@ -49,6 +49,7 @@ flutter:
- assets/images/spaceship/ramp/
- assets/images/chrome_dino/
- assets/images/kicker/
- assets/images/plunger/
- assets/images/slingshot/
- assets/images/sparky/computer/
- assets/images/sparky/bumper/a/

@ -21,6 +21,7 @@ void main() {
addChromeDinoStories(dashbook);
addDashNestBumperStories(dashbook);
addKickerStories(dashbook);
addPlungerStories(dashbook);
addSlingshotStories(dashbook);
addSparkyBumperStories(dashbook);
addZoomStories(dashbook);

@ -0,0 +1,54 @@
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class PlungerGame extends BasicBallGame with KeyboardEvents, Traceable {
PlungerGame() : super(color: const Color(0xFFFF0000));
static const info = '''
Shows how Plunger is rendered.
- Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game.
''';
static const _downKeys = [
LogicalKeyboardKey.arrowDown,
LogicalKeyboardKey.space,
];
late Plunger plunger;
@override
Future<void> onLoad() async {
await super.onLoad();
final center = screenToWorld(camera.viewport.canvasSize! / 2);
plunger = Plunger(compressionDistance: 29)
..initialPosition = Vector2(center.x - (Kicker.size.x * 2), center.y);
await add(plunger);
await traceAllBodies();
}
@override
KeyEventResult onKeyEvent(
RawKeyEvent event,
Set<LogicalKeyboardKey> keysPressed,
) {
final movedPlungerDown = _downKeys.contains(event.logicalKey);
if (movedPlungerDown) {
if (event is RawKeyDownEvent) {
plunger.pull();
} else if (event is RawKeyUpEvent) {
plunger.release();
}
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
}
}

@ -0,0 +1,15 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/plunger/plunger_game.dart';
void addPlungerStories(Dashbook dashbook) {
dashbook.storiesOf('Plunger').add(
'Basic',
(context) => GameWidget(
game: PlungerGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('plunger_game/basic.dart'),
info: PlungerGame.info,
);
}

@ -8,6 +8,7 @@ export 'flutter_forest/stories.dart';
export 'google_word/stories.dart';
export 'launch_ramp/stories.dart';
export 'layer/stories.dart';
export 'plunger/stories.dart';
export 'score_text/stories.dart';
export 'slingshot/stories.dart';
export 'spaceship/stories.dart';

@ -1,12 +1,9 @@
// ignore_for_file: cascade_invocations
import 'dart:collection';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
@ -117,13 +114,23 @@ void main() {
);
});
group('onKeyEvent', () {
final keys = UnmodifiableListView([
LogicalKeyboardKey.space,
LogicalKeyboardKey.arrowDown,
LogicalKeyboardKey.keyS,
]);
group('pull', () {
flameTester.test(
'moves downwards when pull is called',
(game) async {
final plunger = Plunger(
compressionDistance: compressionDistance,
);
await game.ensureAdd(plunger);
plunger.pull();
expect(plunger.body.linearVelocity.y, isNegative);
expect(plunger.body.linearVelocity.x, isZero);
},
);
});
group('release', () {
late Plunger plunger;
setUp(() {
@ -132,57 +139,29 @@ void main() {
);
});
testRawKeyUpEvents(keys, (event) {
final keyLabel = (event.logicalKey != LogicalKeyboardKey.space)
? event.logicalKey.keyLabel
: 'Space';
flameTester.test(
'moves upwards when $keyLabel is released '
'and plunger is below its starting position',
(game) async {
'moves upwards when release is called '
'and plunger is below its starting position', (game) async {
await game.ensureAdd(plunger);
plunger.body.setTransform(Vector2(0, -1), 0);
plunger.onKeyEvent(event, {});
plunger.release();
expect(plunger.body.linearVelocity.y, isPositive);
expect(plunger.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(keys, (event) {
final keyLabel = (event.logicalKey != LogicalKeyboardKey.space)
? event.logicalKey.keyLabel
: 'Space';
flameTester.test(
'does not move when $keyLabel is released '
'does not move when release is called '
'and plunger is in its starting position',
(game) async {
await game.ensureAdd(plunger);
plunger.onKeyEvent(event, {});
plunger.release();
expect(plunger.body.linearVelocity.y, isZero);
expect(plunger.body.linearVelocity.x, isZero);
},
);
});
testRawKeyDownEvents(keys, (event) {
final keyLabel = (event.logicalKey != LogicalKeyboardKey.space)
? event.logicalKey.keyLabel
: 'Space';
flameTester.test(
'moves downwards when $keyLabel is pressed',
(game) async {
await game.ensureAdd(plunger);
plunger.onKeyEvent(event, {});
expect(plunger.body.linearVelocity.y, isNegative);
expect(plunger.body.linearVelocity.x, isZero);
},
);
});
});
});
group('PlungerAnchor', () {
@ -210,11 +189,13 @@ void main() {
group('PlungerAnchorPrismaticJointDef', () {
const compressionDistance = 10.0;
late Plunger plunger;
late PlungerAnchor anchor;
setUp(() {
plunger = Plunger(
compressionDistance: compressionDistance,
);
anchor = PlungerAnchor(plunger: plunger);
});
group('initializes with', () {
@ -222,7 +203,6 @@ void main() {
'plunger body as bodyA',
(game) async {
await game.ensureAdd(plunger);
final anchor = PlungerAnchor(plunger: plunger);
await game.ensureAdd(anchor);
final jointDef = PlungerAnchorPrismaticJointDef(
@ -238,7 +218,6 @@ void main() {
'anchor body as bodyB',
(game) async {
await game.ensureAdd(plunger);
final anchor = PlungerAnchor(plunger: plunger);
await game.ensureAdd(anchor);
final jointDef = PlungerAnchorPrismaticJointDef(
@ -255,7 +234,6 @@ void main() {
'limits enabled',
(game) async {
await game.ensureAdd(plunger);
final anchor = PlungerAnchor(plunger: plunger);
await game.ensureAdd(anchor);
final jointDef = PlungerAnchorPrismaticJointDef(
@ -272,7 +250,6 @@ void main() {
'lower translation limit as negative infinity',
(game) async {
await game.ensureAdd(plunger);
final anchor = PlungerAnchor(plunger: plunger);
await game.ensureAdd(anchor);
final jointDef = PlungerAnchorPrismaticJointDef(
@ -289,7 +266,6 @@ void main() {
'connected body collison enabled',
(game) async {
await game.ensureAdd(plunger);
final anchor = PlungerAnchor(plunger: plunger);
await game.ensureAdd(anchor);
final jointDef = PlungerAnchorPrismaticJointDef(
@ -303,8 +279,6 @@ void main() {
);
});
testRawKeyUpEvents([LogicalKeyboardKey.space], (event) {
late final anchor = PlungerAnchor(plunger: plunger);
flameTester.testGameWidget(
'plunger cannot go below anchor',
setUp: (game, tester) async {
@ -326,14 +300,11 @@ void main() {
expect(plunger.body.position.y > anchor.body.position.y, isTrue);
},
);
});
testRawKeyUpEvents([LogicalKeyboardKey.space], (event) {
flameTester.testGameWidget(
'plunger cannot excessively exceed starting position',
setUp: (game, tester) async {
await game.ensureAdd(plunger);
final anchor = PlungerAnchor(plunger: plunger);
await game.ensureAdd(anchor);
final jointDef = PlungerAnchorPrismaticJointDef(
@ -351,5 +322,4 @@ void main() {
},
);
});
});
}

@ -0,0 +1,78 @@
import 'dart:collection';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(EmptyPinballGameTest.new);
group('PlungerController', () {
group('onKeyEvent', () {
final downKeys = UnmodifiableListView([
LogicalKeyboardKey.arrowDown,
LogicalKeyboardKey.space,
LogicalKeyboardKey.keyS,
]);
late Plunger plunger;
late PlungerController controller;
setUp(() {
plunger = Plunger(compressionDistance: 10);
controller = PlungerController(plunger);
plunger.add(controller);
});
testRawKeyDownEvents(downKeys, (event) {
flameTester.test(
'moves down '
'when ${event.logicalKey.keyLabel} is pressed',
(game) async {
await game.ensureAdd(plunger);
controller.onKeyEvent(event, {});
expect(plunger.body.linearVelocity.y, isNegative);
expect(plunger.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(downKeys, (event) {
flameTester.test(
'moves up '
'when ${event.logicalKey.keyLabel} is released '
'and plunger is below its starting position',
(game) async {
await game.ensureAdd(plunger);
plunger.body.setTransform(Vector2(0, -1), 0);
controller.onKeyEvent(event, {});
expect(plunger.body.linearVelocity.y, isPositive);
expect(plunger.body.linearVelocity.x, isZero);
},
);
});
testRawKeyUpEvents(downKeys, (event) {
flameTester.test(
'does not move when ${event.logicalKey.keyLabel} is released '
'and plunger is in its starting position',
(game) async {
await game.ensureAdd(plunger);
controller.onKeyEvent(event, {});
expect(plunger.body.linearVelocity.y, isZero);
expect(plunger.body.linearVelocity.x, isZero);
},
);
});
});
});
}
Loading…
Cancel
Save