Merge branch 'main' into feat/spaceship-ramp-shot-logic

pull/296/head
RuiAlonso 3 years ago
commit 9e71d746fc

@ -67,7 +67,9 @@ class BallController extends ComponentController<Ball>
const Duration(milliseconds: 2583), const Duration(milliseconds: 2583),
); );
component.resume(); component.resume();
await component.boost(Vector2(40, 110)); await component.add(
BallTurboChargingBehavior(impulse: Vector2(40, 110)),
);
} }
@override @override

@ -12,6 +12,7 @@ class Launcher extends Component {
: super( : super(
children: [ children: [
LaunchRamp(), LaunchRamp(),
Flapper(),
ControlledPlunger(compressionDistance: 9.2) ControlledPlunger(compressionDistance: 9.2)
..initialPosition = Vector2(41.2, 43.7), ..initialPosition = Vector2(41.2, 43.7),
RocketSpriteComponent()..position = Vector2(43, 62.3), RocketSpriteComponent()..position = Vector2(43, 62.3),

@ -130,6 +130,9 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.score.twentyThousand.keyName), images.load(components.Assets.images.score.twentyThousand.keyName),
images.load(components.Assets.images.score.twoHundredThousand.keyName), images.load(components.Assets.images.score.twoHundredThousand.keyName),
images.load(components.Assets.images.score.oneMillion.keyName), images.load(components.Assets.images.score.oneMillion.keyName),
images.load(components.Assets.images.flapper.backSupport.keyName),
images.load(components.Assets.images.flapper.frontSupport.keyName),
images.load(components.Assets.images.flapper.flap.keyName),
images.load(dashTheme.leaderboardIcon.keyName), images.load(dashTheme.leaderboardIcon.keyName),
images.load(sparkyTheme.leaderboardIcon.keyName), images.load(sparkyTheme.leaderboardIcon.keyName),
images.load(androidTheme.leaderboardIcon.keyName), images.load(androidTheme.leaderboardIcon.keyName),

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -22,6 +22,7 @@ class $AssetsImagesGen {
$AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen(); $AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen();
$AssetsImagesDashGen get dash => const $AssetsImagesDashGen(); $AssetsImagesDashGen get dash => const $AssetsImagesDashGen();
$AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen(); $AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen();
$AssetsImagesFlapperGen get flapper => const $AssetsImagesFlapperGen();
$AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen(); $AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen();
$AssetsImagesGoogleWordGen get googleWord => $AssetsImagesGoogleWordGen get googleWord =>
const $AssetsImagesGoogleWordGen(); const $AssetsImagesGoogleWordGen();
@ -133,6 +134,22 @@ class $AssetsImagesDinoGen {
const AssetGenImage('assets/images/dino/top-wall.png'); const AssetGenImage('assets/images/dino/top-wall.png');
} }
class $AssetsImagesFlapperGen {
const $AssetsImagesFlapperGen();
/// File path: assets/images/flapper/back-support.png
AssetGenImage get backSupport =>
const AssetGenImage('assets/images/flapper/back-support.png');
/// File path: assets/images/flapper/flap.png
AssetGenImage get flap =>
const AssetGenImage('assets/images/flapper/flap.png');
/// File path: assets/images/flapper/front-support.png
AssetGenImage get frontSupport =>
const AssetGenImage('assets/images/flapper/front-support.png');
}
class $AssetsImagesFlipperGen { class $AssetsImagesFlipperGen {
const $AssetsImagesFlipperGen(); const $AssetsImagesFlipperGen();

@ -1,14 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/ball_gravitating_behavior.dart';
import 'package:pinball_components/src/components/ball/behaviors/ball_scaling_behavior.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
export 'behaviors/behaviors.dart';
/// {@template ball} /// {@template ball}
/// A solid, [BodyType.dynamic] sphere that rolls and bounces around. /// A solid, [BodyType.dynamic] sphere that rolls and bounces around.
/// {@endtemplate} /// {@endtemplate}
@ -81,12 +80,6 @@ class Ball extends BodyComponent with Layered, InitialPosition, ZIndex {
void resume() { void resume() {
body.gravityScale = Vector2(1, 1); body.gravityScale = Vector2(1, 1);
} }
/// Applies a boost and [_TurboChargeSpriteAnimationComponent] on this [Ball].
Future<void> boost(Vector2 impulse) async {
body.linearVelocity = impulse;
await add(_TurboChargeSpriteAnimationComponent());
}
} }
class _BallSpriteComponent extends SpriteComponent with HasGameRef { class _BallSpriteComponent extends SpriteComponent with HasGameRef {
@ -101,55 +94,3 @@ class _BallSpriteComponent extends SpriteComponent with HasGameRef {
anchor = Anchor.center; anchor = Anchor.center;
} }
} }
class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent
with HasGameRef, ZIndex {
_TurboChargeSpriteAnimationComponent()
: super(
anchor: const Anchor(0.53, 0.72),
removeOnFinish: true,
) {
zIndex = ZIndexes.turboChargeFlame;
}
late final Vector2 _textureSize;
@override
Future<void> onLoad() async {
await super.onLoad();
final spriteSheet = await gameRef.images.load(
Assets.images.ball.flameEffect.keyName,
);
const amountPerRow = 8;
const amountPerColumn = 4;
_textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: _textureSize,
loop: false,
),
);
}
@override
void update(double dt) {
super.update(dt);
if (parent != null) {
final body = (parent! as BodyComponent).body;
final direction = -body.linearVelocity.normalized();
angle = math.atan2(direction.x, -direction.y);
size = (_textureSize / 45) * body.fixtures.first.shape.radius;
}
}
}

@ -0,0 +1,81 @@
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template ball_turbo_charging_behavior}
/// Puts the [Ball] in flames and [_impulse]s it.
/// {@endtemplate}
class BallTurboChargingBehavior extends TimerComponent with ParentIsA<Ball> {
/// {@macro ball_turbo_charging_behavior}
BallTurboChargingBehavior({
required Vector2 impulse,
}) : _impulse = impulse,
super(period: 5, removeOnFinish: true);
final Vector2 _impulse;
@override
Future<void> onLoad() async {
await super.onLoad();
parent.body.linearVelocity = _impulse;
await parent.add(_TurboChargeSpriteAnimationComponent());
}
@override
void onRemove() {
parent
.firstChild<_TurboChargeSpriteAnimationComponent>()!
.removeFromParent();
super.onRemove();
}
}
class _TurboChargeSpriteAnimationComponent extends SpriteAnimationComponent
with HasGameRef, ZIndex, ParentIsA<Ball> {
_TurboChargeSpriteAnimationComponent()
: super(
anchor: const Anchor(0.53, 0.72),
) {
zIndex = ZIndexes.turboChargeFlame;
}
late final Vector2 _textureSize;
@override
void update(double dt) {
super.update(dt);
final direction = -parent.body.linearVelocity.normalized();
angle = math.atan2(direction.x, -direction.y);
size = (_textureSize / 45) * parent.body.fixtures.first.shape.radius;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final spriteSheet = await gameRef.images.load(
Assets.images.ball.flameEffect.keyName,
);
const amountPerRow = 8;
const amountPerColumn = 4;
_textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: _textureSize,
),
);
}
}

