feat: defined `ScoringBehavior` and `ScoringContactBehavior` (#329)

* refactor: renamed ScoringBehavior to ScoringContactBehavior

* feat: defined ScoringBehavior

* docs: improved documentation
pull/333/head
Alejandro Santiago 3 years ago committed by GitHub
parent 41aaaa4646
commit 8957b96ae3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,33 +1,77 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template scoring_behavior} /// {@template scoring_behavior}
/// Adds points to the score when the [Ball] contacts the [parent]. /// Adds [_points] to the score and shows a text effect.
///
/// The behavior removes itself after the duration.
/// {@endtemplate} /// {@endtemplate}
class ScoringBehavior extends ContactBehavior with HasGameRef<PinballGame> { class ScoringBehavior extends Component with HasGameRef<PinballGame> {
/// {@macro scoring_behavior} /// {@macto scoring_behavior}
ScoringBehavior({ ScoringBehavior({
required Points points, required Points points,
}) : _points = points; required Vector2 position,
double duration = 1,
}) : _points = points,
_position = position,
_effectController = EffectController(
duration: duration,
);
final Points _points; final Points _points;
final Vector2 _position;
final EffectController _effectController;
@override @override
void beginContact(Object other, Contact contact) { void update(double dt) {
super.beginContact(other, contact); super.update(dt);
if (other is! Ball) return; if (_effectController.completed) {
removeFromParent();
}
}
@override
Future<void> onLoad() async {
gameRef.read<GameBloc>().add(Scored(points: _points.value)); gameRef.read<GameBloc>().add(Scored(points: _points.value));
gameRef.firstChild<ZCanvasComponent>()!.add( await gameRef.firstChild<ZCanvasComponent>()!.add(
ScoreComponent( ScoreComponent(
points: _points, points: _points,
position: other.body.position, position: _position,
effectController: _effectController,
), ),
); );
} }
} }
/// {@template scoring_contact_behavior}
/// Adds points to the score when the [Ball] contacts the [parent].
/// {@endtemplate}
class ScoringContactBehavior extends ContactBehavior
with HasGameRef<PinballGame> {
/// {@macro scoring_contact_behavior}
ScoringContactBehavior({
required Points points,
}) : _points = points;
final Points _points;
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
parent.add(
ScoringBehavior(
points: _points,
position: other.body.position,
),
);
}
}

