Merge branch 'main' into chore/dialog-background

pull/236/head
arturplaczek 3 years ago committed by GitHub
commit 7bfbdaac3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -18,17 +18,8 @@ class Board extends Component {
final flutterForest = FlutterForest();
// TODO(alestiago): adjust positioning to real design.
// TODO(alestiago): add dino in pinball game.
final dino = ChromeDino()
..initialPosition = Vector2(
BoardDimensions.bounds.center.dx + 25,
BoardDimensions.bounds.center.dy - 10,
);
await addAll([
bottomGroup,
dino,
flutterForest,
]);
}

@ -34,8 +34,10 @@ extension PinballGameAssetsX on PinballGame {
images.load(
components.Assets.images.launchRamp.backgroundRailing.keyName,
),
images.load(components.Assets.images.dino.dinoLandTop.keyName),
images.load(components.Assets.images.dino.dinoLandBottom.keyName),
images.load(components.Assets.images.dino.bottomWall.keyName),
images.load(components.Assets.images.dino.topWall.keyName),
images.load(components.Assets.images.dino.animatronic.head.keyName),
images.load(components.Assets.images.dino.animatronic.mouth.keyName),
images.load(components.Assets.images.dash.animatronic.keyName),
images.load(components.Assets.images.dash.bumper.a.active.keyName),
images.load(components.Assets.images.dash.bumper.a.inactive.keyName),
@ -76,13 +78,11 @@ extension PinballGameAssetsX on PinballGame {
components.Assets.images.spaceship.ramp.arrow.active5.keyName,
),
images.load(components.Assets.images.spaceship.rail.main.keyName),
images.load(components.Assets.images.spaceship.rail.foreground.keyName),
images.load(components.Assets.images.spaceship.rail.exit.keyName),
images.load(components.Assets.images.androidBumper.a.lit.keyName),
images.load(components.Assets.images.androidBumper.a.dimmed.keyName),
images.load(components.Assets.images.androidBumper.b.lit.keyName),
images.load(components.Assets.images.androidBumper.b.dimmed.keyName),
images.load(components.Assets.images.chromeDino.mouth.keyName),
images.load(components.Assets.images.chromeDino.head.keyName),
images.load(components.Assets.images.sparky.computer.top.keyName),
images.load(components.Assets.images.sparky.computer.base.keyName),
images.load(components.Assets.images.sparky.animatronic.keyName),

@ -6,6 +6,7 @@ import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/assets.gen.dart';
@ -18,7 +19,8 @@ class PinballGame extends Forge2DGame
with
FlameBloc,
HasKeyboardHandlerComponents,
Controls<_GameBallsController> {
Controls<_GameBallsController>,
TapDetector {
PinballGame({
required this.characterTheme,
required this.audio,
@ -57,6 +59,7 @@ class PinballGame extends Forge2DGame
await addFromBlueprint(AndroidAcres());
unawaited(addFromBlueprint(Slingshots()));
unawaited(addFromBlueprint(DinoWalls()));
await add(ChromeDino()..initialPosition = Vector2(12.3, -6.9));
await add(
GoogleWord(
position: Vector2(
@ -69,6 +72,61 @@ class PinballGame extends Forge2DGame
controller.attachTo(launcher.components.whereType<Plunger>().first);
await super.onLoad();
}
BoardSide? focusedBoardSide;
@override
void onTapDown(TapDownInfo info) {
if (info.raw.kind == PointerDeviceKind.touch) {
final rocket = children.whereType<RocketSpriteComponent>().first;
final bounds = rocket.topLeftPosition & rocket.size;
// NOTE(wolfen): As long as Flame does not have https://github.com/flame-engine/flame/issues/1586 we need to check it at the highest level manually.
if (bounds.contains(info.eventPosition.game.toOffset())) {
children.whereType<Plunger>().first.pull();
} else {
final leftSide = info.eventPosition.widget.x < canvasSize.x / 2;
focusedBoardSide = leftSide ? BoardSide.left : BoardSide.right;
final flippers = descendants().whereType<Flipper>().where((flipper) {
return flipper.side == focusedBoardSide;
});
flippers.first.moveUp();
}
}
super.onTapDown(info);
}
@override
void onTapUp(TapUpInfo info) {
final rocket = descendants().whereType<RocketSpriteComponent>().first;
final bounds = rocket.topLeftPosition & rocket.size;
if (bounds.contains(info.eventPosition.game.toOffset())) {
children.whereType<Plunger>().first.release();
} else {
_moveFlippersDown();
}
super.onTapUp(info);
}
@override
void onTapCancel() {
children.whereType<Plunger>().first.release();
_moveFlippersDown();
super.onTapCancel();
}
void _moveFlippersDown() {
if (focusedBoardSide != null) {
final flippers = descendants().whereType<Flipper>().where((flipper) {
return flipper.side == focusedBoardSide;
});
flippers.first.moveDown();
focusedBoardSide = null;
}
}
}
class _GameBallsController extends ComponentController<PinballGame>
@ -115,7 +173,7 @@ class _GameBallsController extends ComponentController<PinballGame>
}
}
class DebugPinballGame extends PinballGame with FPSCounter, TapDetector {
class DebugPinballGame extends PinballGame with FPSCounter {
DebugPinballGame({
required CharacterTheme characterTheme,
required PinballAudio audio,
@ -152,9 +210,11 @@ class DebugPinballGame extends PinballGame with FPSCounter, TapDetector {
@override
void onTapUp(TapUpInfo info) {
add(
ControlledBall.debug()..initialPosition = info.eventPosition.game,
);
super.onTapUp(info);
if (info.raw.kind == PointerDeviceKind.mouse) {
add(ControlledBall.debug()..initialPosition = info.eventPosition.game);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 61 KiB

@ -16,8 +16,6 @@ class $AssetsImagesGen {
$AssetsImagesBallGen get ball => const $AssetsImagesBallGen();
$AssetsImagesBaseboardGen get baseboard => const $AssetsImagesBaseboardGen();
$AssetsImagesBoundaryGen get boundary => const $AssetsImagesBoundaryGen();
$AssetsImagesChromeDinoGen get chromeDino =>
const $AssetsImagesChromeDinoGen();
$AssetsImagesDashGen get dash => const $AssetsImagesDashGen();
$AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen();
$AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen();
@ -97,18 +95,6 @@ class $AssetsImagesBoundaryGen {
const AssetGenImage('assets/images/boundary/outer.png');
}
class $AssetsImagesChromeDinoGen {
const $AssetsImagesChromeDinoGen();
/// File path: assets/images/chrome_dino/head.png
AssetGenImage get head =>
const AssetGenImage('assets/images/chrome_dino/head.png');
/// File path: assets/images/chrome_dino/mouth.png
AssetGenImage get mouth =>
const AssetGenImage('assets/images/chrome_dino/mouth.png');
}
class $AssetsImagesDashGen {
const $AssetsImagesDashGen();
@ -122,13 +108,16 @@ class $AssetsImagesDashGen {
class $AssetsImagesDinoGen {
const $AssetsImagesDinoGen();
/// File path: assets/images/dino/dino-land-bottom.png
AssetGenImage get dinoLandBottom =>
const AssetGenImage('assets/images/dino/dino-land-bottom.png');
$AssetsImagesDinoAnimatronicGen get animatronic =>
const $AssetsImagesDinoAnimatronicGen();
/// File path: assets/images/dino/bottom-wall.png
AssetGenImage get bottomWall =>
const AssetGenImage('assets/images/dino/bottom-wall.png');
/// File path: assets/images/dino/dino-land-top.png
AssetGenImage get dinoLandTop =>
const AssetGenImage('assets/images/dino/dino-land-top.png');
/// File path: assets/images/dino/top-wall.png
AssetGenImage get topWall =>
const AssetGenImage('assets/images/dino/top-wall.png');
}
class $AssetsImagesFlipperGen {
@ -304,12 +293,24 @@ class $AssetsImagesDashBumperGen {
const $AssetsImagesDashBumperMainGen();
}
class $AssetsImagesDinoAnimatronicGen {
const $AssetsImagesDinoAnimatronicGen();
/// File path: assets/images/dino/animatronic/head.png
AssetGenImage get head =>
const AssetGenImage('assets/images/dino/animatronic/head.png');
/// File path: assets/images/dino/animatronic/mouth.png
AssetGenImage get mouth =>
const AssetGenImage('assets/images/dino/animatronic/mouth.png');
}
class $AssetsImagesSpaceshipRailGen {
const $AssetsImagesSpaceshipRailGen();
/// File path: assets/images/spaceship/rail/foreground.png
AssetGenImage get foreground =>
const AssetGenImage('assets/images/spaceship/rail/foreground.png');
/// File path: assets/images/spaceship/rail/exit.png
AssetGenImage get exit =>
const AssetGenImage('assets/images/spaceship/rail/exit.png');
/// File path: assets/images/spaceship/rail/main.png
AssetGenImage get main =>

@ -67,7 +67,7 @@ class _BottomBoundarySpriteComponent extends SpriteComponent with HasGameRef {
_BottomBoundarySpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-5.4, 55.6),
position: Vector2(-5, 55.6),
);
@override

@ -1,31 +1,33 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart' hide Timer;
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template chrome_dino}
/// Dinosaur that gobbles up a [Ball], swivel his head around, and shoots it
/// back out.
/// Dino that swivels back and forth, opening its mouth to eat a [Ball].
///
/// Upon eating a [Ball], the dino rotates and spits the [Ball] out in a
/// different direction.
/// {@endtemplate}
class ChromeDino extends BodyComponent with InitialPosition {
/// {@macro chrome_dino}
ChromeDino()
: super(
// TODO(alestiago): Remove once sprites are defined.
paint: Paint()..color = Colors.blue,
priority: RenderPriority.dino,
renderBody: false,
);
/// The size of the dinosaur mouth.
static final size = Vector2(5, 2.5);
static final size = Vector2(5.5, 5);
/// Anchors the [ChromeDino] to the [RevoluteJoint] that controls its arc
/// motion.
Future<_ChromeDinoJoint> _anchorToJoint() async {
final anchor = _ChromeDinoAnchor();
// TODO(allisonryan0002): try moving to anchor after new body is defined.
final anchor = _ChromeDinoAnchor()
..initialPosition = initialPosition + Vector2(9, -4);
await add(anchor);
final jointDef = _ChromeDinoAnchorRevoluteJointDef(
@ -42,9 +44,11 @@ class ChromeDino extends BodyComponent with InitialPosition {
Future<void> onLoad() async {
await super.onLoad();
final joint = await _anchorToJoint();
const framesInAnimation = 98;
const animationFPS = 1 / 24;
await add(
TimerComponent(
period: 1,
period: (framesInAnimation / 2) * animationFPS,
onTick: joint._swivel,
repeat: true,
),
@ -54,44 +58,17 @@ class ChromeDino extends BodyComponent with InitialPosition {
List<FixtureDef> _createFixtureDefs() {
final fixtureDefs = <FixtureDef>[];
// TODO(alestiago): Subject to change when sprites are added.
final box = PolygonShape()..setAsBoxXY(size.x / 2, size.y / 2);
final fixtureDef = FixtureDef(
box,
density: 999,
friction: 0.3,
restitution: 0.1,
isSensor: true,
);
// TODO(allisonryan0002): Update this shape to better match sprite.
final box = PolygonShape()
..setAsBox(
size.x / 2,
size.y / 2,
initialPosition + Vector2(-4, 2),
-_ChromeDinoJoint._halfSweepingAngle,
);
final fixtureDef = FixtureDef(box, density: 1);
fixtureDefs.add(fixtureDef);
// FIXME(alestiago): Investigate why adding these fixtures is considered as
// an invalid contact type.
// final upperEdge = EdgeShape()
// ..set(
// Vector2(-size.x / 2, -size.y / 2),
// Vector2(size.x / 2, -size.y / 2),
// );
// final upperEdgeDef = FixtureDef(upperEdge)..density = 0.5;
// fixtureDefs.add(upperEdgeDef);
// final lowerEdge = EdgeShape()
// ..set(
// Vector2(-size.x / 2, size.y / 2),
// Vector2(size.x / 2, size.y / 2),
// );
// final lowerEdgeDef = FixtureDef(lowerEdge)..density = 0.5;
// fixtureDefs.add(lowerEdgeDef);
// final rightEdge = EdgeShape()
// ..set(
// Vector2(size.x / 2, -size.y / 2),
// Vector2(size.x / 2, size.y / 2),
// );
// final rightEdgeDef = FixtureDef(rightEdge)..density = 0.5;
// fixtureDefs.add(rightEdgeDef);
return fixtureDefs;
}
@ -110,13 +87,18 @@ class ChromeDino extends BodyComponent with InitialPosition {
}
}
/// {@template flipper_anchor}
/// [JointAnchor] positioned at the end of a [ChromeDino].
/// {@endtemplate}
class _ChromeDinoAnchor extends JointAnchor {
/// {@macro flipper_anchor}
_ChromeDinoAnchor() {
initialPosition = Vector2(ChromeDino.size.x / 2, 0);
_ChromeDinoAnchor();
// TODO(allisonryan0002): if these aren't moved when fixing the rendering, see
// if the joint can be created in onMount to resolve render syncing.
@override
Future<void> onLoad() async {
await super.onLoad();
await addAll([
_ChromeDinoMouthSprite(),
_ChromeDinoHeadSprite(),
]);
}
}
@ -135,22 +117,86 @@ class _ChromeDinoAnchorRevoluteJointDef extends RevoluteJointDef {
chromeDino.body.position + anchor.body.position,
);
enableLimit = true;
// TODO(alestiago): Apply design angle value.
const angle = math.pi / 3.5;
lowerAngle = -angle / 2;
upperAngle = angle / 2;
lowerAngle = -_ChromeDinoJoint._halfSweepingAngle;
upperAngle = _ChromeDinoJoint._halfSweepingAngle;
enableMotor = true;
// TODO(alestiago): Tune this values.
maxMotorTorque = motorSpeed = chromeDino.body.mass * 30;
maxMotorTorque = chromeDino.body.mass * 255;
motorSpeed = 2;
}
}
class _ChromeDinoJoint extends RevoluteJoint {
_ChromeDinoJoint(_ChromeDinoAnchorRevoluteJointDef def) : super(def);
static const _halfSweepingAngle = 0.1143;
/// Sweeps the [ChromeDino] up and down repeatedly.
void _swivel() {
setMotorSpeed(-motorSpeed);
}
}
class _ChromeDinoMouthSprite extends SpriteAnimationComponent with HasGameRef {
_ChromeDinoMouthSprite()
: super(
anchor: Anchor(Anchor.center.x + 0.47, Anchor.center.y - 0.29),
angle: _ChromeDinoJoint._halfSweepingAngle,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final image = gameRef.images.fromCache(
Assets.images.dino.animatronic.mouth.keyName,
);
const amountPerRow = 11;
const amountPerColumn = 9;
final textureSize = Vector2(
image.width / amountPerRow,
image.height / amountPerColumn,
);
size = textureSize / 10;
final data = SpriteAnimationData.sequenced(
amount: (amountPerColumn * amountPerRow) - 1,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
);
animation = SpriteAnimation.fromFrameData(image, data)..currentIndex = 45;
}
}
class _ChromeDinoHeadSprite extends SpriteAnimationComponent with HasGameRef {
_ChromeDinoHeadSprite()
: super(
anchor: Anchor(Anchor.center.x + 0.47, Anchor.center.y - 0.29),
angle: _ChromeDinoJoint._halfSweepingAngle,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final image = gameRef.images.fromCache(
Assets.images.dino.animatronic.head.keyName,
);
const amountPerRow = 11;
const amountPerColumn = 9;
final textureSize = Vector2(
image.width / amountPerRow,
image.height / amountPerColumn,
);
size = textureSize / 10;
final data = SpriteAnimationData.sequenced(
amount: (amountPerColumn * amountPerRow) - 1,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
);
animation = SpriteAnimation.fromFrameData(image, data)..currentIndex = 45;
}
}

@ -35,51 +35,46 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
List<FixtureDef> _createFixtureDefs() {
final topStraightShape = EdgeShape()
..set(
Vector2(28.65, -35.1),
Vector2(29.5, -35.1),
Vector2(28.65, -34.3),
Vector2(29.5, -34.3),
);
final topStraightFixtureDef = FixtureDef(topStraightShape);
final topCurveShape = BezierCurveShape(
controlPoints: [
topStraightShape.vertex1,
Vector2(18.8, -27),
Vector2(26.6, -21),
Vector2(18.8, -26.2),
Vector2(26.6, -20.2),
],
);
final topCurveFixtureDef = FixtureDef(topCurveShape);
final middleCurveShape = BezierCurveShape(
controlPoints: [
topCurveShape.vertices.last,
Vector2(27.8, -20.1),
Vector2(26.8, -19.5),
Vector2(27.8, -19.3),
Vector2(26.8, -18.7),
],
);
final middleCurveFixtureDef = FixtureDef(middleCurveShape);
final bottomCurveShape = BezierCurveShape(
controlPoints: [
middleCurveShape.vertices.last,
Vector2(23, -15),
Vector2(27, -15),
Vector2(23, -14.2),
Vector2(27, -14.2),
],
);
final bottomCurveFixtureDef = FixtureDef(bottomCurveShape);
final bottomStraightShape = EdgeShape()
..set(
bottomCurveShape.vertices.last,
Vector2(31, -14.5),
Vector2(31, -13.7),
);
final bottomStraightFixtureDef = FixtureDef(bottomStraightShape);
return [
topStraightFixtureDef,
topCurveFixtureDef,
middleCurveFixtureDef,
bottomCurveFixtureDef,
bottomStraightFixtureDef,
FixtureDef(topStraightShape),
FixtureDef(topCurveShape),
FixtureDef(middleCurveShape),
FixtureDef(bottomCurveShape),
FixtureDef(bottomStraightShape),
];
}
@ -109,12 +104,12 @@ class _DinoTopWallSpriteComponent extends SpriteComponent with HasGameRef {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.dino.dinoLandTop.keyName,
Assets.images.dino.topWall.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
position = Vector2(22.8, -38.9);
position = Vector2(22.8, -38.1);
}
}
@ -131,17 +126,11 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
);
List<FixtureDef> _createFixtureDefs() {
const restitution = 1.0;
final topStraightShape = EdgeShape()
..set(
Vector2(32.4, -8.8),
Vector2(25, -7.7),
);
final topStraightFixtureDef = FixtureDef(
topStraightShape,
restitution: restitution,
);
final topLeftCurveShape = BezierCurveShape(
controlPoints: [
@ -150,36 +139,24 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
Vector2(29.8, 13.8),
],
);
final topLeftCurveFixtureDef = FixtureDef(
topLeftCurveShape,
restitution: restitution,
);
final bottomLeftStraightShape = EdgeShape()
..set(
topLeftCurveShape.vertices.last,
Vector2(31.9, 44.1),
);
final bottomLeftStraightFixtureDef = FixtureDef(
bottomLeftStraightShape,
restitution: restitution,
);
final bottomStraightShape = EdgeShape()
..set(
bottomLeftStraightShape.vertex2,
Vector2(37.8, 44.1),
);
final bottomStraightFixtureDef = FixtureDef(
bottomStraightShape,
restitution: restitution,
);
return [
topStraightFixtureDef,
topLeftCurveFixtureDef,
bottomLeftStraightFixtureDef,
bottomStraightFixtureDef,
FixtureDef(topStraightShape),
FixtureDef(topLeftCurveShape),
FixtureDef(bottomLeftStraightShape),
FixtureDef(bottomStraightShape),
];
}
@ -203,11 +180,11 @@ class _DinoBottomWallSpriteComponent extends SpriteComponent with HasGameRef {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.dino.dinoLandBottom.keyName,
Assets.images.dino.bottomWall.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
position = Vector2(23.6, -9.5);
position = Vector2(23.8, -9.5);
}
}

@ -24,7 +24,7 @@ abstract class RenderPriority {
static const int ballOnSpaceship = _above + spaceshipSaucer;
/// Render priority for the [Ball] while it's on the [SpaceshipRail].
static const int ballOnSpaceshipRail = _below + spaceshipSaucer;
static const int ballOnSpaceshipRail = _above + spaceshipRail;
/// Render priority for the [Ball] while it's on the [LaunchRamp].
static const int ballOnLaunchRamp = _above + launchRamp;
@ -87,9 +87,9 @@ abstract class RenderPriority {
static const int spaceshipRail = _above + bottomGroup;
static const int spaceshipRailForeground = _above + spaceshipRail;
static const int spaceshipRailExit = _above + ballOnSpaceshipRail;
static const int spaceshipSaucer = _above + spaceshipRail;
static const int spaceshipSaucer = _above + ballOnSpaceshipRail;
static const int spaceshipSaucerWall = _above + spaceshipSaucer;

@ -2,88 +2,71 @@ import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/gen/assets.gen.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template spaceship_rail}
/// A [Blueprint] for the spaceship drop tube.
/// A [Blueprint] for the rail exiting the [Spaceship].
/// {@endtemplate}
class SpaceshipRail extends Blueprint {
/// {@macro spaceship_rail}
SpaceshipRail()
: super(
components: [
_SpaceshipRailRamp(),
_SpaceshipRail(),
_SpaceshipRailExit(),
_SpaceshipRailBase(radius: 0.55)
..initialPosition = Vector2(-26.15, -18.65),
_SpaceshipRailBase(radius: 0.8)
..initialPosition = Vector2(-25.5, 12.9),
_SpaceshipRailForeground()
_SpaceshipRailExitSpriteComponent()
],
);
}
class _SpaceshipRailRamp extends BodyComponent with Layered {
_SpaceshipRailRamp()
class _SpaceshipRail extends BodyComponent with Layered {
_SpaceshipRail()
: super(
priority: RenderPriority.spaceshipRail,
children: [_SpaceshipRailSpriteComponent()],
renderBody: false,
children: [_SpaceshipRailRampSpriteComponent()],
) {
layer = Layer.spaceshipExitRail;
}
List<FixtureDef> _createFixtureDefs() {
final fixturesDefs = <FixtureDef>[];
final topArcShape = ArcShape(
center: Vector2(-35.5, -30.9),
center: Vector2(-35.1, -30.9),
arcRadius: 2.5,
angle: math.pi,
rotation: 0.2,
);
final topArcFixtureDef = FixtureDef(topArcShape);
fixturesDefs.add(topArcFixtureDef);
final topLeftCurveShape = BezierCurveShape(
controlPoints: [
Vector2(-37.9, -30.4),
Vector2(-38, -23.9),
Vector2(-37.6, -30.4),
Vector2(-37.8, -23.9),
Vector2(-30.93, -18.2),
],
);
final topLeftCurveFixtureDef = FixtureDef(topLeftCurveShape);
fixturesDefs.add(topLeftCurveFixtureDef);
final middleLeftCurveShape = BezierCurveShape(
controlPoints: [
topLeftCurveShape.vertices.last,
Vector2(-22.6, -10.3),
Vector2(-30, -0.2),
Vector2(-29.5, -0.2),
],
);
final middleLeftCurveFixtureDef = FixtureDef(middleLeftCurveShape);
fixturesDefs.add(middleLeftCurveFixtureDef);
final bottomLeftCurveShape = BezierCurveShape(
controlPoints: [
middleLeftCurveShape.vertices.last,
Vector2(-36, 8.6),
Vector2(-32.04, 18.3),
Vector2(-35.6, 8.6),
Vector2(-31.3, 18.3),
],
);
final bottomLeftCurveFixtureDef = FixtureDef(bottomLeftCurveShape);
fixturesDefs.add(bottomLeftCurveFixtureDef);
final topRightStraightShape = EdgeShape()
..set(
Vector2(-33, -31.3),
Vector2(-27.2, -21.3),
Vector2(-33, -31.3),
);
final topRightStraightFixtureDef = FixtureDef(topRightStraightShape);
fixturesDefs.add(topRightStraightFixtureDef);
final middleRightCurveShape = BezierCurveShape(
controlPoints: [
@ -92,8 +75,6 @@ class _SpaceshipRailRamp extends BodyComponent with Layered {
Vector2(-25.29, 1.7),
],
);
final middleRightCurveFixtureDef = FixtureDef(middleRightCurveShape);
fixturesDefs.add(middleRightCurveFixtureDef);
final bottomRightCurveShape = BezierCurveShape(
controlPoints: [
@ -102,10 +83,16 @@ class _SpaceshipRailRamp extends BodyComponent with Layered {
Vector2(-26.8, 15.7),
],
);
final bottomRightCurveFixtureDef = FixtureDef(bottomRightCurveShape);
fixturesDefs.add(bottomRightCurveFixtureDef);
return fixturesDefs;
return [
FixtureDef(topArcShape),
FixtureDef(topLeftCurveShape),
FixtureDef(middleLeftCurveShape),
FixtureDef(bottomLeftCurveShape),
FixtureDef(topRightStraightShape),
FixtureDef(middleRightCurveShape),
FixtureDef(bottomRightCurveShape),
];
}
@override
@ -116,55 +103,47 @@ class _SpaceshipRailRamp extends BodyComponent with Layered {
}
}
class _SpaceshipRailRampSpriteComponent extends SpriteComponent
with HasGameRef {
class _SpaceshipRailSpriteComponent extends SpriteComponent with HasGameRef {
_SpaceshipRailSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-29.4, -5.7),
);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.spaceship.rail.main.keyName,
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.spaceship.rail.main.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(-29.4, -5.7);
}
}
class _SpaceshipRailForeground extends SpriteComponent with HasGameRef {
_SpaceshipRailForeground()
: super(priority: RenderPriority.spaceshipRailForeground);
class _SpaceshipRailExitSpriteComponent extends SpriteComponent
with HasGameRef {
_SpaceshipRailExitSpriteComponent()
: super(
anchor: Anchor.center,
position: Vector2(-28, 19.4),
priority: RenderPriority.spaceshipRailExit,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.spaceship.rail.foreground.keyName,
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.spaceship.rail.exit.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.center;
position = Vector2(-28.5, 19.7);
}
}
/// Represents the ground bases of the [_SpaceshipRailRamp].
class _SpaceshipRailBase extends BodyComponent with InitialPosition {
_SpaceshipRailBase({required this.radius}) : super(renderBody: false);
final double radius;
@override
Body createBody() {
final shape = CircleShape()..radius = radius;
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef(
position: initialPosition,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}

@ -50,6 +50,7 @@ flutter:
- assets/images/baseboard/
- assets/images/boundary/
- assets/images/dino/
- assets/images/dino/animatronic/
- assets/images/flipper/
- assets/images/launch_ramp/
- assets/images/dash/
@ -60,7 +61,6 @@ flutter:
- assets/images/spaceship/rail/
- assets/images/spaceship/ramp/
- assets/images/spaceship/ramp/arrow/
- assets/images/chrome_dino/
- assets/images/kicker/
- assets/images/plunger/
- assets/images/slingshot/

@ -12,6 +12,10 @@ class SpaceshipRailGame extends BallGame {
color: Colors.blue,
ballPriority: RenderPriority.ballOnSpaceshipRail,
ballLayer: Layer.spaceshipExitRail,
imagesFileNames: [
Assets.images.spaceship.rail.main.keyName,
Assets.images.spaceship.rail.exit.keyName,
],
);
static const description = '''

@ -1,8 +1,22 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class ChromeDinoGame extends Forge2DGame {
static const description = 'Shows how a ChromeDino is rendered.';
class ChromeDinoGame extends BallGame {
ChromeDinoGame()
: super(
imagesFileNames: [
Assets.images.dino.animatronic.mouth.keyName,
Assets.images.dino.animatronic.head.keyName,
],
);
static const description = '''
Shows how ChromeDino is rendered.
- Activate the "trace" parameter to overlay the body.
- Tap anywhere on the screen to spawn a ball into the game.
''';
@override
Future<void> onLoad() async {
@ -10,5 +24,7 @@ class ChromeDinoGame extends Forge2DGame {
camera.followVector2(Vector2.zero());
await add(ChromeDino());
await traceAllBodies();
}
}

@ -4,7 +4,7 @@ import 'package:sandbox/stories/chrome_dino/chrome_dino_game.dart';
void addChromeDinoStories(Dashbook dashbook) {
dashbook.storiesOf('Chrome Dino').addGame(
title: 'Trace',
title: 'Traced',
description: ChromeDinoGame.description,
gameBuilder: (_) => ChromeDinoGame(),
);

@ -20,8 +20,8 @@ class DinoWallGame extends BallGame {
await super.onLoad();
await images.loadAll([
Assets.images.dino.dinoLandTop.keyName,
Assets.images.dino.dinoLandBottom.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.bottomWall.keyName,
]);
await addFromBlueprint(DinoWalls());

@ -1,13 +1,19 @@
// ignore_for_file: cascade_invocations
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(Forge2DGame.new);
final assets = [
Assets.images.dino.animatronic.mouth.keyName,
Assets.images.dino.animatronic.head.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
group('ChromeDino', () {
flameTester.test(
@ -20,19 +26,84 @@ void main() {
},
);
flameTester.test(
'swivels',
(game) async {
// TODO(alestiago): Write golden tests to check the
// swivel animation.
final chromeDino = ChromeDino();
await game.ensureAdd(chromeDino);
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.ensureAdd(ChromeDino());
game.camera.followVector2(Vector2.zero());
await tester.pump();
},
verify: (game, tester) async {
final sweepAnimationDuration = game
.descendants()
.whereType<SpriteAnimationComponent>()
.first
.animation!
.totalDuration() /
2;
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/chrome_dino/up.png'),
);
final previousPosition = chromeDino.body.position.clone();
game.update(64);
game.update(sweepAnimationDuration * 0.25);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/chrome_dino/middle.png'),
);
expect(chromeDino.body.position, isNot(equals(previousPosition)));
game.update(sweepAnimationDuration * 0.25);
await tester.pump();
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/chrome_dino/down.png'),
);
},
);
group('swivels', () {
flameTester.test(
'up',
(game) async {
final chromeDino = ChromeDino();
await game.ensureAdd(chromeDino);
game.camera.followVector2(Vector2.zero());
final sweepAnimationDuration = game
.descendants()
.whereType<SpriteAnimationComponent>()
.first
.animation!
.totalDuration() /
2;
game.update(sweepAnimationDuration * 1.5);
expect(chromeDino.body.angularVelocity, isPositive);
},
);
flameTester.test(
'down',
(game) async {
final chromeDino = ChromeDino();
await game.ensureAdd(chromeDino);
game.camera.followVector2(Vector2.zero());
final sweepAnimationDuration = game
.descendants()
.whereType<SpriteAnimationComponent>()
.first
.animation!
.totalDuration() /
2;
game.update(sweepAnimationDuration * 0.5);
expect(chromeDino.body.angularVelocity, isNegative);
},
);
});
});
}

@ -45,6 +45,7 @@ void main() {
);
},
);
flameTester.test(
'loads correctly',
(game) async {

@ -12,8 +12,8 @@ void main() {
group('DinoWalls', () {
TestWidgetsFlutterBinding.ensureInitialized();
final assets = [
Assets.images.dino.dinoLandTop.keyName,
Assets.images.dino.dinoLandBottom.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.bottomWall.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 92 KiB

@ -11,13 +11,19 @@ import '../../helpers/helpers.dart';
void main() {
group('SpaceshipRail', () {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
final assets = [
Assets.images.spaceship.rail.main.keyName,
Assets.images.spaceship.rail.exit.keyName,
];
final flameTester = FlameTester(() => TestGame(assets));
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.images.loadAll(assets);
await game.addFromBlueprint(SpaceshipRail());
await game.ready();
await tester.pump();
game.camera.followVector2(Vector2.zero());
game.camera.zoom = 8;

@ -21,6 +21,8 @@ void main() {
Assets.images.spaceship.ramp.arrow.active3.keyName,
Assets.images.spaceship.ramp.arrow.active4.keyName,
Assets.images.spaceship.ramp.arrow.active5.keyName,
Assets.images.spaceship.rail.main.keyName,
Assets.images.spaceship.rail.exit.keyName,
Assets.images.androidBumper.a.lit.keyName,
Assets.images.androidBumper.a.dimmed.keyName,
Assets.images.androidBumper.b.lit.keyName,

@ -105,18 +105,6 @@ void main() {
expect(flutterForest.length, equals(1));
},
);
flameTester.test(
'one ChromeDino',
(game) async {
final board = Board();
await game.ready();
await game.ensureAdd(board);
final chromeDino = board.descendants().whereType<ChromeDino>();
expect(chromeDino.length, equals(1));
},
);
});
});
}

@ -3,6 +3,7 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pinball/game/game.dart';
@ -27,10 +28,10 @@ void main() {
Assets.images.boundary.bottom.keyName,
Assets.images.boundary.outer.keyName,
Assets.images.boundary.outerBottom.keyName,
Assets.images.chromeDino.mouth.keyName,
Assets.images.chromeDino.head.keyName,
Assets.images.dino.dinoLandTop.keyName,
Assets.images.dino.dinoLandBottom.keyName,
Assets.images.dino.animatronic.mouth.keyName,
Assets.images.dino.animatronic.head.keyName,
Assets.images.dino.topWall.keyName,
Assets.images.dino.bottomWall.keyName,
Assets.images.dash.animatronic.keyName,
Assets.images.dash.bumper.a.active.keyName,
Assets.images.dash.bumper.a.inactive.keyName,
@ -72,7 +73,7 @@ void main() {
Assets.images.spaceship.ramp.arrow.active4.keyName,
Assets.images.spaceship.ramp.arrow.active5.keyName,
Assets.images.spaceship.rail.main.keyName,
Assets.images.spaceship.rail.foreground.keyName,
Assets.images.spaceship.rail.exit.keyName,
Assets.images.sparky.bumper.a.active.keyName,
Assets.images.sparky.bumper.a.inactive.keyName,
Assets.images.sparky.bumper.b.active.keyName,
@ -229,6 +230,181 @@ void main() {
);
});
});
group('flipper control', () {
flameTester.test('tap down moves left flipper up', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.zero());
when(() => eventPosition.widget).thenReturn(Vector2.zero());
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.left,
);
game.onTapDown(tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative);
});
flameTester.test('tap down moves right flipper up', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.zero());
when(() => eventPosition.widget).thenReturn(game.canvasSize);
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.right,
);
game.onTapDown(tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative);
});
flameTester.test('tap up moves flipper down', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.zero());
when(() => eventPosition.widget).thenReturn(Vector2.zero());
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.left,
);
game.onTapDown(tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative);
final tapUpEvent = MockTapUpInfo();
when(() => tapUpEvent.eventPosition).thenReturn(eventPosition);
game.onTapUp(tapUpEvent);
await game.ready();
expect(flippers.first.body.linearVelocity.y, isPositive);
});
flameTester.test('tap cancel moves flipper down', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.zero());
when(() => eventPosition.widget).thenReturn(Vector2.zero());
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final flippers = game.descendants().whereType<Flipper>().where(
(flipper) => flipper.side == BoardSide.left,
);
game.onTapDown(tapDownEvent);
expect(flippers.first.body.linearVelocity.y, isNegative);
game.onTapCancel();
expect(flippers.first.body.linearVelocity.y, isPositive);
});
});
group('plunger control', () {
flameTester.test('tap down moves plunger down', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2(40, 60));
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final plunger = game.descendants().whereType<Plunger>().first;
game.onTapDown(tapDownEvent);
expect(plunger.body.linearVelocity.y, equals(7));
});
flameTester.test('tap up releases plunger', (game) async {
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2(40, 60));
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final plunger = game.descendants().whereType<Plunger>().first;
game.onTapDown(tapDownEvent);
expect(plunger.body.linearVelocity.y, equals(7));
final tapUpEvent = MockTapUpInfo();
when(() => tapUpEvent.eventPosition).thenReturn(eventPosition);
game.onTapUp(tapUpEvent);
expect(plunger.body.linearVelocity.y, equals(0));
});
flameTester.test('tap cancel releases plunger', (game) async {
await game.ready();
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2(40, 60));
final raw = MockTapDownDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.touch);
final tapDownEvent = MockTapDownInfo();
when(() => tapDownEvent.eventPosition).thenReturn(eventPosition);
when(() => tapDownEvent.raw).thenReturn(raw);
final plunger = game.descendants().whereType<Plunger>().first;
game.onTapDown(tapDownEvent);
expect(plunger.body.linearVelocity.y, equals(7));
game.onTapCancel();
expect(plunger.body.linearVelocity.y, equals(0));
});
});
});
group('DebugPinballGame', () {
@ -238,8 +414,12 @@ void main() {
final eventPosition = MockEventPosition();
when(() => eventPosition.game).thenReturn(Vector2.all(10));
final raw = MockTapUpDetails();
when(() => raw.kind).thenReturn(PointerDeviceKind.mouse);
final tapUpEvent = MockTapUpInfo();
when(() => tapUpEvent.eventPosition).thenReturn(eventPosition);
when(() => tapUpEvent.raw).thenReturn(raw);
final previousBalls =
game.descendants().whereType<ControlledBall>().toList();

@ -2,7 +2,7 @@ import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:mocktail/mocktail.dart';
@ -55,8 +55,14 @@ class MockRawKeyUpEvent extends Mock implements RawKeyUpEvent {
}
}
class MockTapDownInfo extends Mock implements TapDownInfo {}
class MockTapDownDetails extends Mock implements TapDownDetails {}
class MockTapUpInfo extends Mock implements TapUpInfo {}
class MockTapUpDetails extends Mock implements TapUpDetails {}
class MockEventPosition extends Mock implements EventPosition {}
class MockFilter extends Mock implements Filter {}

Loading…
Cancel
Save