@ -1,2 +1,3 @@
export 'ball_gravitating_behavior.dart'; export 'ball_gravitating_behavior.dart';
export 'ball_scaling_behavior.dart'; export 'ball_scaling_behavior.dart';
export 'ball_turbo_charging_behavior.dart';

@ -14,6 +14,7 @@ export 'dash_animatronic.dart';
export 'dash_nest_bumper/dash_nest_bumper.dart'; export 'dash_nest_bumper/dash_nest_bumper.dart';
export 'dino_walls.dart'; export 'dino_walls.dart';
export 'fire_effect.dart'; export 'fire_effect.dart';
export 'flapper/flapper.dart';
export 'flipper.dart'; export 'flipper.dart';
export 'google_letter/google_letter.dart'; export 'google_letter/google_letter.dart';
export 'initial_position.dart'; export 'initial_position.dart';

@ -0,0 +1,15 @@
// ignore_for_file: public_member_api_docs
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
class FlapperSpinningBehavior extends ContactBehavior<FlapperEntrance> {
@override
void beginContact(Object other, Contact contact) {
super.beginContact(other, contact);
if (other is! Ball) return;
parent.parent?.firstChild<SpriteAnimationComponent>()?.playing = true;
}
}

@ -0,0 +1,215 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/flapper/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template flapper}
/// Flap to let a [Ball] out of the [LaunchRamp] and to prevent [Ball]s from
/// going back in.
/// {@endtemplate}
class Flapper extends Component {
/// {@macro flapper}
Flapper()
: super(
children: [
FlapperEntrance(
children: [
FlapperSpinningBehavior(),
],
)..initialPosition = Vector2(4, -69.3),
_FlapperStructure(),
_FlapperExit()..initialPosition = Vector2(-0.6, -33.8),
_BackSupportSpriteComponent(),
_FrontSupportSpriteComponent(),
FlapSpriteAnimationComponent(),
],
);
/// Creates a [Flapper] without any children.
///
/// This can be used for testing [Flapper]'s behaviors in isolation.
@visibleForTesting
Flapper.test();
}
/// {@template flapper_entrance}
/// Sensor used in [FlapperSpinningBehavior] to animate
/// [FlapSpriteAnimationComponent].
/// {@endtemplate}
class FlapperEntrance extends BodyComponent with InitialPosition, Layered {
/// {@macro flapper_entrance}
FlapperEntrance({
Iterable<Component>? children,
}) : super(
children: children,
renderBody: false,
) {
layer = Layer.launcher;
}
@override
Body createBody() {
final shape = EdgeShape()
..set(
Vector2.zero(),
Vector2(0, 3.2),
);
final fixtureDef = FixtureDef(
shape,
isSensor: true,
);
final bodyDef = BodyDef(position: initialPosition);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
class _FlapperStructure extends BodyComponent with Layered {
_FlapperStructure() : super(renderBody: false) {
layer = Layer.board;
}
List<FixtureDef> _createFixtureDefs() {
final leftEdgeShape = EdgeShape()
..set(
Vector2(1.9, -69.3),
Vector2(1.9, -66),
);
final bottomEdgeShape = EdgeShape()
..set(
leftEdgeShape.vertex2,
Vector2(3.9, -66),
);
return [
FixtureDef(leftEdgeShape),
FixtureDef(bottomEdgeShape),
];
}
@override
Body createBody() {
final body = world.createBody(BodyDef());
_createFixtureDefs().forEach(body.createFixture);
return body;
}
}
class _FlapperExit extends LayerSensor {
_FlapperExit()
: super(
insideLayer: Layer.launcher,
outsideLayer: Layer.board,
orientation: LayerEntranceOrientation.down,
insideZIndex: ZIndexes.ballOnLaunchRamp,
outsideZIndex: ZIndexes.ballOnBoard,
) {
layer = Layer.launcher;
}
@override
Shape get shape => PolygonShape()
..setAsBox(
1.7,
0.1,
initialPosition,
1.5708,
);
}
/// {@template flap_sprite_animation_component}
/// Flap suspended between supports that animates to let the [Ball] exit the
/// [LaunchRamp].
/// {@endtemplate}
@visibleForTesting
class FlapSpriteAnimationComponent extends SpriteAnimationComponent
with HasGameRef, ZIndex {
/// {@macro flap_sprite_animation_component}
FlapSpriteAnimationComponent()
: super(
anchor: Anchor.center,
position: Vector2(2.8, -70.7),
playing: false,
) {
zIndex = ZIndexes.flapper;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final spriteSheet = gameRef.images.fromCache(
Assets.images.flapper.flap.keyName,
);
const amountPerRow = 14;
const amountPerColumn = 1;
final textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
size = textureSize / 10;
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: amountPerRow * amountPerColumn,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
loop: false,
),
)..onComplete = () {
animation?.reset();
playing = false;
};
}
}
class _BackSupportSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_BackSupportSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(2.95, -70.6),
) {
zIndex = ZIndexes.flapperBack;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.flapper.backSupport.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}
class _FrontSupportSpriteComponent extends SpriteComponent
with HasGameRef, ZIndex {
_FrontSupportSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(2.9, -67.6),
) {
zIndex = ZIndexes.flapperFront;
}
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.flapper.frontSupport.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}

