mirror of https://github.com/flutter/pinball.git
parent
b9c2f3a54f
commit
d809145152
@ -1,47 +0,0 @@
|
|||||||
// ignore_for_file: avoid_renaming_method_parameters
|
|
||||||
|
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
|
|
||||||
/// {@template score_points}
|
|
||||||
/// Specifies the amount of points received on [Ball] collision.
|
|
||||||
/// {@endtemplate}
|
|
||||||
mixin ScorePoints<T extends Forge2DGame> on BodyComponent<T> {
|
|
||||||
/// {@macro score_points}
|
|
||||||
int get points;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onLoad() async {
|
|
||||||
await super.onLoad();
|
|
||||||
body.userData = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// {@template ball_score_points_callbacks}
|
|
||||||
/// Adds points to the score when a [Ball] collides with a [BodyComponent] that
|
|
||||||
/// implements [ScorePoints].
|
|
||||||
/// {@endtemplate}
|
|
||||||
class BallScorePointsCallback extends ContactCallback<Ball, ScorePoints> {
|
|
||||||
/// {@macro ball_score_points_callbacks}
|
|
||||||
BallScorePointsCallback(PinballGame game) : _gameRef = game;
|
|
||||||
|
|
||||||
final PinballGame _gameRef;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void begin(
|
|
||||||
Ball ball,
|
|
||||||
ScorePoints scorePoints,
|
|
||||||
Contact _,
|
|
||||||
) {
|
|
||||||
_gameRef.read<GameBloc>().add(Scored(points: scorePoints.points));
|
|
||||||
_gameRef.audio.score();
|
|
||||||
|
|
||||||
_gameRef.add(
|
|
||||||
ScoreText(
|
|
||||||
text: scorePoints.points.toString(),
|
|
||||||
position: ball.body.position,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,53 @@
|
|||||||
|
// ignore_for_file: avoid_renaming_method_parameters
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
/// {@template scoring_behaviour}
|
||||||
|
///
|
||||||
|
/// {@endtemplate}
|
||||||
|
class ScoringBehaviour extends Component
|
||||||
|
with ContactCallbacks, HasGameRef<PinballGame> {
|
||||||
|
/// {@macro scoring_behaviour}
|
||||||
|
ScoringBehaviour({
|
||||||
|
required int points,
|
||||||
|
}) : _points = points;
|
||||||
|
|
||||||
|
final int _points;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
// TODO(alestiago): Refactor once the following is merged:
|
||||||
|
// https://github.com/flame-engine/flame/pull/1566
|
||||||
|
final parent = this.parent;
|
||||||
|
if (parent is BodyComponent) {
|
||||||
|
final userData = parent.body.userData;
|
||||||
|
if (userData is ContactCallbacks) {
|
||||||
|
userData.add(this);
|
||||||
|
} else {
|
||||||
|
parent.body.userData = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void beginContact(Object other, Contact contact) {
|
||||||
|
super.beginContact(other, contact);
|
||||||
|
if (other is! Ball) return;
|
||||||
|
|
||||||
|
gameRef.read<GameBloc>().add(Scored(points: _points));
|
||||||
|
gameRef.audio.score();
|
||||||
|
|
||||||
|
gameRef.add(
|
||||||
|
ScoreText(
|
||||||
|
text: _points.toString(),
|
||||||
|
position: other.body.position,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
|
/// {@template contact_callbacks_adder}
|
||||||
|
///
|
||||||
|
/// {@endtemplate}
|
||||||
|
// TODO(alestiago): Consider adding streams to [ContactCallbacks].
|
||||||
|
extension ContactCallbacksAdder on ContactCallbacks {
|
||||||
|
/// {@macro contact_callbacks_adder}
|
||||||
|
void add(ContactCallbacks contactCallbacks) {
|
||||||
|
if (contactCallbacks.onBeginContact != null) {
|
||||||
|
onBeginContact = (other, contact) {
|
||||||
|
onBeginContact?.call(other, contact);
|
||||||
|
contactCallbacks.beginContact(other, contact);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contactCallbacks.onEndContact != null) {
|
||||||
|
onEndContact = (other, contact) {
|
||||||
|
onEndContact?.call(other, contact);
|
||||||
|
contactCallbacks.endContact(other, contact);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contactCallbacks.onPreSolve != null) {
|
||||||
|
onPreSolve = (other, contact, oldManifold) {
|
||||||
|
onPreSolve?.call(other, contact, oldManifold);
|
||||||
|
contactCallbacks.preSolve(other, contact, oldManifold);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contactCallbacks.onPostSolve != null) {
|
||||||
|
onPostSolve = (other, contact, impulse) {
|
||||||
|
onPostSolve?.call(other, contact, impulse);
|
||||||
|
contactCallbacks.postSolve(other, contact, impulse);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,105 +0,0 @@
|
|||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:mocktail/mocktail.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
import 'package:pinball_audio/pinball_audio.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
|
|
||||||
import '../../helpers/helpers.dart';
|
|
||||||
|
|
||||||
class FakeScorePoints extends BodyComponent with ScorePoints {
|
|
||||||
@override
|
|
||||||
Body createBody() {
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get points => 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('BallScorePointsCallback', () {
|
|
||||||
late PinballGame game;
|
|
||||||
late GameBloc bloc;
|
|
||||||
late PinballAudio audio;
|
|
||||||
late Ball ball;
|
|
||||||
late FakeScorePoints fakeScorePoints;
|
|
||||||
|
|
||||||
setUp(() {
|
|
||||||
game = MockPinballGame();
|
|
||||||
bloc = MockGameBloc();
|
|
||||||
audio = MockPinballAudio();
|
|
||||||
fakeScorePoints = FakeScorePoints();
|
|
||||||
|
|
||||||
ball = MockBall();
|
|
||||||
final ballBody = MockBody();
|
|
||||||
when(() => ball.body).thenReturn(ballBody);
|
|
||||||
when(() => ballBody.position).thenReturn(Vector2.all(4));
|
|
||||||
});
|
|
||||||
|
|
||||||
setUpAll(() {
|
|
||||||
registerFallbackValue(FakeGameEvent());
|
|
||||||
});
|
|
||||||
|
|
||||||
group('begin', () {
|
|
||||||
test(
|
|
||||||
'emits Scored event with points',
|
|
||||||
() {
|
|
||||||
when(game.read<GameBloc>).thenReturn(bloc);
|
|
||||||
when(() => game.audio).thenReturn(audio);
|
|
||||||
|
|
||||||
BallScorePointsCallback(game).begin(
|
|
||||||
ball,
|
|
||||||
fakeScorePoints,
|
|
||||||
FakeContact(),
|
|
||||||
);
|
|
||||||
|
|
||||||
verify(
|
|
||||||
() => bloc.add(
|
|
||||||
Scored(points: fakeScorePoints.points),
|
|
||||||
),
|
|
||||||
).called(1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
|
||||||
'plays a Score sound',
|
|
||||||
() {
|
|
||||||
when(game.read<GameBloc>).thenReturn(bloc);
|
|
||||||
when(() => game.audio).thenReturn(audio);
|
|
||||||
|
|
||||||
BallScorePointsCallback(game).begin(
|
|
||||||
ball,
|
|
||||||
fakeScorePoints,
|
|
||||||
FakeContact(),
|
|
||||||
);
|
|
||||||
|
|
||||||
verify(audio.score).called(1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
|
||||||
"adds a ScoreText component at Ball's position",
|
|
||||||
() {
|
|
||||||
when(game.read<GameBloc>).thenReturn(bloc);
|
|
||||||
when(() => game.audio).thenReturn(audio);
|
|
||||||
|
|
||||||
BallScorePointsCallback(game).begin(
|
|
||||||
ball,
|
|
||||||
fakeScorePoints,
|
|
||||||
FakeContact(),
|
|
||||||
);
|
|
||||||
|
|
||||||
verify(
|
|
||||||
() => game.add(
|
|
||||||
ScoreText(
|
|
||||||
text: fakeScorePoints.points.toString(),
|
|
||||||
position: ball.body.position,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).called(1);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -0,0 +1,100 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
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 'package:pinball_audio/pinball_audio.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ScoringBehaviour', () {
|
||||||
|
group('beginContact', () {
|
||||||
|
late GameBloc bloc;
|
||||||
|
late PinballAudio audio;
|
||||||
|
late Ball ball;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
audio = MockPinballAudio();
|
||||||
|
|
||||||
|
ball = MockBall();
|
||||||
|
final ballBody = MockBody();
|
||||||
|
when(() => ball.body).thenReturn(ballBody);
|
||||||
|
when(() => ballBody.position).thenReturn(Vector2.all(4));
|
||||||
|
});
|
||||||
|
|
||||||
|
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
|
||||||
|
gameBuilder: () => EmptyPinballTestGame(
|
||||||
|
audio: audio,
|
||||||
|
),
|
||||||
|
blocBuilder: () {
|
||||||
|
bloc = MockGameBloc();
|
||||||
|
const state = GameState(
|
||||||
|
score: 0,
|
||||||
|
balls: 0,
|
||||||
|
bonusHistory: [],
|
||||||
|
);
|
||||||
|
whenListen(bloc, Stream.value(state), initialState: state);
|
||||||
|
return bloc;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'emits Scored event with points',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
const points = 20;
|
||||||
|
final scoringBehaviour = ScoringBehaviour(points: points);
|
||||||
|
await game.ensureAdd(scoringBehaviour);
|
||||||
|
|
||||||
|
scoringBehaviour.beginContact(ball, MockContact());
|
||||||
|
|
||||||
|
verify(
|
||||||
|
() => bloc.add(
|
||||||
|
const Scored(points: points),
|
||||||
|
),
|
||||||
|
).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
'plays score sound',
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
const points = 20;
|
||||||
|
final scoringBehaviour = ScoringBehaviour(points: points);
|
||||||
|
await game.ensureAdd(scoringBehaviour);
|
||||||
|
|
||||||
|
scoringBehaviour.beginContact(ball, MockContact());
|
||||||
|
|
||||||
|
verify(audio.score).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameBlocTester.testGameWidget(
|
||||||
|
"adds a ScoreText component at Ball's position with points",
|
||||||
|
setUp: (game, tester) async {
|
||||||
|
const points = 20;
|
||||||
|
final scoringBehaviour = ScoringBehaviour(points: points);
|
||||||
|
await game.ensureAdd(scoringBehaviour);
|
||||||
|
|
||||||
|
scoringBehaviour.beginContact(ball, MockContact());
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
final scoreText = game.descendants().whereType<ScoreText>();
|
||||||
|
expect(scoreText.length, equals(1));
|
||||||
|
expect(
|
||||||
|
scoreText.first.text,
|
||||||
|
equals(points.toString()),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
scoreText.first.position,
|
||||||
|
equals(ball.body.position),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in new issue