@ -20,24 +20,24 @@ class AndroidAcres extends Component {
AndroidSpaceship(position: Vector2(-26.5, -28.5)), AndroidSpaceship(position: Vector2(-26.5, -28.5)),
AndroidAnimatronic( AndroidAnimatronic(
children: [ children: [
ScoringBehavior(points: Points.twoHundredThousand), ScoringContactBehavior(points: Points.twoHundredThousand),
], ],
)..initialPosition = Vector2(-26, -28.25), )..initialPosition = Vector2(-26, -28.25),
AndroidBumper.a( AndroidBumper.a(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(-25, 1.3), )..initialPosition = Vector2(-25, 1.3),
AndroidBumper.b( AndroidBumper.b(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(-32.8, -9.2), )..initialPosition = Vector2(-32.8, -9.2),
AndroidBumper.cow( AndroidBumper.cow(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(-20.5, -13.8), )..initialPosition = Vector2(-20.5, -13.8),

@ -52,7 +52,8 @@ class _BottomGroupSide extends Component {
final kicker = Kicker( final kicker = Kicker(
side: _side, side: _side,
children: [ children: [
ScoringBehavior(points: Points.fiveThousand)..applyTo(['bouncy_edge']), ScoringContactBehavior(points: Points.fiveThousand)
..applyTo(['bouncy_edge']),
], ],
)..initialPosition = Vector2( )..initialPosition = Vector2(
(22.64 * direction) + centerXAdjustment, (22.64 * direction) + centerXAdjustment,

@ -17,7 +17,7 @@ class DinoDesert extends Component {
children: [ children: [
ChromeDino( ChromeDino(
children: [ children: [
ScoringBehavior(points: Points.twoHundredThousand) ScoringContactBehavior(points: Points.twoHundredThousand)
..applyTo(['inside_mouth']), ..applyTo(['inside_mouth']),
], ],
)..initialPosition = Vector2(12.6, -6.9), )..initialPosition = Vector2(12.6, -6.9),

@ -18,25 +18,25 @@ class FlutterForest extends Component with ZIndex {
children: [ children: [
Signpost( Signpost(
children: [ children: [
ScoringBehavior(points: Points.fiveThousand), ScoringContactBehavior(points: Points.fiveThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(8.35, -58.3), )..initialPosition = Vector2(8.35, -58.3),
DashNestBumper.main( DashNestBumper.main(
children: [ children: [
ScoringBehavior(points: Points.twoHundredThousand), ScoringContactBehavior(points: Points.twoHundredThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(18.55, -59.35), )..initialPosition = Vector2(18.55, -59.35),
DashNestBumper.a( DashNestBumper.a(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(8.95, -51.95), )..initialPosition = Vector2(8.95, -51.95),
DashNestBumper.b( DashNestBumper.b(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(22.3, -46.75), )..initialPosition = Vector2(22.3, -46.75),

@ -16,27 +16,27 @@ class GoogleWord extends Component with ZIndex {
children: [ children: [
GoogleLetter( GoogleLetter(
0, 0,
children: [ScoringBehavior(points: Points.fiveThousand)], children: [ScoringContactBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-13.1, 1.72), )..initialPosition = position + Vector2(-13.1, 1.72),
GoogleLetter( GoogleLetter(
1, 1,
children: [ScoringBehavior(points: Points.fiveThousand)], children: [ScoringContactBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-8.33, -0.75), )..initialPosition = position + Vector2(-8.33, -0.75),
GoogleLetter( GoogleLetter(
2, 2,
children: [ScoringBehavior(points: Points.fiveThousand)], children: [ScoringContactBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(-2.88, -1.85), )..initialPosition = position + Vector2(-2.88, -1.85),
GoogleLetter( GoogleLetter(
3, 3,
children: [ScoringBehavior(points: Points.fiveThousand)], children: [ScoringContactBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(2.88, -1.85), )..initialPosition = position + Vector2(2.88, -1.85),
GoogleLetter( GoogleLetter(
4, 4,
children: [ScoringBehavior(points: Points.fiveThousand)], children: [ScoringContactBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(8.33, -0.75), )..initialPosition = position + Vector2(8.33, -0.75),
GoogleLetter( GoogleLetter(
5, 5,
children: [ScoringBehavior(points: Points.fiveThousand)], children: [ScoringContactBehavior(points: Points.fiveThousand)],
)..initialPosition = position + Vector2(13.1, 1.72), )..initialPosition = position + Vector2(13.1, 1.72),
GoogleWordBonusBehavior(), GoogleWordBonusBehavior(),
], ],

@ -17,19 +17,19 @@ class SparkyScorch extends Component {
children: [ children: [
SparkyBumper.a( SparkyBumper.a(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(-22.9, -41.65), )..initialPosition = Vector2(-22.9, -41.65),
SparkyBumper.b( SparkyBumper.b(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(-21.25, -57.9), )..initialPosition = Vector2(-21.25, -57.9),
SparkyBumper.c( SparkyBumper.c(
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
BumperNoisyBehavior(), BumperNoisyBehavior(),
], ],
)..initialPosition = Vector2(-3.3, -52.55), )..initialPosition = Vector2(-3.3, -52.55),
@ -51,7 +51,7 @@ class SparkyComputerSensor extends BodyComponent
: super( : super(
renderBody: false, renderBody: false,
children: [ children: [
ScoringBehavior(points: Points.twentyThousand), ScoringContactBehavior(points: Points.twentyThousand),
], ],
); );

@ -23,16 +23,20 @@ class ScoreComponent extends SpriteComponent with HasGameRef, ZIndex {
ScoreComponent({ ScoreComponent({
required this.points, required this.points,
required Vector2 position, required Vector2 position,
}) : super( required EffectController effectController,
}) : _effectController = effectController,
super(
position: position, position: position,
anchor: Anchor.center, anchor: Anchor.center,
) { ) {
zIndex = ZIndexes.score; zIndex = ZIndexes.score;
} }
late Points points;
late final Effect _effect; late final Effect _effect;
late Points points; final EffectController _effectController;
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
@ -46,7 +50,7 @@ class ScoreComponent extends SpriteComponent with HasGameRef, ZIndex {
await add( await add(
_effect = MoveEffect.by( _effect = MoveEffect.by(
Vector2(0, -5), Vector2(0, -5),
EffectController(duration: 1), _effectController,
), ),
); );
} }

@ -1,3 +1,4 @@
import 'package:flame/effects.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart' as components; import 'package:pinball_components/pinball_components.dart' as components;
import 'package:pinball_theme/pinball_theme.dart'; import 'package:pinball_theme/pinball_theme.dart';
@ -52,6 +53,7 @@ class BackboardGameOverGame extends AssetsGame
points: components.Points.values points: components.Points.values
.firstWhere((element) => element.value == score), .firstWhere((element) => element.value == score),
position: Vector2(0, 50), position: Vector2(0, 50),
effectController: EffectController(duration: 1),
), ),
); );
}, },

@ -1,5 +1,6 @@
import 'dart:math'; import 'dart:math';
import 'package:flame/effects.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart'; import 'package:sandbox/common/common.dart';
@ -38,6 +39,7 @@ class ScoreGame extends AssetsGame with TapDetector {
ScoreComponent( ScoreComponent(
points: score, points: score,
position: info.eventPosition.game..multiply(Vector2(1, -1)), position: info.eventPosition.game..multiply(Vector2(1, -1)),
effectController: EffectController(duration: 1),
), ),
); );
} }

@ -28,6 +28,7 @@ void main() {
ScoreComponent( ScoreComponent(
points: Points.oneMillion, points: Points.oneMillion,
position: Vector2.zero(), position: Vector2.zero(),
effectController: EffectController(duration: 1),
), ),
); );
}, },
@ -46,6 +47,7 @@ void main() {
ScoreComponent( ScoreComponent(
points: Points.oneMillion, points: Points.oneMillion,
position: Vector2.zero(), position: Vector2.zero(),
effectController: EffectController(duration: 1),
), ),
); );
@ -67,6 +69,7 @@ void main() {
ScoreComponent( ScoreComponent(
points: Points.oneMillion, points: Points.oneMillion,
position: Vector2.zero(), position: Vector2.zero(),
effectController: EffectController(duration: 1),
), ),
); );
@ -88,6 +91,7 @@ void main() {
ScoreComponent( ScoreComponent(
points: Points.fiveThousand, points: Points.fiveThousand,
position: Vector2.zero(), position: Vector2.zero(),
effectController: EffectController(duration: 1),
), ),
); );
@ -113,6 +117,7 @@ void main() {
ScoreComponent( ScoreComponent(
points: Points.twentyThousand, points: Points.twentyThousand,
position: Vector2.zero(), position: Vector2.zero(),
effectController: EffectController(duration: 1),
), ),
); );
@ -138,6 +143,7 @@ void main() {
ScoreComponent( ScoreComponent(
points: Points.twoHundredThousand, points: Points.twoHundredThousand,
position: Vector2.zero(), position: Vector2.zero(),
effectController: EffectController(duration: 1),
), ),
); );
@ -163,6 +169,7 @@ void main() {
ScoreComponent( ScoreComponent(
points: Points.oneMillion, points: Points.oneMillion,
position: Vector2.zero(), position: Vector2.zero(),
effectController: EffectController(duration: 1),
), ),
); );

@ -34,80 +34,177 @@ void main() {
Assets.images.score.oneMillion.keyName, Assets.images.score.oneMillion.keyName,
]; ];
group('ScoringBehavior', () { late GameBloc bloc;
group('beginContact', () { late Ball ball;
late GameBloc bloc; late BodyComponent parent;
late Ball ball;
late BodyComponent parent; setUp(() {
ball = _MockBall();
setUp(() { final ballBody = _MockBody();
ball = _MockBall(); when(() => ball.body).thenReturn(ballBody);
final ballBody = _MockBody(); when(() => ballBody.position).thenReturn(Vector2.all(4));
when(() => ball.body).thenReturn(ballBody);
when(() => ballBody.position).thenReturn(Vector2.all(4)); parent = _TestBodyComponent();
});
parent = _TestBodyComponent();
});
final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
gameBuilder: EmptyPinballTestGame.new,
blocBuilder: () {
bloc = _MockGameBloc();
const state = GameState(
score: 0,
multiplier: 1,
rounds: 3,
bonusHistory: [],
);
whenListen(bloc, Stream.value(state), initialState: state);
return bloc;
},
assets: assets,
);
flameBlocTester.testGameWidget( final flameBlocTester = FlameBlocTester<EmptyPinballTestGame, GameBloc>(
'emits Scored event with points', gameBuilder: EmptyPinballTestGame.new,
setUp: (game, tester) async { blocBuilder: () {
const points = Points.oneMillion; bloc = _MockGameBloc();
final scoringBehavior = ScoringBehavior(points: points); const state = GameState(
await parent.add(scoringBehavior); score: 0,
final canvas = ZCanvasComponent(children: [parent]); multiplier: 1,
await game.ensureAdd(canvas); rounds: 3,
bonusHistory: [],
scoringBehavior.beginContact(ball, _MockContact());
verify(
() => bloc.add(
Scored(points: points.value),
),
).called(1);
},
); );
whenListen(bloc, Stream.value(state), initialState: state);
return bloc;
},
assets: assets,
);
flameBlocTester.testGameWidget( group('ScoringBehavior', () {
"adds a ScoreComponent at Ball's position with points", test('can be instantiated', () {
setUp: (game, tester) async { expect(
const points = Points.oneMillion; ScoringBehavior(
final scoringBehavior = ScoringBehavior(points: points); points: Points.fiveThousand,
await parent.add(scoringBehavior); position: Vector2.zero(),
final canvas = ZCanvasComponent(children: [parent]); ),
await game.ensureAdd(canvas); isA<ScoringBehavior>(),
scoringBehavior.beginContact(ball, _MockContact());
await game.ready();
final scoreText = game.descendants().whereType<ScoreComponent>();
expect(scoreText.length, equals(1));
expect(
scoreText.first.points,
equals(points),
);
expect(
scoreText.first.position,
equals(ball.body.position),
);
},
); );
}); });
flameBlocTester.testGameWidget(
'can be loaded',
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
final behavior = ScoringBehavior(
points: Points.fiveThousand,
position: Vector2.zero(),
);
await parent.add(behavior);
await game.ensureAdd(canvas);
expect(
parent.firstChild<ScoringBehavior>(),
equals(behavior),
);
},
);
flameBlocTester.testGameWidget(
'emits Scored event with points when added',
setUp: (game, tester) async {
const points = Points.oneMillion;
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
final behavior = ScoringBehavior(
points: points,
position: Vector2(0, 0),
);
await parent.ensureAdd(behavior);
verify(
() => bloc.add(
Scored(points: points.value),
),
).called(1);
},
);
flameBlocTester.testGameWidget(
'correctly renders text',
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
const points = Points.oneMillion;
final position = Vector2.all(1);
final behavior = ScoringBehavior(
points: points,
position: position,
);
await parent.ensureAdd(behavior);
final scoreText = game.descendants().whereType<ScoreComponent>();
expect(scoreText.length, equals(1));
expect(
scoreText.first.points,
equals(points),
);
expect(
scoreText.first.position,
equals(position),
);
},
);
flameBlocTester.testGameWidget(
'is removed after duration',
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
const duration = 2.0;
final behavior = ScoringBehavior(
points: Points.oneMillion,
position: Vector2(0, 0),
duration: duration,
);
await parent.ensureAdd(behavior);
game.update(duration);
game.update(0);
await tester.pump();
},
verify: (game, _) async {
expect(
game.descendants().whereType<ScoringBehavior>(),
isEmpty,
);
},
);
});
group('ScoringContactBehavior', () {
flameBlocTester.testGameWidget(
'beginContact adds a ScoringBehavior',
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
final behavior = ScoringContactBehavior(points: Points.oneMillion);
await parent.ensureAdd(behavior);
behavior.beginContact(ball, _MockContact());
await game.ready();
expect(
parent.firstChild<ScoringBehavior>(),
isNotNull,
);
},
);
flameBlocTester.testGameWidget(
"beginContact positions text at contact's position",
setUp: (game, tester) async {
final canvas = ZCanvasComponent(children: [parent]);
await game.ensureAdd(canvas);
final behavior = ScoringContactBehavior(points: Points.oneMillion);
await parent.ensureAdd(behavior);
behavior.beginContact(ball, _MockContact());
await game.ready();
final scoreText = game.descendants().whereType<ScoreComponent>();
expect(
scoreText.first.position,
equals(ball.body.position),
);
},
);
}); });
} }

@ -68,13 +68,13 @@ void main() {
group('adds', () { group('adds', () {
flameTester.test( flameTester.test(
'ScoringBehavior to ChromeDino', 'ScoringContactBehavior to ChromeDino',
(game) async { (game) async {
await game.ensureAdd(DinoDesert()); await game.ensureAdd(DinoDesert());
final chromeDino = game.descendants().whereType<ChromeDino>().single; final chromeDino = game.descendants().whereType<ChromeDino>().single;
expect( expect(
chromeDino.firstChild<ScoringBehavior>(), chromeDino.firstChild<ScoringContactBehavior>(),
isNotNull, isNotNull,
); );
}, },

Loading…
Cancel
Save