@ -1,7 +1,5 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -17,8 +15,6 @@ class LaunchRamp extends Component {
children: [ children: [
_LaunchRampBase(), _LaunchRampBase(),
_LaunchRampForegroundRailing(), _LaunchRampForegroundRailing(),
_LaunchRampExit()..initialPosition = Vector2(0.6, -34),
_LaunchRampCloseWall()..initialPosition = Vector2(4, -69.5),
], ],
); );
} }
@ -109,8 +105,10 @@ class _LaunchRampBaseSpriteComponent extends SpriteComponent with HasGameRef {
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
Assets.images.launchRamp.ramp.keyName, gameRef.images.fromCache(
Assets.images.launchRamp.ramp.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
@ -125,8 +123,10 @@ class _LaunchRampBackgroundRailingSpriteComponent extends SpriteComponent
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
Assets.images.launchRamp.backgroundRailing.keyName, gameRef.images.fromCache(
Assets.images.launchRamp.backgroundRailing.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
@ -190,8 +190,10 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent
Future<void> onLoad() async { Future<void> onLoad() async {
await super.onLoad(); await super.onLoad();
final sprite = await gameRef.loadSprite( final sprite = Sprite(
Assets.images.launchRamp.foregroundRailing.keyName, gameRef.images.fromCache(
Assets.images.launchRamp.foregroundRailing.keyName,
),
); );
this.sprite = sprite; this.sprite = sprite;
size = sprite.originalSize / 10; size = sprite.originalSize / 10;
@ -199,51 +201,3 @@ class _LaunchRampForegroundRailingSpriteComponent extends SpriteComponent
position = Vector2(22.8, 0.5); position = Vector2(22.8, 0.5);
} }
} }
class _LaunchRampCloseWall extends BodyComponent with InitialPosition, Layered {
_LaunchRampCloseWall() : super(renderBody: false) {
layer = Layer.board;
}
@override
Body createBody() {
final shape = EdgeShape()..set(Vector2.zero(), Vector2(0, 3));
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef()
..userData = this
..position = initialPosition;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
/// {@template launch_ramp_exit}
/// [LayerSensor] with [Layer.launcher] to filter [Ball]s exiting the
/// [LaunchRamp].
/// {@endtemplate}
class _LaunchRampExit extends LayerSensor {
/// {@macro launch_ramp_exit}
_LaunchRampExit()
: super(
insideLayer: Layer.launcher,
outsideLayer: Layer.board,
orientation: LayerEntranceOrientation.down,
insideZIndex: ZIndexes.ballOnLaunchRamp,
outsideZIndex: ZIndexes.ballOnBoard,
) {
layer = Layer.launcher;
}
static final Vector2 _size = Vector2(1.6, 0.1);
@override
Shape get shape => PolygonShape()
..setAsBox(
_size.x,
_size.y,
initialPosition,
math.pi / 2,
);
}

@ -45,6 +45,12 @@ abstract class ZIndexes {
static const launchRampForegroundRailing = _above + ballOnLaunchRamp; static const launchRampForegroundRailing = _above + ballOnLaunchRamp;
static const flapperBack = _above + outerBoundary;
static const flapperFront = _above + flapper;
static const flapper = _above + ballOnLaunchRamp;
static const plunger = _above + launchRamp; static const plunger = _above + launchRamp;
static const rocket = _below + bottomBoundary; static const rocket = _below + bottomBoundary;

@ -88,6 +88,7 @@ flutter:
- assets/images/multiplier/x5/ - assets/images/multiplier/x5/
- assets/images/multiplier/x6/ - assets/images/multiplier/x6/
- assets/images/score/ - assets/images/score/
- assets/images/flapper/
flutter_gen: flutter_gen:
line_length: 80 line_length: 80

@ -13,8 +13,9 @@ class BallBoosterGame extends LineGame {
@override @override
void onLine(Vector2 line) { void onLine(Vector2 line) {
final ball = Ball(baseColor: Colors.transparent); final ball = Ball(baseColor: Colors.transparent);
add(ball); final impulse = line * -1 * 20;
ball.add(BallTurboChargingBehavior(impulse: impulse));
ball.mounted.then((value) => ball.boost(line * -1 * 20)); add(ball);
} }
} }

@ -1,12 +1,10 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/behaviors.dart';
import '../../../helpers/helpers.dart'; import '../../../helpers/helpers.dart';
@ -180,50 +178,5 @@ void main() {
); );
}); });
}); });
group('boost', () {
flameTester.test('applies an impulse to the ball', (game) async {
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
expect(ball.body.linearVelocity, equals(Vector2.zero()));
await ball.boost(Vector2.all(10));
expect(ball.body.linearVelocity.x, greaterThan(0));
expect(ball.body.linearVelocity.y, greaterThan(0));
});
flameTester.test('adds TurboChargeSpriteAnimation', (game) async {
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
await ball.boost(Vector2.all(10));
game.update(0);
expect(
ball.children.whereType<SpriteAnimationComponent>().single,
isNotNull,
);
});
flameTester.test('removes TurboChargeSpriteAnimation after it finishes',
(game) async {
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
await ball.boost(Vector2.all(10));
game.update(0);
final turboChargeSpriteAnimation =
ball.children.whereType<SpriteAnimationComponent>().single;
expect(ball.contains(turboChargeSpriteAnimation), isTrue);
game.update(turboChargeSpriteAnimation.animation!.totalDuration());
game.update(0.1);
expect(ball.contains(turboChargeSpriteAnimation), isFalse);
});
});
}); });
} }

