mirror of https://github.com/flutter/pinball.git
commit
d66261581f
@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
|
/// {@template anchor}
|
||||||
|
/// Non visual [BodyComponent] used to hold a [BodyType.dynamic] in [Joint]s
|
||||||
|
/// with this [BodyType.static].
|
||||||
|
///
|
||||||
|
/// It is recommended to [_position] the anchor first and then use the body
|
||||||
|
/// position as the anchor point when initializing a [JointDef].
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// initialize(
|
||||||
|
/// dynamicBody.body,
|
||||||
|
/// anchor.body,
|
||||||
|
/// anchor.body.position,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
/// {@endtemplate}
|
||||||
|
class Anchor extends BodyComponent {
|
||||||
|
/// {@macro anchor}
|
||||||
|
Anchor({
|
||||||
|
required Vector2 position,
|
||||||
|
}) : _position = position;
|
||||||
|
|
||||||
|
final Vector2 _position;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final bodyDef = BodyDef()..position = _position;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
export 'anchor.dart';
|
||||||
export 'ball.dart';
|
export 'ball.dart';
|
||||||
export 'plunger.dart';
|
export 'plunger.dart';
|
||||||
export 'score_points.dart';
|
export 'score_points.dart';
|
||||||
|
export 'wall.dart';
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
// ignore_for_file: avoid_renaming_method_parameters
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball/game/components/components.dart';
|
||||||
|
|
||||||
|
class Wall extends BodyComponent {
|
||||||
|
Wall({
|
||||||
|
required this.start,
|
||||||
|
required this.end,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Vector2 start;
|
||||||
|
final Vector2 end;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = EdgeShape()..set(start, end);
|
||||||
|
|
||||||
|
final fixtureDef = FixtureDef(shape)
|
||||||
|
..restitution = 0.0
|
||||||
|
..friction = 0.3;
|
||||||
|
|
||||||
|
final bodyDef = BodyDef()
|
||||||
|
..userData = this
|
||||||
|
..position = Vector2.zero()
|
||||||
|
..type = BodyType.static;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BottomWall extends Wall {
|
||||||
|
BottomWall(Forge2DGame game)
|
||||||
|
: super(
|
||||||
|
start: game.screenToWorld(game.camera.viewport.effectiveSize),
|
||||||
|
end: Vector2(
|
||||||
|
0,
|
||||||
|
game.screenToWorld(game.camera.viewport.effectiveSize).y,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BottomWallBallContactCallback extends ContactCallback<Ball, BottomWall> {
|
||||||
|
@override
|
||||||
|
void begin(Ball ball, BottomWall wall, Contact contact) {
|
||||||
|
ball.lost();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void end(_, __, ___) {}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
export 'bloc/game_bloc.dart';
|
export 'bloc/game_bloc.dart';
|
||||||
export 'components/components.dart';
|
export 'components/components.dart';
|
||||||
export 'pinball_game.dart';
|
export 'pinball_game.dart';
|
||||||
export 'view/pinball_game_page.dart';
|
export 'view/view.dart';
|
||||||
|
@ -1,16 +1,45 @@
|
|||||||
import 'package:flame/game.dart';
|
import 'package:flame/game.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:pinball/game/game.dart';
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
class PinballGamePage extends StatelessWidget {
|
class PinballGamePage extends StatelessWidget {
|
||||||
const PinballGamePage({Key? key}) : super(key: key);
|
const PinballGamePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
static Route route() {
|
static Route route() {
|
||||||
return MaterialPageRoute<void>(builder: (_) => const PinballGamePage());
|
return MaterialPageRoute<void>(
|
||||||
|
builder: (_) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => GameBloc(),
|
||||||
|
child: const PinballGamePage(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GameWidget(game: PinballGame());
|
return const PinballGameView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PinballGameView extends StatelessWidget {
|
||||||
|
const PinballGameView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocListener<GameBloc, GameState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.isGameOver) {
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) {
|
||||||
|
return const GameOverDialog();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: GameWidget<PinballGame>(game: PinballGame()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
export 'pinball_game_page.dart';
|
||||||
|
export 'widgets/widgets.dart';
|
@ -0,0 +1,18 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class GameOverDialog extends StatelessWidget {
|
||||||
|
const GameOverDialog({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Dialog(
|
||||||
|
child: SizedBox(
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
child: Center(
|
||||||
|
child: Text('Game Over'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1 @@
|
|||||||
|
export 'game_over_dialog.dart';
|
@ -0,0 +1,60 @@
|
|||||||
|
// 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() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('Anchor', () {
|
||||||
|
final flameTester = FlameTester(PinballGame.new);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final anchor = Anchor(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(anchor);
|
||||||
|
|
||||||
|
expect(game.contains(anchor), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('body', () {
|
||||||
|
flameTester.test(
|
||||||
|
'positions correctly',
|
||||||
|
(game) async {
|
||||||
|
final position = Vector2.all(10);
|
||||||
|
final anchor = Anchor(position: position);
|
||||||
|
await game.ensureAdd(anchor);
|
||||||
|
game.contains(anchor);
|
||||||
|
|
||||||
|
expect(anchor.body.position, position);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'is static',
|
||||||
|
(game) async {
|
||||||
|
final anchor = Anchor(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(anchor);
|
||||||
|
|
||||||
|
expect(anchor.body.bodyType, equals(BodyType.static));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('fixtures', () {
|
||||||
|
flameTester.test(
|
||||||
|
'has none',
|
||||||
|
(game) async {
|
||||||
|
final anchor = Anchor(position: Vector2.zero());
|
||||||
|
await game.ensureAdd(anchor);
|
||||||
|
|
||||||
|
expect(anchor.body.fixtures, isEmpty);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
// 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:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
group('Wall', () {
|
||||||
|
group('BottomWallBallContactCallback', () {
|
||||||
|
test(
|
||||||
|
'removes the ball on begin contact when the wall is a bottom one',
|
||||||
|
() {
|
||||||
|
final game = MockPinballGame();
|
||||||
|
final wall = MockBottomWall();
|
||||||
|
final ball = MockBall();
|
||||||
|
|
||||||
|
when(() => ball.gameRef).thenReturn(game);
|
||||||
|
|
||||||
|
BottomWallBallContactCallback()
|
||||||
|
// Remove once https://github.com/flame-engine/flame/pull/1415
|
||||||
|
// is merged
|
||||||
|
..end(MockBall(), MockBottomWall(), MockContact())
|
||||||
|
..begin(ball, wall, MockContact());
|
||||||
|
|
||||||
|
verify(ball.lost).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
final flameTester = FlameTester(PinballGame.new);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final wall = Wall(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: Vector2(100, 0),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(wall);
|
||||||
|
|
||||||
|
expect(game.contains(wall), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('body', () {
|
||||||
|
flameTester.test(
|
||||||
|
'positions correctly',
|
||||||
|
(game) async {
|
||||||
|
final wall = Wall(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: Vector2(100, 0),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(wall);
|
||||||
|
game.contains(wall);
|
||||||
|
|
||||||
|
expect(wall.body.position, Vector2.zero());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'is static',
|
||||||
|
(game) async {
|
||||||
|
final wall = Wall(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: Vector2(100, 0),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(wall);
|
||||||
|
|
||||||
|
expect(wall.body.bodyType, equals(BodyType.static));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('first fixture', () {
|
||||||
|
flameTester.test(
|
||||||
|
'exists',
|
||||||
|
(game) async {
|
||||||
|
final wall = Wall(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: Vector2(100, 0),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(wall);
|
||||||
|
|
||||||
|
expect(wall.body.fixtures[0], isA<Fixture>());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'has restitution equals 0',
|
||||||
|
(game) async {
|
||||||
|
final wall = Wall(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: Vector2(100, 0),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(wall);
|
||||||
|
|
||||||
|
final fixture = wall.body.fixtures[0];
|
||||||
|
expect(fixture.restitution, equals(0));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'has friction',
|
||||||
|
(game) async {
|
||||||
|
final wall = Wall(
|
||||||
|
start: Vector2.zero(),
|
||||||
|
end: Vector2(100, 0),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(wall);
|
||||||
|
|
||||||
|
final fixture = wall.body.fixtures[0];
|
||||||
|
expect(fixture.friction, greaterThan(0));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
FlameTester flameBlocTester({required GameBloc Function() gameBlocBuilder}) {
|
||||||
|
return FlameTester(
|
||||||
|
PinballGame.new,
|
||||||
|
pumpWidget: (gameWidget, tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
BlocProvider.value(
|
||||||
|
value: gameBlocBuilder(),
|
||||||
|
child: gameWidget,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
|
||||||
|
class MockPinballGame extends Mock implements PinballGame {}
|
||||||
|
|
||||||
|
class MockWall extends Mock implements Wall {}
|
||||||
|
|
||||||
|
class MockBottomWall extends Mock implements BottomWall {}
|
||||||
|
|
||||||
|
class MockBall extends Mock implements Ball {}
|
||||||
|
|
||||||
|
class MockContact extends Mock implements Contact {}
|
||||||
|
|
||||||
|
class MockGameBloc extends Mock implements GameBloc {}
|
Loading…
Reference in new issue