feat: define SlingShot component (#39)

* feat: created sling-short.dart

* refactor: used appropiate file name

* chore: included sling_shot export

* refactor: simplified _createFixtureDefs

* doc: included SlingShot in doc comment

* feat: implemented basic SlingShot

* feat: used EdgeShape instead of PolygonShape

* feat: implemented _addSlingShot method

* feat: adding placeholder art for the flippers

* Update lib/game/components/flipper.dart

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

* docs: included missing documentation (#29)

* chore: ignored lint rue

* docs: documented ball.dart

* docs: ignored lint rule

* docs: documented wall.dart

* docs: documented game_over_dialog.dart

* docs: fixed typo

* docs: included TODO comments

* fix: misisng doc

* chore: add code owners (#31)

* feat: add character selection (#20)

* chore: lock file

* feat: character selection page

* fix: ignore generated asset coverage

* chore: add suggestions

* feat: tint ball with theme color

* refactor: decrease theme cubit scope

* chore: minimize changes

* chore: typos and readability

* refactor: use extension for initial pinball game

* fix: tests from merge

* refactor: ignore docs for views

* refactor: revert to ignoring for file

* fix: todo analyzer warning

* refactor: remove Flutter dep from geometry (#27)

* fix: removed flutter dependency

* test: fixed tests for assertions

* test: check assertion with isA

* ci: added geometry workflow file

* refactor: changed flame dep to vector_math for vector2

* fix: changed import for vector to vector_math_64

* Update .github/workflows/geometry.yaml

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* chore: rename pinball game test extension (#33)

* chore: rename pinball game test extension

* refactor: initial to create

* docs: small change

* chore: removed unecessary end callback (#30)

* feat: adding ball spawning upon click on debug mode (#28)

* feat: adding ball spawming upon click on debug mode

* PR suggestions

* fix: coverage

* fix: rebase

* feat: rebase fixes

* feat: moved triangle to centroid

* feat: made SlingShot a PositionBodyComponent

* feat: removed PositionBodyComponent

* refactor: moved centroid function

* refactor: simplified centroid function

* docs: typo in macro

* feat: modified restitution value

* refactor: added variable for incline

* docs: included TODO comment

* feat: included tests

* feat: removed friction from SlingShot

* feat: removed adding slinghsots

* refactor: used variables for fixtures

* feat: included side in SlingShot

* feat: included different shapes test

* docs: fixed typo

* refactor: removed unused import

* refactor: used centroid from geometry package

* docs: fixed typo

* refactor: improved triangleVertices readability

* refactor: removed EmptyGame class

Co-authored-by: Erick Zanardo <erickzanardoo@gmail.com>
Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
Co-authored-by: Rui Miguel Alonso <ruiskas@gmail.com>
pull/48/head
Alejandro Santiago 4 years ago committed by GitHub
parent 6a68bf1ed7
commit 413900e89f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,7 +3,7 @@ import 'package:pinball/game/game.dart';
/// Indicates a side of the board. /// Indicates a side of the board.
/// ///
/// Usually used to position or mirror elements of a [PinballGame]; such as a /// Usually used to position or mirror elements of a [PinballGame]; such as a
/// [Flipper]. /// [Flipper] or [SlingShot].
enum BoardSide { enum BoardSide {
/// The left side of the board. /// The left side of the board.
left, left,

@ -8,4 +8,5 @@ export 'pathway.dart';
export 'plunger.dart'; export 'plunger.dart';
export 'round_bumper.dart'; export 'round_bumper.dart';
export 'score_points.dart'; export 'score_points.dart';
export 'sling_shot.dart';
export 'wall.dart'; export 'wall.dart';

@ -0,0 +1,96 @@
import 'package:flame/extensions.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:geometry/geometry.dart' show centroid;
import 'package:pinball/game/game.dart';
/// {@template sling_shot}
/// Triangular [BodyType.static] body that propels the [Ball] towards the
/// opposite side.
///
/// [SlingShot]s are usually positioned above each [Flipper].
/// {@endtemplate sling_shot}
class SlingShot extends BodyComponent {
/// {@macro sling_shot}
SlingShot({
required Vector2 position,
required BoardSide side,
}) : _position = position,
_side = side {
// TODO(alestiago): Use sprite instead of color when provided.
paint = Paint()
..color = const Color(0xFF00FF00)
..style = PaintingStyle.fill;
}
/// The initial position of the [SlingShot] body.
final Vector2 _position;
/// Whether the [SlingShot] is on the left or right side of the board.
///
/// A [SlingShot] with [BoardSide.left] propels the [Ball] to the right,
/// whereas a [SlingShot] with [BoardSide.right] propels the [Ball] to the
/// left.
final BoardSide _side;
List<FixtureDef> _createFixtureDefs() {
final fixtures = <FixtureDef>[];
// TODO(alestiago): Use size from PositionedBodyComponent instead,
// once a sprite is given.
final size = Vector2(10, 10);
// TODO(alestiago): This magic number can be deduced by specifying the
// angle and using polar coordinate system to place the bottom right
// vertex.
// Something as: y = -size.y * math.cos(angle)
const additionalIncrement = 2;
final triangleVertices = _side.isLeft
? [
Vector2(0, 0),
Vector2(0, -size.y),
Vector2(
size.x,
-size.y - additionalIncrement,
),
]
: [
Vector2(size.x, 0),
Vector2(size.x, -size.y),
Vector2(
0,
-size.y - additionalIncrement,
),
];
final triangleCentroid = centroid(triangleVertices);
for (final vertex in triangleVertices) {
vertex.setFrom(vertex - triangleCentroid);
}
final triangle = PolygonShape()..set(triangleVertices);
final triangleFixtureDef = FixtureDef(triangle)..friction = 0;
fixtures.add(triangleFixtureDef);
final kicker = EdgeShape()
..set(
triangleVertices.first,
triangleVertices.last,
);
// TODO(alestiago): Play with restitution value once game is bundled.
final kickerFixtureDef = FixtureDef(kicker)
..restitution = 20.0
..friction = 0;
fixtures.add(kickerFixtureDef);
return fixtures;
}
@override
Body createBody() {
final bodyDef = BodyDef()..position = _position;
final body = world.createBody(bodyDef);
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}

@ -18,6 +18,7 @@ void main() {
'loads correctly', 'loads correctly',
(game) async { (game) async {
final ball = Ball(position: Vector2.zero()); final ball = Ball(position: Vector2.zero());
await game.ready();
await game.ensureAdd(ball); await game.ensureAdd(ball);
expect(game.contains(ball), isTrue); expect(game.contains(ball), isTrue);

@ -0,0 +1,180 @@
// ignore_for_file: cascade_invocations
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/game.dart';
void main() {
group('SlingShot', () {
final flameTester = FlameTester(Forge2DGame.new);
flameTester.test(
'loads correctly',
(game) async {
final slingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
expect(game.contains(slingShot), isTrue);
},
);
group('body', () {
flameTester.test(
'positions correctly',
(game) async {
final position = Vector2.all(10);
final slingShot = SlingShot(
position: position,
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
expect(slingShot.body.position, equals(position));
},
);
flameTester.test(
'is static',
(game) async {
final slingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
expect(slingShot.body.bodyType, equals(BodyType.static));
},
);
});
group('first fixture', () {
flameTester.test(
'exists',
(game) async {
final slingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
expect(slingShot.body.fixtures[0], isA<Fixture>());
},
);
flameTester.test(
'shape is triangular',
(game) async {
final slingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
final fixture = slingShot.body.fixtures[0];
expect(fixture.shape.shapeType, equals(ShapeType.polygon));
expect((fixture.shape as PolygonShape).vertices.length, equals(3));
},
);
flameTester.test(
'triangular shapes are different '
'when side is left or right',
(game) async {
final leftSlingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
final rightSlingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.right,
);
await game.ensureAdd(leftSlingShot);
await game.ensureAdd(rightSlingShot);
final rightShape =
rightSlingShot.body.fixtures[0].shape as PolygonShape;
final leftShape =
leftSlingShot.body.fixtures[0].shape as PolygonShape;
expect(rightShape.vertices, isNot(equals(leftShape.vertices)));
},
);
flameTester.test(
'has no friction',
(game) async {
final slingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
final fixture = slingShot.body.fixtures[0];
expect(fixture.friction, equals(0));
},
);
});
group('second fixture', () {
flameTester.test(
'exists',
(game) async {
final slingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
expect(slingShot.body.fixtures[1], isA<Fixture>());
},
);
flameTester.test(
'shape is edge',
(game) async {
final slingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
final fixture = slingShot.body.fixtures[1];
expect(fixture.shape.shapeType, equals(ShapeType.edge));
},
);
flameTester.test(
'has restitution',
(game) async {
final slingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
final fixture = slingShot.body.fixtures[1];
expect(fixture.restitution, greaterThan(0));
},
);
flameTester.test(
'has no friction',
(game) async {
final slingShot = SlingShot(
position: Vector2.zero(),
side: BoardSide.left,
);
await game.ensureAdd(slingShot);
final fixture = slingShot.body.fixtures[1];
expect(fixture.friction, equals(0));
},
);
});
});
}
Loading…
Cancel
Save