@ -6,7 +6,6 @@ import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/behaviors.dart';
import '../../../../helpers/helpers.dart'; import '../../../../helpers/helpers.dart';

@ -6,7 +6,6 @@ import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_components/src/components/ball/behaviors/behaviors.dart';
import '../../../../helpers/helpers.dart'; import '../../../../helpers/helpers.dart';

@ -0,0 +1,94 @@
// ignore_for_file: cascade_invocations
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group(
'BallTurboChargingBehavior',
() {
final assets = [Assets.images.ball.ball.keyName];
final flameTester = FlameTester(() => TestGame(assets));
const baseColor = Color(0xFFFFFFFF);
test('can be instantiated', () {
expect(
BallTurboChargingBehavior(impulse: Vector2.zero()),
isA<BallTurboChargingBehavior>(),
);
});
flameTester.test('can be loaded', (game) async {
final ball = Ball.test(baseColor: baseColor);
final behavior = BallTurboChargingBehavior(impulse: Vector2.zero());
await ball.add(behavior);
await game.ensureAdd(ball);
expect(
ball.firstChild<BallTurboChargingBehavior>(),
equals(behavior),
);
});
flameTester.test(
'impulses the ball velocity when loaded',
(game) async {
final ball = Ball.test(baseColor: baseColor);
await game.ensureAdd(ball);
final impulse = Vector2.all(1);
final behavior = BallTurboChargingBehavior(impulse: impulse);
await ball.ensureAdd(behavior);
expect(
ball.body.linearVelocity.x,
equals(impulse.x),
);
expect(
ball.body.linearVelocity.y,
equals(impulse.y),
);
},
);
flameTester.test('adds sprite', (game) async {
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
await ball.ensureAdd(
BallTurboChargingBehavior(impulse: Vector2.zero()),
);
expect(
ball.children.whereType<SpriteAnimationComponent>().single,
isNotNull,
);
});
flameTester.test('removes sprite after it finishes', (game) async {
final ball = Ball(baseColor: baseColor);
await game.ensureAdd(ball);
final behavior = BallTurboChargingBehavior(impulse: Vector2.zero());
await ball.ensureAdd(behavior);
final turboChargeSpriteAnimation =
ball.children.whereType<SpriteAnimationComponent>().single;
expect(ball.contains(turboChargeSpriteAnimation), isTrue);
game.update(behavior.timer.limit);
game.update(0.1);
expect(ball.contains(turboChargeSpriteAnimation), isFalse);
});
},
);
}

@ -0,0 +1,53 @@
// 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_components/pinball_components.dart';
import 'package:pinball_components/src/components/flapper/behaviors/behaviors.dart';
import '../../../../helpers/helpers.dart';
class _MockBall extends Mock implements Ball {}
class _MockContact extends Mock implements Contact {}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.flapper.flap.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group(
'FlapperSpinningBehavior',
() {
test('can be instantiated', () {
expect(
FlapperSpinningBehavior(),
isA<FlapperSpinningBehavior>(),
);
});
flameTester.test(
'beginContact plays the flapper animation',
(game) async {
final behavior = FlapperSpinningBehavior();
final entrance = FlapperEntrance();
final flap = FlapSpriteAnimationComponent();
final flapper = Flapper.test();
await flapper.addAll([entrance, flap]);
await entrance.add(behavior);
await game.ensureAdd(flapper);
behavior.beginContact(_MockBall(), _MockContact());
expect(flap.playing, isTrue);
},
);
},
);
}

@ -0,0 +1,100 @@
// 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_components/pinball_components.dart';
import 'package:pinball_components/src/components/flapper/behaviors/behaviors.dart';
import 'package:pinball_flame/pinball_flame.dart';
import '../../../helpers/helpers.dart';
void main() {
group('Flapper', () {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.flapper.flap.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.test('loads correctly', (game) async {
final component = Flapper();
await game.ensureAdd(component);
expect(game.contains(component), isTrue);
});
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.images.loadAll(assets);
final canvas = ZCanvasComponent(children: [Flapper()]);
await game.ensureAdd(canvas);
game.camera
..followVector2(Vector2(3, -70))
..zoom = 25;
await tester.pump();
},
verify: (game, tester) async {
const goldenFilePath = '../golden/flapper/';
final flapSpriteAnimationComponent = game
.descendants()
.whereType<FlapSpriteAnimationComponent>()
.first
..playing = true;
final animationDuration =
flapSpriteAnimationComponent.animation!.totalDuration();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('${goldenFilePath}start.png'),
);
game.update(animationDuration * 0.25);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('${goldenFilePath}middle.png'),
);
game.update(animationDuration * 0.75);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('${goldenFilePath}end.png'),
);
},
);
flameTester.test('adds a FlapperSpiningBehavior to FlapperEntrance',
(game) async {
final flapper = Flapper();
await game.ensureAdd(flapper);
final flapperEntrance = flapper.firstChild<FlapperEntrance>()!;
expect(
flapperEntrance.firstChild<FlapperSpinningBehavior>(),
isNotNull,
);
});
flameTester.test(
'flap stops animating after animation completes',
(game) async {
final flapper = Flapper();
await game.ensureAdd(flapper);
final flapSpriteAnimationComponent =
flapper.firstChild<FlapSpriteAnimationComponent>()!;
flapSpriteAnimationComponent.playing = true;
game.update(
flapSpriteAnimationComponent.animation!.totalDuration() + 0.1,
);
expect(flapSpriteAnimationComponent.playing, isFalse);
},
);
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@ -9,7 +9,13 @@ import '../../helpers/helpers.dart';
void main() { void main() {
group('LaunchRamp', () { group('LaunchRamp', () {
final flameTester = FlameTester(TestGame.new); TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.launchRamp.ramp.keyName,
Assets.images.launchRamp.backgroundRailing.keyName,
Assets.images.launchRamp.foregroundRailing.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.test('loads correctly', (game) async { flameTester.test('loads correctly', (game) async {
final component = LaunchRamp(); final component = LaunchRamp();
@ -20,9 +26,12 @@ void main() {
flameTester.testGameWidget( flameTester.testGameWidget(
'renders correctly', 'renders correctly',
setUp: (game, tester) async { setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.ensureAdd(LaunchRamp()); await game.ensureAdd(LaunchRamp());
game.camera.followVector2(Vector2.zero()); game.camera.followVector2(Vector2.zero());
game.camera.zoom = 4.1; game.camera.zoom = 4.1;
await game.ready();
await tester.pump();
}, },
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(

@ -1,6 +1,7 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame/extensions.dart'; import 'package:flame/extensions.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
@ -99,6 +100,7 @@ void main() {
group('turboCharge', () { group('turboCharge', () {
setUpAll(() { setUpAll(() {
registerFallbackValue(Vector2.zero()); registerFallbackValue(Vector2.zero());
registerFallbackValue(Component());
}); });
flameBlocTester.testGameWidget( flameBlocTester.testGameWidget(
@ -124,7 +126,7 @@ void main() {
final controller = _WrappedBallController(ball, gameRef); final controller = _WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc); when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller); when(() => ball.controller).thenReturn(controller);
when(() => ball.boost(any())).thenAnswer((_) async {}); when(() => ball.add(any())).thenAnswer((_) async {});
await controller.turboCharge(); await controller.turboCharge();
@ -140,29 +142,13 @@ void main() {
final controller = _WrappedBallController(ball, gameRef); final controller = _WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc); when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller); when(() => ball.controller).thenReturn(controller);
when(() => ball.boost(any())).thenAnswer((_) async {}); when(() => ball.add(any())).thenAnswer((_) async {});
await controller.turboCharge(); await controller.turboCharge();
verify(ball.resume).called(1); verify(ball.resume).called(1);
}, },
); );
flameBlocTester.test(
'boosts the ball',
(game) async {
final gameRef = _MockPinballGame();
final ball = _MockControlledBall();
final controller = _WrappedBallController(ball, gameRef);
when(() => gameRef.read<GameBloc>()).thenReturn(gameBloc);
when(() => ball.controller).thenReturn(controller);
when(() => ball.boost(any())).thenAnswer((_) async {});
await controller.turboCharge();
verify(() => ball.boost(any())).called(1);
},
);
}); });
}); });
} }

@ -0,0 +1,85 @@
// ignore_for_file: cascade_invocations
import 'package:flame_test/flame_test.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 assets = [
Assets.images.launchRamp.ramp.keyName,
Assets.images.launchRamp.backgroundRailing.keyName,
Assets.images.launchRamp.foregroundRailing.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
Assets.images.flapper.flap.keyName,
Assets.images.plunger.plunger.keyName,
Assets.images.plunger.rocket.keyName,
];
final flameTester = FlameTester(
() => EmptyPinballTestGame(assets: assets),
);
group('Launcher', () {
flameTester.test(
'loads correctly',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
expect(game.contains(launcher), isTrue);
},
);
group('loads', () {
flameTester.test(
'a LaunchRamp',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
final descendantsQuery =
launcher.descendants().whereType<LaunchRamp>();
expect(descendantsQuery.length, equals(1));
},
);
flameTester.test(
'a Flapper',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
final descendantsQuery = launcher.descendants().whereType<Flapper>();
expect(descendantsQuery.length, equals(1));
},
);
flameTester.test(
'a Plunger',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
final descendantsQuery = launcher.descendants().whereType<Plunger>();
expect(descendantsQuery.length, equals(1));
},
);
flameTester.test(
'a RocketSpriteComponent',
(game) async {
final launcher = Launcher();
await game.ensureAdd(launcher);
final descendantsQuery =
launcher.descendants().whereType<RocketSpriteComponent>();
expect(descendantsQuery.length, equals(1));
},
);
});
});
}

@ -124,6 +124,9 @@ void main() {
Assets.images.sparky.bumper.b.dimmed.keyName, Assets.images.sparky.bumper.b.dimmed.keyName,
Assets.images.sparky.bumper.c.lit.keyName, Assets.images.sparky.bumper.c.lit.keyName,
Assets.images.sparky.bumper.c.dimmed.keyName, Assets.images.sparky.bumper.c.dimmed.keyName,
Assets.images.flapper.flap.keyName,
Assets.images.flapper.backSupport.keyName,
Assets.images.flapper.frontSupport.keyName,
]; ];
late GameBloc gameBloc; late GameBloc gameBloc;

@ -26,14 +26,13 @@
<!-- Open Graph Data --> <!-- Open Graph Data -->
<meta property="og:title" content="Google I/O Pinball"> <meta property="og:title" content="Google I/O Pinball">
<!-- TODO(jonathandaniels-vgv): revisit once Google sets up deployments --> <meta property="og:url" content="https://pinball.flutter.dev">
<meta property="og:url" content="https://flutter.dev">
<meta property="og:image" <meta property="og:image"
content="https://firebasestorage.googleapis.com/v0/b/pinball-dev.appspot.com/o/images%2Fpinball_share_image.png?alt=media"> content="https://firebasestorage.googleapis.com/v0/b/io-pinball.appspot.com/o/images%2Fpinball_share_image.png?alt=media">
<!-- Twitter Share Data --> <!-- Twitter Share Data -->
<meta name="twitter:image" <meta name="twitter:image"
content="https://firebasestorage.googleapis.com/v0/b/pinball-dev.appspot.com/o/images%2Fpinball_share_image.png?alt=media"> content="https://firebasestorage.googleapis.com/v0/b/io-pinball.appspot.com/o/images%2Fpinball_share_image.png?alt=media">
<meta name="twitter:text:title" content="I/O Pinball Machine - Flutter"> <meta name="twitter:text:title" content="I/O Pinball Machine - Flutter">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="I/O Pinball Machine - Flutter"> <meta name="twitter:title" content="I/O Pinball Machine - Flutter">

Loading…
Cancel
Save