Merge branch 'main' into ci/included-concurrency-tags

pull/241/head
Alejandro Santiago 3 years ago committed by GitHub
commit 117e1e8f27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,18 @@
name: share_repository
on:
push:
paths:
- "packages/share_repository/**"
- ".github/workflows/share_repository.yaml"
pull_request:
paths:
- "packages/share_repository/**"
- ".github/workflows/share_repository.yaml"
jobs:
build:
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/flutter_package.yml@v1
with:
working_directory: packages/share_repository

Before

Width:  |  Height:  |  Size: 306 KiB

After

Width:  |  Height:  |  Size: 306 KiB

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -13,7 +13,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_audio/pinball_audio.dart';
class App extends StatelessWidget {
@ -36,7 +36,7 @@ class App extends StatelessWidget {
RepositoryProvider.value(value: _pinballAudio),
],
child: BlocProvider(
create: (context) => ThemeCubit(),
create: (context) => CharacterThemeCubit(),
child: const MaterialApp(
title: 'I/O Pinball',
localizationsDelegates: [

@ -12,6 +12,12 @@ enum GameBonus {
/// Bonus achieved when a ball enters Sparky's computer.
sparkyTurboCharge,
/// Bonus achieved when the ball goes in the dino mouth.
dinoChomp,
/// Bonus achieved when a ball enters the android spaceship.
androidSpaceship,
}
/// {@template game_state}

@ -4,7 +4,6 @@ export 'camera_controller.dart';
export 'controlled_ball.dart';
export 'controlled_flipper.dart';
export 'controlled_plunger.dart';
export 'controlled_sparky_computer.dart';
export 'flutter_forest.dart';
export 'game_flow_controller.dart';
export 'google_word.dart';

@ -15,8 +15,8 @@ class ControlledBall extends Ball with Controls<BallController> {
/// When a launched [Ball] is lost, it will decrease the [GameState.balls]
/// count, and a new [Ball] is spawned.
ControlledBall.launch({
required PinballTheme theme,
}) : super(baseColor: theme.characterTheme.ballColor) {
required CharacterTheme characterTheme,
}) : super(baseColor: characterTheme.ballColor) {
controller = BallController(this);
priority = RenderPriority.ballOnLaunchRamp;
layer = Layer.launcher;
@ -28,8 +28,8 @@ class ControlledBall extends Ball with Controls<BallController> {
/// When a bonus [Ball] is lost, the [GameState.balls] doesn't change.
/// {@endtemplate}
ControlledBall.bonus({
required PinballTheme theme,
}) : super(baseColor: theme.characterTheme.ballColor) {
required CharacterTheme characterTheme,
}) : super(baseColor: characterTheme.ballColor) {
controller = BallController(this);
priority = RenderPriority.ballOnBoard;
}
@ -62,10 +62,13 @@ class BallController extends ComponentController<Ball>
Future<void> turboCharge() async {
gameRef.read<GameBloc>().add(const SparkyTurboChargeActivated());
// TODO(allisonryan0002): adjust delay to match animation duration once
// given animations.
component.stop();
await Future<void>.delayed(const Duration(seconds: 1));
// TODO(alestiago): Refactor this hard coded duration once the following is
// merged:
// https://github.com/flame-engine/flame/pull/1564
await Future<void>.delayed(
const Duration(milliseconds: 2583),
);
component.resume();
await component.boost(Vector2(40, 110));
}

@ -1,52 +0,0 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template controlled_sparky_computer}
/// [SparkyComputer] with a [SparkyComputerController] attached.
/// {@endtemplate}
class ControlledSparkyComputer extends SparkyComputer
with Controls<SparkyComputerController>, HasGameRef<Forge2DGame> {
/// {@macro controlled_sparky_computer}
ControlledSparkyComputer() : super() {
controller = SparkyComputerController(this);
}
@override
Future<void> onLoad() async {
await super.onLoad();
gameRef.addContactCallback(SparkyComputerSensorBallContactCallback());
}
}
/// {@template sparky_computer_controller}
/// Controller attached to a [SparkyComputer] that handles its game related
/// logic.
/// {@endtemplate}
// TODO(allisonryan0002): listen for turbo charge game bonus and animate Sparky.
class SparkyComputerController
extends ComponentController<ControlledSparkyComputer> {
/// {@macro sparky_computer_controller}
SparkyComputerController(ControlledSparkyComputer controlledComputer)
: super(controlledComputer);
}
/// {@template sparky_computer_sensor_ball_contact_callback}
/// Turbo charges the [Ball] when it enters the [SparkyComputer]
/// {@endtemplate}
@visibleForTesting
class SparkyComputerSensorBallContactCallback
extends ContactCallback<SparkyComputerSensor, ControlledBall> {
/// {@macro sparky_computer_sensor_ball_contact_callback}
SparkyComputerSensorBallContactCallback();
@override
void begin(_, ControlledBall ball, __) {
ball.controller.turboCharge();
}
}

@ -71,7 +71,7 @@ class _FlutterForestController extends ComponentController<FlutterForest>
Future<void> _addBonusBall() async {
await gameRef.add(
ControlledBall.bonus(theme: gameRef.theme)
ControlledBall.bonus(characterTheme: gameRef.characterTheme)
..initialPosition = Vector2(17.2, -52.7),
);
}

@ -32,8 +32,7 @@ class GameFlowController extends ComponentController<PinballGame>
// next page
component.firstChild<Backboard>()?.gameOverMode(
score: state?.score ?? 0,
characterIconPath:
component.theme.characterTheme.leaderboardIcon.keyName,
characterIconPath: component.characterTheme.leaderboardIcon.keyName,
);
component.firstChild<CameraController>()?.focusOnBackboard();
}

@ -1,10 +1,10 @@
// ignore_for_file: avoid_renaming_method_parameters
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
/// {@template sparky_fire_zone}
/// Area positioned at the top left of the [Board] where the [Ball]
@ -12,29 +12,21 @@ import 'package:pinball_components/pinball_components.dart';
///
/// When a [Ball] hits [SparkyBumper]s, the bumper animates.
/// {@endtemplate}
class SparkyFireZone extends Component with HasGameRef<PinballGame> {
class SparkyFireZone extends Blueprint {
/// {@macro sparky_fire_zone}
SparkyFireZone();
@override
Future<void> onLoad() async {
await super.onLoad();
gameRef.addContactCallback(SparkyBumperBallContactCallback());
final lowerLeftBumper = _SparkyBumper.a()
..initialPosition = Vector2(-22.9, -41.65);
final upperLeftBumper = _SparkyBumper.b()
..initialPosition = Vector2(-21.25, -57.9);
final rightBumper = _SparkyBumper.c()
..initialPosition = Vector2(-3.3, -52.55);
await addAll([
lowerLeftBumper,
upperLeftBumper,
rightBumper,
]);
}
SparkyFireZone()
: super(
components: [
_SparkyBumper.a()..initialPosition = Vector2(-22.9, -41.65),
_SparkyBumper.b()..initialPosition = Vector2(-21.25, -57.9),
_SparkyBumper.c()..initialPosition = Vector2(-3.3, -52.55),
SparkyComputerSensor()..initialPosition = Vector2(-13, -49.8),
SparkyAnimatronic()..position = Vector2(-13.8, -58.2),
],
blueprints: [
SparkyComputer(),
],
);
}
// TODO(alestiago): Revisit ScorePoints logic once the FlameForge2D
@ -48,6 +40,14 @@ class _SparkyBumper extends SparkyBumper with ScorePoints {
@override
int get points => 20;
@override
Future<void> onLoad() async {
await super.onLoad();
// TODO(alestiago): Revisit once this has been merged:
// https://github.com/flame-engine/flame/pull/1547
gameRef.addContactCallback(SparkyBumperBallContactCallback());
}
}
/// Listens when a [Ball] bounces bounces against a [SparkyBumper].
@ -63,3 +63,48 @@ class SparkyBumperBallContactCallback
sparkyBumper.animate();
}
}
/// {@template sparky_computer_sensor}
/// Small sensor body used to detect when a ball has entered the
/// [SparkyComputer].
/// {@endtemplate}
// TODO(alestiago): Revisit once this has been merged:
// https://github.com/flame-engine/flame/pull/1547
class SparkyComputerSensor extends BodyComponent with InitialPosition {
/// {@macro sparky_computer_sensor}
SparkyComputerSensor() {
renderBody = false;
}
@override
Body createBody() {
final shape = CircleShape()..radius = 0.1;
final fixtureDef = FixtureDef(shape, isSensor: true);
final bodyDef = BodyDef(
position: initialPosition,
userData: this,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
@override
Future<void> onLoad() async {
await super.onLoad();
// TODO(alestiago): Revisit once this has been merged:
// https://github.com/flame-engine/flame/pull/1547
gameRef.addContactCallback(SparkyComputerSensorBallContactCallback());
}
}
@visibleForTesting
// TODO(alestiago): Revisit once this has been merged:
// https://github.com/flame-engine/flame/pull/1547
// ignore: public_member_api_docs
class SparkyComputerSensorBallContactCallback
extends ContactCallback<SparkyComputerSensor, ControlledBall> {
@override
void begin(_, ControlledBall controlledBall, __) {
controlledBall.controller.turboCharge();
controlledBall.gameRef.firstChild<SparkyAnimatronic>()?.playing = true;
}
}

@ -43,8 +43,11 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.dash.bumper.b.inactive.keyName),
images.load(components.Assets.images.dash.bumper.main.active.keyName),
images.load(components.Assets.images.dash.bumper.main.inactive.keyName),
images.load(components.Assets.images.plunger.plunger.keyName),
images.load(components.Assets.images.plunger.rocket.keyName),
images.load(components.Assets.images.boundary.bottom.keyName),
images.load(components.Assets.images.boundary.outer.keyName),
images.load(components.Assets.images.boundary.outerBottom.keyName),
images.load(components.Assets.images.spaceship.saucer.keyName),
images.load(components.Assets.images.spaceship.bridge.keyName),
images.load(components.Assets.images.spaceship.ramp.boardOpening.keyName),
@ -80,12 +83,11 @@ extension PinballGameAssetsX on PinballGame {
images.load(components.Assets.images.alienBumper.b.inactive.keyName),
images.load(components.Assets.images.chromeDino.mouth.keyName),
images.load(components.Assets.images.chromeDino.head.keyName),
images.load(components.Assets.images.plunger.plunger.keyName),
images.load(components.Assets.images.plunger.rocket.keyName),
images.load(components.Assets.images.sparky.computer.base.keyName),
images.load(components.Assets.images.sparky.computer.top.keyName),
images.load(components.Assets.images.sparky.bumper.a.active.keyName),
images.load(components.Assets.images.sparky.computer.base.keyName),
images.load(components.Assets.images.sparky.animatronic.keyName),
images.load(components.Assets.images.sparky.bumper.a.inactive.keyName),
images.load(components.Assets.images.sparky.bumper.a.active.keyName),
images.load(components.Assets.images.sparky.bumper.b.active.keyName),
images.load(components.Assets.images.sparky.bumper.b.inactive.keyName),
images.load(components.Assets.images.sparky.bumper.c.active.keyName),

@ -2,6 +2,7 @@
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
@ -19,7 +20,7 @@ class PinballGame extends Forge2DGame
HasKeyboardHandlerComponents,
Controls<_GameBallsController> {
PinballGame({
required this.theme,
required this.characterTheme,
required this.audio,
}) {
images.prefix = '';
@ -32,7 +33,7 @@ class PinballGame extends Forge2DGame
@override
Color backgroundColor() => Colors.transparent;
final PinballTheme theme;
final CharacterTheme characterTheme;
final PinballAudio audio;
@ -49,13 +50,13 @@ class PinballGame extends Forge2DGame
// TODO(allisonryan0002): banish Wall and Board classes in later PR.
await add(BottomWall());
unawaited(addFromBlueprint(Boundaries()));
unawaited(addFromBlueprint(ControlledSparkyComputer()));
unawaited(addFromBlueprint(LaunchRamp()));
final launcher = Launcher();
unawaited(addFromBlueprint(launcher));
unawaited(add(Board()));
unawaited(add(AlienZone()));
unawaited(add(SparkyFireZone()));
await addFromBlueprint(SparkyFireZone());
unawaited(addFromBlueprint(Slingshots()));
unawaited(addFromBlueprint(DinoWalls()));
unawaited(_addBonusWord());
@ -118,7 +119,7 @@ class _GameBallsController extends ComponentController<PinballGame>
void _spawnBall() {
final ball = ControlledBall.launch(
theme: gameRef.theme,
characterTheme: gameRef.characterTheme,
)..initialPosition = Vector2(
_plunger.body.position.x,
_plunger.body.position.y - Ball.size.y,
@ -134,12 +135,12 @@ class _GameBallsController extends ComponentController<PinballGame>
}
}
class DebugPinballGame extends PinballGame with TapDetector {
class DebugPinballGame extends PinballGame with FPSCounter, TapDetector {
DebugPinballGame({
required PinballTheme theme,
required CharacterTheme characterTheme,
required PinballAudio audio,
}) : super(
theme: theme,
characterTheme: characterTheme,
audio: audio,
) {
controller = _DebugGameBallsController(this);
@ -149,6 +150,7 @@ class DebugPinballGame extends PinballGame with TapDetector {
Future<void> onLoad() async {
await super.onLoad();
await _loadBackground();
await add(_DebugInformation());
}
// TODO(alestiago): Move to PinballGame once we have the real background
@ -191,3 +193,35 @@ class _DebugGameBallsController extends _GameBallsController {
return noBallsLeft && canBallRespawn;
}
}
class _DebugInformation extends Component with HasGameRef<DebugPinballGame> {
_DebugInformation() : super(priority: RenderPriority.debugInfo);
@override
PositionType get positionType => PositionType.widget;
final _debugTextPaint = TextPaint(
style: const TextStyle(
color: Colors.green,
fontSize: 10,
),
);
final _debugBackgroundPaint = Paint()..color = Colors.white;
@override
void render(Canvas canvas) {
final debugText = [
'FPS: ${gameRef.fps().toStringAsFixed(1)}',
'BALLS: ${gameRef.descendants().whereType<ControlledBall>().length}',
].join(' | ');
final height = _debugTextPaint.measureTextHeight(debugText);
final position = Vector2(0, gameRef.camera.canvasSize.y - height);
canvas.drawRect(
position & Vector2(gameRef.camera.canvasSize.x, height),
_debugBackgroundPaint,
);
_debugTextPaint.render(canvas, debugText, position);
}
}

@ -5,8 +5,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_audio/pinball_audio.dart';
class PinballGamePage extends StatelessWidget {
@ -31,17 +31,19 @@ class PinballGamePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.read<ThemeCubit>().state.theme;
final characterTheme =
context.read<CharacterThemeCubit>().state.characterTheme;
final audio = context.read<PinballAudio>();
final pinballAudio = context.read<PinballAudio>();
final game = isDebugMode
? DebugPinballGame(theme: theme, audio: audio)
: PinballGame(theme: theme, audio: audio);
? DebugPinballGame(characterTheme: characterTheme, audio: audio)
: PinballGame(characterTheme: characterTheme, audio: audio);
final loadables = [
...game.preLoadAssets(),
pinballAudio.load(),
...BonusAnimation.loadAssets(),
];
return MultiBlocProvider(
@ -112,6 +114,13 @@ class PinballGameLoadedView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isPlaying = context.select(
(StartGameBloc bloc) => bloc.state.status == StartGameStatus.play,
);
final gameWidgetWidth = MediaQuery.of(context).size.height * 9 / 16;
final screenWidth = MediaQuery.of(context).size.width;
final leftMargin = (screenWidth / 2) - (gameWidgetWidth / 1.8);
return Stack(
children: [
Positioned.fill(
@ -130,10 +139,13 @@ class PinballGameLoadedView extends StatelessWidget {
},
),
),
const Positioned(
top: 8,
left: 8,
child: GameHud(),
Positioned(
top: 16,
left: leftMargin,
child: Visibility(
visible: isPlaying,
child: const GameHud(),
),
),
],
);

@ -1,19 +1,23 @@
// ignore_for_file: public_member_api_docs
import 'package:flame/flame.dart';
import 'package:flame/sprite.dart';
import 'package:flame/widgets.dart';
import 'package:flutter/material.dart' hide Image;
import 'package:pinball/gen/assets.gen.dart';
import 'package:pinball_flame/pinball_flame.dart';
class BonusAnimation extends StatelessWidget {
/// {@template bonus_animation}
/// [Widget] that displays bonus animations.
/// {@endtemplate}
class BonusAnimation extends StatefulWidget {
/// {@macro bonus_animation}
const BonusAnimation._(
this.imagePath, {
String imagePath, {
VoidCallback? onCompleted,
Key? key,
}) : _onCompleted = onCompleted,
}) : _imagePath = imagePath,
_onCompleted = onCompleted,
super(key: key);
/// [Widget] that displays the dash nest animation.
BonusAnimation.dashNest({
Key? key,
VoidCallback? onCompleted,
@ -23,6 +27,7 @@ class BonusAnimation extends StatelessWidget {
key: key,
);
/// [Widget] that displays the sparky turbo charge animation.
BonusAnimation.sparkyTurboCharge({
Key? key,
VoidCallback? onCompleted,
@ -32,56 +37,94 @@ class BonusAnimation extends StatelessWidget {
key: key,
);
BonusAnimation.dino({
/// [Widget] that displays the dino chomp animation.
BonusAnimation.dinoChomp({
Key? key,
VoidCallback? onCompleted,
}) : this._(
Assets.images.bonusAnimation.dino.keyName,
Assets.images.bonusAnimation.dinoChomp.keyName,
onCompleted: onCompleted,
key: key,
);
BonusAnimation.android({
/// [Widget] that displays the android spaceship animation.
BonusAnimation.androidSpaceship({
Key? key,
VoidCallback? onCompleted,
}) : this._(
Assets.images.bonusAnimation.android.keyName,
Assets.images.bonusAnimation.androidSpaceship.keyName,
onCompleted: onCompleted,
key: key,
);
BonusAnimation.google({
/// [Widget] that displays the google word animation.
BonusAnimation.googleWord({
Key? key,
VoidCallback? onCompleted,
}) : this._(
Assets.images.bonusAnimation.google.keyName,
Assets.images.bonusAnimation.googleWord.keyName,
onCompleted: onCompleted,
key: key,
);
final String imagePath;
final String _imagePath;
final VoidCallback? _onCompleted;
static Future<void> loadAssets() {
/// Returns a list of assets to be loaded for animations.
static List<Future> loadAssets() {
Flame.images.prefix = '';
return Flame.images.loadAll([
Assets.images.bonusAnimation.dashNest.keyName,
Assets.images.bonusAnimation.sparkyTurboCharge.keyName,
Assets.images.bonusAnimation.dino.keyName,
Assets.images.bonusAnimation.android.keyName,
Assets.images.bonusAnimation.google.keyName,
]);
return [
Flame.images.load(Assets.images.bonusAnimation.dashNest.keyName),
Flame.images.load(Assets.images.bonusAnimation.sparkyTurboCharge.keyName),
Flame.images.load(Assets.images.bonusAnimation.dinoChomp.keyName),
Flame.images.load(Assets.images.bonusAnimation.androidSpaceship.keyName),
Flame.images.load(Assets.images.bonusAnimation.googleWord.keyName),
];
}
@override
State<BonusAnimation> createState() => _BonusAnimationState();
}
class _BonusAnimationState extends State<BonusAnimation>
with TickerProviderStateMixin {
late SpriteAnimationController controller;
late SpriteAnimation animation;
bool shouldRunBuildCallback = true;
@override
void dispose() {
controller.dispose();
super.dispose();
}
// When the animation is overwritten by another animation, we need to stop
// the callback in the build method as it will break the new animation.
// Otherwise we need to set up a new callback when a new animation starts to
// show the score view at the end of the animation.
@override
void didUpdateWidget(BonusAnimation oldWidget) {
shouldRunBuildCallback = oldWidget._imagePath == widget._imagePath;
Future<void>.delayed(
Duration(seconds: animation.totalDuration().ceil()),
() {
widget._onCompleted?.call();
},
);
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
final spriteSheet = SpriteSheet.fromColumnsAndRows(
image: Flame.images.fromCache(imagePath),
image: Flame.images.fromCache(widget._imagePath),
columns: 8,
rows: 9,
);
final animation = spriteSheet.createAnimation(
animation = spriteSheet.createAnimation(
row: 0,
stepTime: 1 / 24,
to: spriteSheet.rows * spriteSheet.columns,
@ -91,15 +134,22 @@ class BonusAnimation extends StatelessWidget {
Future<void>.delayed(
Duration(seconds: animation.totalDuration().ceil()),
() {
_onCompleted?.call();
if (shouldRunBuildCallback) {
widget._onCompleted?.call();
}
},
);
controller = SpriteAnimationController(
animation: animation,
vsync: this,
)..forward();
return SizedBox(
width: double.infinity,
height: double.infinity,
child: SpriteAnimationWidget(
animation: animation,
controller: controller,
),
);
}

@ -1,46 +1,122 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/gen/gen.dart';
import 'package:pinball/theme/app_colors.dart';
/// {@template game_hud}
/// Overlay of a [PinballGame] that displays the current [GameState.score] and
/// [GameState.balls].
/// Overlay on the [PinballGame].
///
/// Displays the current [GameState.score], [GameState.balls] and animates when
/// the player gets a [GameBonus].
/// {@endtemplate}
class GameHud extends StatelessWidget {
class GameHud extends StatefulWidget {
/// {@macro game_hud}
const GameHud({Key? key}) : super(key: key);
@override
State<GameHud> createState() => _GameHudState();
}
class _GameHudState extends State<GameHud> {
bool showAnimation = false;
/// Ratio from sprite frame (width 500, height 144) w / h = ratio
static const _ratio = 3.47;
static const _width = 265.0;
@override
Widget build(BuildContext context) {
final state = context.watch<GameBloc>().state;
return Container(
color: Colors.redAccent,
width: 200,
height: 100,
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${state.score}',
style: Theme.of(context).textTheme.headline3,
final isGameOver = context.select((GameBloc bloc) => bloc.state.isGameOver);
return _ScoreViewDecoration(
child: SizedBox(
height: _width / _ratio,
width: _width,
child: BlocListener<GameBloc, GameState>(
listenWhen: (previous, current) =>
previous.bonusHistory.length != current.bonusHistory.length,
listener: (_, __) => setState(() => showAnimation = true),
child: AnimatedSwitcher(
duration: kThemeAnimationDuration,
child: showAnimation && !isGameOver
? _AnimationView(
onComplete: () {
if (mounted) {
setState(() => showAnimation = false);
}
},
)
: const ScoreView(),
),
Wrap(
direction: Axis.vertical,
children: [
for (var i = 0; i < state.balls; i++)
const Padding(
padding: EdgeInsets.only(top: 6, right: 6),
child: CircleAvatar(
radius: 8,
backgroundColor: Colors.black,
),
),
],
),
),
);
}
}
class _ScoreViewDecoration extends StatelessWidget {
const _ScoreViewDecoration({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
@override
Widget build(BuildContext context) {
const radius = BorderRadius.all(Radius.circular(12));
const boardWidth = 5.0;
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: radius,
border: Border.all(
color: AppColors.white,
width: boardWidth,
),
image: DecorationImage(
fit: BoxFit.cover,
image: AssetImage(
Assets.images.score.miniScoreBackground.path,
),
],
),
),
child: Padding(
padding: const EdgeInsets.all(boardWidth - 1),
child: ClipRRect(
borderRadius: radius,
child: child,
),
),
);
}
}
class _AnimationView extends StatelessWidget {
const _AnimationView({
Key? key,
required this.onComplete,
}) : super(key: key);
final VoidCallback onComplete;
@override
Widget build(BuildContext context) {
final lastBonus = context.select(
(GameBloc bloc) => bloc.state.bonusHistory.last,
);
switch (lastBonus) {
case GameBonus.dashNest:
return BonusAnimation.dashNest(onCompleted: onComplete);
case GameBonus.sparkyTurboCharge:
return BonusAnimation.sparkyTurboCharge(onCompleted: onComplete);
case GameBonus.dinoChomp:
return BonusAnimation.dinoChomp(onCompleted: onComplete);
case GameBonus.googleWord:
return BonusAnimation.googleWord(onCompleted: onComplete);
case GameBonus.androidSpaceship:
return BonusAnimation.androidSpaceship(onCompleted: onComplete);
}
}
}

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:pinball/game/pinball_game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball/select_character/select_character.dart';
/// {@template play_button_overlay}
/// [Widget] that renders the button responsible to starting the game

@ -0,0 +1,70 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/theme/theme.dart';
/// {@template round_count_display}
/// Colored square indicating if a round is available.
/// {@endtemplate}
class RoundCountDisplay extends StatelessWidget {
/// {@macro round_count_display}
const RoundCountDisplay({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
// TODO(arturplaczek): refactor when GameState handle balls and rounds and
// select state.rounds property instead of state.ball
final balls = context.select((GameBloc bloc) => bloc.state.balls);
return Row(
children: [
Text(
l10n.rounds,
style: AppTextStyle.subtitle1.copyWith(
color: AppColors.orange,
),
),
const SizedBox(width: 8),
Row(
children: [
RoundIndicator(isActive: balls >= 1),
RoundIndicator(isActive: balls >= 2),
RoundIndicator(isActive: balls >= 3),
],
),
],
);
}
}
/// {@template round_indicator}
/// [Widget] that displays the round indicator.
/// {@endtemplate}
@visibleForTesting
class RoundIndicator extends StatelessWidget {
/// {@macro round_indicator}
const RoundIndicator({
Key? key,
required this.isActive,
}) : super(key: key);
/// A value that describes whether the indicator is active.
final bool isActive;
@override
Widget build(BuildContext context) {
final color = isActive ? AppColors.orange : AppColors.orange.withAlpha(128);
const size = 8.0;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Container(
color: color,
height: size,
width: size,
),
);
}
}

@ -0,0 +1,86 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template score_view}
/// [Widget] that displays the score.
/// {@endtemplate}
class ScoreView extends StatelessWidget {
/// {@macro score_view}
const ScoreView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final isGameOver = context.select((GameBloc bloc) => bloc.state.isGameOver);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
child: AnimatedSwitcher(
duration: kThemeAnimationDuration,
child: isGameOver ? const _GameOver() : const _ScoreDisplay(),
),
);
}
}
class _GameOver extends StatelessWidget {
const _GameOver({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Text(
l10n.gameOver,
style: AppTextStyle.headline1.copyWith(
color: AppColors.white,
),
);
}
}
class _ScoreDisplay extends StatelessWidget {
const _ScoreDisplay({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
l10n.score.toLowerCase(),
style: AppTextStyle.subtitle1.copyWith(
color: AppColors.orange,
),
),
const _ScoreText(),
const RoundCountDisplay(),
],
);
}
}
class _ScoreText extends StatelessWidget {
const _ScoreText({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final score = context.select((GameBloc bloc) => bloc.state.score);
return Text(
score.formatScore(),
style: AppTextStyle.headline1.copyWith(
color: AppColors.white,
),
);
}
}

@ -1,3 +1,5 @@
export 'bonus_animation.dart';
export 'game_hud.dart';
export 'play_button_overlay.dart';
export 'round_count_display.dart';
export 'score_view.dart';

@ -14,26 +14,27 @@ class $AssetsImagesGen {
const $AssetsImagesBonusAnimationGen();
$AssetsImagesComponentsGen get components =>
const $AssetsImagesComponentsGen();
$AssetsImagesScoreGen get score => const $AssetsImagesScoreGen();
}
class $AssetsImagesBonusAnimationGen {
const $AssetsImagesBonusAnimationGen();
/// File path: assets/images/bonus_animation/android.png
AssetGenImage get android =>
const AssetGenImage('assets/images/bonus_animation/android.png');
/// File path: assets/images/bonus_animation/android_spaceship.png
AssetGenImage get androidSpaceship => const AssetGenImage(
'assets/images/bonus_animation/android_spaceship.png');
/// File path: assets/images/bonus_animation/dash_nest.png
AssetGenImage get dashNest =>
const AssetGenImage('assets/images/bonus_animation/dash_nest.png');
/// File path: assets/images/bonus_animation/dino.png
AssetGenImage get dino =>
const AssetGenImage('assets/images/bonus_animation/dino.png');
/// File path: assets/images/bonus_animation/dino_chomp.png
AssetGenImage get dinoChomp =>
const AssetGenImage('assets/images/bonus_animation/dino_chomp.png');
/// File path: assets/images/bonus_animation/google.png
AssetGenImage get google =>
const AssetGenImage('assets/images/bonus_animation/google.png');
/// File path: assets/images/bonus_animation/google_word.png
AssetGenImage get googleWord =>
const AssetGenImage('assets/images/bonus_animation/google_word.png');
/// File path: assets/images/bonus_animation/sparky_turbo_charge.png
AssetGenImage get sparkyTurboCharge => const AssetGenImage(
@ -48,6 +49,14 @@ class $AssetsImagesComponentsGen {
const AssetGenImage('assets/images/components/background.png');
}
class $AssetsImagesScoreGen {
const $AssetsImagesScoreGen();
/// File path: assets/images/score/mini_score_background.png
AssetGenImage get miniScoreBackground =>
const AssetGenImage('assets/images/score/mini_score_background.png');
}
class Assets {
Assets._();

@ -75,5 +75,9 @@
"enterInitials": "Enter your initials",
"@enterInitials": {
"description": "Text displayed on the ending dialog when game finishes to ask the user for his initials"
},
"rounds": "Ball Ct:",
"@rounds": {
"description": "Text displayed on the scoreboard widget to indicate rounds left"
}
}

@ -36,7 +36,7 @@ extension LeaderboardEntryDataX on LeaderboardEntryData {
rank: position.toString(),
playerInitials: playerInitials,
score: score,
character: character.toTheme.character,
character: character.toTheme.leaderboardIcon,
);
}
}

@ -5,7 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:leaderboard_repository/leaderboard_repository.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/leaderboard/leaderboard.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball_theme/pinball_theme.dart';
class LeaderboardPage extends StatelessWidget {

@ -5,12 +5,12 @@ import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:pinball_theme/pinball_theme.dart';
part 'theme_state.dart';
part 'character_theme_state.dart';
class ThemeCubit extends Cubit<ThemeState> {
ThemeCubit() : super(const ThemeState.initial());
class CharacterThemeCubit extends Cubit<CharacterThemeState> {
CharacterThemeCubit() : super(const CharacterThemeState.initial());
void characterSelected(CharacterTheme characterTheme) {
emit(ThemeState(PinballTheme(characterTheme: characterTheme)));
emit(CharacterThemeState(characterTheme));
}
}

@ -0,0 +1,15 @@
// ignore_for_file: public_member_api_docs
// TODO(allisonryan0002): Document this section when the API is stable.
part of 'character_theme_cubit.dart';
class CharacterThemeState extends Equatable {
const CharacterThemeState(this.characterTheme);
const CharacterThemeState.initial() : characterTheme = const DashTheme();
final CharacterTheme characterTheme;
@override
List<Object> get props => [characterTheme];
}

@ -0,0 +1,2 @@
export 'cubit/character_theme_cubit.dart';
export 'view/view.dart';

@ -3,8 +3,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pinball/l10n/l10n.dart';
import 'package:pinball/select_character/select_character.dart';
import 'package:pinball/start_game/start_game.dart';
import 'package:pinball/theme/theme.dart';
import 'package:pinball_theme/pinball_theme.dart';
class CharacterSelectionDialog extends StatelessWidget {
@ -19,7 +19,7 @@ class CharacterSelectionDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => ThemeCubit(),
create: (_) => CharacterThemeCubit(),
child: const CharacterSelectionView(),
);
}
@ -109,12 +109,14 @@ class CharacterImageButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final currentCharacterTheme = context.select<ThemeCubit, CharacterTheme>(
(cubit) => cubit.state.theme.characterTheme,
final currentCharacterTheme =
context.select<CharacterThemeCubit, CharacterTheme>(
(cubit) => cubit.state.characterTheme,
);
return GestureDetector(
onTap: () => context.read<ThemeCubit>().characterSelected(characterTheme),
onTap: () =>
context.read<CharacterThemeCubit>().characterSelected(characterTheme),
child: DecoratedBox(
decoration: BoxDecoration(
color: (currentCharacterTheme == characterTheme)
@ -124,7 +126,7 @@ class CharacterImageButton extends StatelessWidget {
),
child: Padding(
padding: const EdgeInsets.all(8),
child: characterTheme.character.image(),
child: characterTheme.icon.image(),
),
),
);

@ -5,7 +5,7 @@ import 'package:pinball/theme/theme.dart';
import 'package:pinball_components/pinball_components.dart';
const _fontPackage = 'pinball_components';
const _primaryFontFamily = PinballFonts.pixeloidSans;
const _primaryFontFamily = FontFamily.pixeloidSans;
abstract class AppTextStyle {
static const headline1 = TextStyle(

@ -1,16 +0,0 @@
// ignore_for_file: public_member_api_docs
// TODO(allisonryan0002): Document this section when the API is stable.
part of 'theme_cubit.dart';
class ThemeState extends Equatable {
const ThemeState(this.theme);
const ThemeState.initial()
: theme = const PinballTheme(characterTheme: DashTheme());
final PinballTheme theme;
@override
List<Object> get props => [theme];
}

@ -1,4 +1,2 @@
export 'app_colors.dart';
export 'app_text_style.dart';
export 'cubit/theme_cubit.dart';
export 'view/view.dart';

@ -72,6 +72,20 @@ class FetchPlayerRankingException extends LeaderboardException {
);
}
/// {@template fetch_prohibited_initials_exception}
/// Exception thrown when failure occurs while fetching prohibited initials.
/// {@endtemplate}
class FetchProhibitedInitialsException extends LeaderboardException {
/// {@macro fetch_prohibited_initials_exception}
const FetchProhibitedInitialsException(
Object error,
StackTrace stackTrace,
) : super(
error,
stackTrace,
);
}
/// {@template leaderboard_repository}
/// Repository to access leaderboard data in Firebase Cloud Firestore.
/// {@endtemplate}
@ -152,4 +166,25 @@ class LeaderboardRepository {
throw FetchPlayerRankingException(error, stackTrace);
}
}
/// Determines if the given [initials] are allowed.
Future<bool> areInitialsAllowed({required String initials}) async {
// Initials can only be three uppercase A-Z letters
final initialsRegex = RegExp(r'^[A-Z]{3}$');
if (!initialsRegex.hasMatch(initials)) {
return false;
}
try {
final document = await _firebaseFirestore
.collection('prohibitedInitials')
.doc('list')
.get();
final prohibitedInitials =
document.get('prohibitedInitials') as List<String>;
return !prohibitedInitials.contains(initials);
} on Exception catch (error, stackTrace) {
throw FetchProhibitedInitialsException(error, stackTrace);
}
}
}

@ -21,6 +21,9 @@ class MockQueryDocumentSnapshot extends Mock
class MockDocumentReference extends Mock
implements DocumentReference<Map<String, dynamic>> {}
class MockDocumentSnapshot extends Mock
implements DocumentSnapshot<Map<String, dynamic>> {}
void main() {
group('LeaderboardRepository', () {
late FirebaseFirestore firestore;
@ -223,5 +226,94 @@ void main() {
);
});
});
group('areInitialsAllowed', () {
late LeaderboardRepository leaderboardRepository;
late CollectionReference<Map<String, dynamic>> collectionReference;
late DocumentReference<Map<String, dynamic>> documentReference;
late DocumentSnapshot<Map<String, dynamic>> documentSnapshot;
setUp(() async {
collectionReference = MockCollectionReference();
documentReference = MockDocumentReference();
documentSnapshot = MockDocumentSnapshot();
leaderboardRepository = LeaderboardRepository(firestore);
when(() => firestore.collection('prohibitedInitials'))
.thenReturn(collectionReference);
when(() => collectionReference.doc('list'))
.thenReturn(documentReference);
when(() => documentReference.get())
.thenAnswer((_) async => documentSnapshot);
when<dynamic>(() => documentSnapshot.get('prohibitedInitials'))
.thenReturn(['BAD']);
});
test('returns true if initials are three letters and allowed', () async {
final isUsernameAllowedResponse =
await leaderboardRepository.areInitialsAllowed(
initials: 'ABC',
);
expect(
isUsernameAllowedResponse,
isTrue,
);
});
test(
'returns false if initials are shorter than 3 characters',
() async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'AB');
expect(areInitialsAllowedResponse, isFalse);
},
);
test(
'returns false if initials are longer than 3 characters',
() async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'ABCD');
expect(areInitialsAllowedResponse, isFalse);
},
);
test(
'returns false if initials contain a lowercase letter',
() async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'AbC');
expect(areInitialsAllowedResponse, isFalse);
},
);
test(
'returns false if initials contain a special character',
() async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'A@C');
expect(areInitialsAllowedResponse, isFalse);
},
);
test('returns false if initials are forbidden', () async {
final areInitialsAllowedResponse =
await leaderboardRepository.areInitialsAllowed(initials: 'BAD');
expect(areInitialsAllowedResponse, isFalse);
});
test(
'throws FetchProhibitedInitialsException when Exception occurs '
'when trying to retrieve information from firestore',
() async {
when(() => firestore.collection('prohibitedInitials'))
.thenThrow(Exception('oops'));
expect(
() => leaderboardRepository.areInitialsAllowed(initials: 'ABC'),
throwsA(isA<FetchProhibitedInitialsException>()),
);
},
);
});
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

@ -86,6 +86,10 @@ class $AssetsImagesBoundaryGen {
AssetGenImage get bottom =>
const AssetGenImage('assets/images/boundary/bottom.png');
/// File path: assets/images/boundary/outer-bottom.png
AssetGenImage get outerBottom =>
const AssetGenImage('assets/images/boundary/outer-bottom.png');
/// File path: assets/images/boundary/outer.png
AssetGenImage get outer =>
const AssetGenImage('assets/images/boundary/outer.png');
@ -257,6 +261,8 @@ class $AssetsImagesSpaceshipGen {
class $AssetsImagesSparkyGen {
const $AssetsImagesSparkyGen();
AssetGenImage get animatronic =>
const AssetGenImage('assets/images/sparky/animatronic.png');
$AssetsImagesSparkyBumperGen get bumper =>
const $AssetsImagesSparkyBumperGen();
$AssetsImagesSparkyComputerGen get computer =>

@ -1,2 +1,3 @@
export 'assets.gen.dart';
export 'fonts.gen.dart';
export 'pinball_fonts.dart';

@ -13,6 +13,7 @@ class Boundaries extends Blueprint {
components: [
_BottomBoundary(),
_OuterBoundary(),
_OuterBottomBoundarySpriteComponent(),
],
);
}
@ -91,8 +92,10 @@ class _OuterBoundary extends BodyComponent with InitialPosition {
/// {@macro outer_boundary}
_OuterBoundary()
: super(
priority: RenderPriority.outerBoudary,
children: [_OuterBoundarySpriteComponent()],
priority: RenderPriority.outerBoundary,
children: [
_OuterBoundarySpriteComponent(),
],
) {
renderBody = false;
}
@ -157,3 +160,25 @@ class _OuterBoundarySpriteComponent extends SpriteComponent with HasGameRef {
size = sprite.originalSize / 10;
}
}
class _OuterBottomBoundarySpriteComponent extends SpriteComponent
with HasGameRef {
_OuterBottomBoundarySpriteComponent()
: super(
priority: RenderPriority.outerBottomBoundary,
anchor: Anchor.center,
position: Vector2(0, 71),
);
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.boundary.outerBottom.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
}
}

@ -29,5 +29,6 @@ export 'slingshot.dart';
export 'spaceship.dart';
export 'spaceship_rail.dart';
export 'spaceship_ramp.dart';
export 'sparky_animatronic.dart';
export 'sparky_bumper.dart';
export 'sparky_computer.dart';

@ -38,17 +38,9 @@ class DashAnimatronic extends SpriteAnimationComponent with HasGameRef {
textureSize: textureSize,
loop: false,
),
);
}
@override
void update(double dt) {
super.update(dt);
if (animation != null) {
if (animation!.isLastFrame) {
animation!.reset();
)..onComplete = () {
animation?.reset();
playing = false;
}
}
};
}
}

@ -34,24 +34,21 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
}
List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
final topStraightShape = EdgeShape()
..set(
Vector2(28.65, -35.1),
Vector2(29.5, -35.1),
);
final topStraightFixtureDef = FixtureDef(topStraightShape);
fixturesDef.add(topStraightFixtureDef);
final topCurveShape = BezierCurveShape(
controlPoints: [
topStraightShape.vertex1,
Vector2(17.4, -26.38),
Vector2(25.5, -20.7),
Vector2(18.8, -27),
Vector2(26.6, -21),
],
);
fixturesDef.add(FixtureDef(topCurveShape));
final topCurveFixtureDef = FixtureDef(topCurveShape);
final middleCurveShape = BezierCurveShape(
controlPoints: [
@ -60,16 +57,16 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
Vector2(26.8, -19.5),
],
);
fixturesDef.add(FixtureDef(middleCurveShape));
final middleCurveFixtureDef = FixtureDef(middleCurveShape);
final bottomCurveShape = BezierCurveShape(
controlPoints: [
middleCurveShape.vertices.last,
Vector2(21.5, -15.8),
Vector2(25.8, -14.8),
Vector2(23, -15),
Vector2(27, -15),
],
);
fixturesDef.add(FixtureDef(bottomCurveShape));
final bottomCurveFixtureDef = FixtureDef(bottomCurveShape);
final bottomStraightShape = EdgeShape()
..set(
@ -77,9 +74,14 @@ class _DinoTopWall extends BodyComponent with InitialPosition {
Vector2(31, -14.5),
);
final bottomStraightFixtureDef = FixtureDef(bottomStraightShape);
fixturesDef.add(bottomStraightFixtureDef);
return fixturesDef;
return [
topStraightFixtureDef,
topCurveFixtureDef,
middleCurveFixtureDef,
bottomCurveFixtureDef,
bottomStraightFixtureDef,
];
}
@override
@ -106,12 +108,14 @@ class _DinoTopWallSpriteComponent extends SpriteComponent with HasGameRef {
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.dino.dinoLandTop.keyName,
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.dino.dinoLandTop.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;
position = Vector2(22, -41.8);
position = Vector2(22.8, -38.9);
}
}
@ -129,69 +133,56 @@ class _DinoBottomWall extends BodyComponent with InitialPosition {
}
List<FixtureDef> _createFixtureDefs() {
final fixturesDef = <FixtureDef>[];
const restitution = 1.0;
final topStraightControlPoints = [
Vector2(32.4, -8.8),
Vector2(25, -7.7),
];
final topStraightShape = EdgeShape()
..set(
topStraightControlPoints.first,
topStraightControlPoints.last,
Vector2(32.4, -8.8),
Vector2(25, -7.7),
);
final topStraightFixtureDef = FixtureDef(
topStraightShape,
restitution: restitution,
);
fixturesDef.add(topStraightFixtureDef);
final topLeftCurveControlPoints = [
topStraightControlPoints.last,
Vector2(21.8, -7),
Vector2(29.5, 13.8),
];
final topLeftCurveShape = BezierCurveShape(
controlPoints: topLeftCurveControlPoints,
controlPoints: [
topStraightShape.vertex2,
Vector2(21.8, -7),
Vector2(29.8, 13.8),
],
);
final topLeftCurveFixtureDef = FixtureDef(
topLeftCurveShape,
restitution: restitution,
);
fixturesDef.add(topLeftCurveFixtureDef);
final bottomLeftStraightControlPoints = [
topLeftCurveControlPoints.last,
Vector2(31.8, 44.1),
];
final bottomLeftStraightShape = EdgeShape()
..set(
bottomLeftStraightControlPoints.first,
bottomLeftStraightControlPoints.last,
topLeftCurveShape.vertices.last,
Vector2(31.9, 44.1),
);
final bottomLeftStraightFixtureDef = FixtureDef(
bottomLeftStraightShape,
restitution: restitution,
);
fixturesDef.add(bottomLeftStraightFixtureDef);
final bottomStraightControlPoints = [
bottomLeftStraightControlPoints.last,
Vector2(37.8, 44.1),
];
final bottomStraightShape = EdgeShape()
..set(
bottomStraightControlPoints.first,
bottomStraightControlPoints.last,
bottomLeftStraightShape.vertex2,
Vector2(37.8, 44.1),
);
final bottomStraightFixtureDef = FixtureDef(
bottomStraightShape,
restitution: restitution,
);
fixturesDef.add(bottomStraightFixtureDef);
return fixturesDef;
return [
topStraightFixtureDef,
topLeftCurveFixtureDef,
bottomLeftStraightFixtureDef,
bottomStraightFixtureDef,
];
}
@override
@ -212,8 +203,10 @@ class _DinoBottomWallSpriteComponent extends SpriteComponent with HasGameRef {
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.dino.dinoLandBottom.keyName,
final sprite = Sprite(
gameRef.images.fromCache(
Assets.images.dino.dinoLandBottom.keyName,
),
);
this.sprite = sprite;
size = sprite.originalSize / 10;

@ -78,6 +78,7 @@ class _GoogleLetterSprite extends SpriteComponent with HasGameRef {
Future<void> onLoad() async {
await super.onLoad();
// TODO(alestiago): Used cached assets.
final sprite = await gameRef.loadSprite(_path);
this.sprite = sprite;
// TODO(alestiago): Size correctly once the assets are provided.

@ -136,6 +136,7 @@ class _KickerSpriteComponent extends SpriteComponent with HasGameRef {
Future<void> onLoad() async {
await super.onLoad();
// TODO(alestiago): Used cached asset.
final sprite = await gameRef.loadSprite(
(_side.isLeft)
? Assets.images.kicker.left.keyName

@ -148,6 +148,7 @@ class _PlungerSpriteAnimationGroupComponent
Future<void> onLoad() async {
await super.onLoad();
// TODO(alestiago): Used cached images.
final spriteSheet = await gameRef.images.load(
Assets.images.plunger.plunger.keyName,
);

@ -39,7 +39,9 @@ abstract class RenderPriority {
static const int bottomBoundary = _above + dinoBottomWall;
static const int outerBoudary = _above + background;
static const int outerBoundary = _above + background;
static const int outerBottomBoundary = _above + rocket;
// Bottom Group
@ -47,7 +49,7 @@ abstract class RenderPriority {
// Launcher
static const int launchRamp = _above + outerBoudary;
static const int launchRamp = _above + outerBoundary;
static const int launchRampForegroundRailing = _below + ballOnBoard;
@ -113,4 +115,7 @@ abstract class RenderPriority {
// Score Text
static const int scoreText = _above + spaceshipRampForegroundRailing;
// Debug information
static const int debugInfo = _above + scoreText;
}

@ -0,0 +1,46 @@
import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template sparky_animatronic}
/// Animated Sparky that sits on top of the [SparkyComputer].
/// {@endtemplate}
class SparkyAnimatronic extends SpriteAnimationComponent with HasGameRef {
/// {@macro sparky_animatronic}
SparkyAnimatronic()
: super(
anchor: Anchor.center,
playing: false,
priority: RenderPriority.sparkyAnimatronic,
);
@override
Future<void> onLoad() async {
await super.onLoad();
final spriteSheet = gameRef.images.fromCache(
Assets.images.sparky.animatronic.keyName,
);
const amountPerRow = 9;
const amountPerColumn = 7;
final textureSize = Vector2(
spriteSheet.width / amountPerRow,
spriteSheet.height / amountPerColumn,
);
size = textureSize / 10;
animation = SpriteAnimation.fromFrameData(
spriteSheet,
SpriteAnimationData.sequenced(
amount: (amountPerRow * amountPerColumn) - 1,
amountPerRow: amountPerRow,
stepTime: 1 / 24,
textureSize: textureSize,
loop: false,
),
)..onComplete = () {
animation?.reset();
playing = false;
};
}
}

@ -7,9 +7,6 @@ import 'package:pinball_flame/pinball_flame.dart';
/// {@template sparky_computer}
/// A computer owned by Sparky.
///
/// Register a [ContactCallback] for [SparkyComputerSensor] to listen when
/// something enters the [SparkyComputer].
/// {@endtemplate}
class SparkyComputer extends Blueprint {
/// {@macro sparky_computer}
@ -18,7 +15,6 @@ class SparkyComputer extends Blueprint {
components: [
_ComputerBase(),
_ComputerTopSpriteComponent(),
SparkyComputerSensor(),
],
);
}
@ -104,24 +100,3 @@ class _ComputerTopSpriteComponent extends SpriteComponent with HasGameRef {
size = sprite.originalSize / 10;
}
}
/// {@template sparky_computer_sensor}
/// Small sensor body used to detect when a ball has entered the
/// [SparkyComputer].
/// {@endtemplate}
class SparkyComputerSensor extends BodyComponent with InitialPosition {
/// {@macro sparky_computer_sensor}
SparkyComputerSensor() {
renderBody = false;
}
@override
Body createBody() {
final shape = CircleShape()..radius = 0.1;
final fixtureDef = FixtureDef(shape, isSensor: true);
final bodyDef = BodyDef()
..position = initialPosition
..userData = this;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}

@ -61,6 +61,7 @@ flutter:
- assets/images/slingshot/
- assets/images/alien_bumper/a/
- assets/images/alien_bumper/b/
- assets/images/sparky/
- assets/images/sparky/computer/
- assets/images/sparky/bumper/a/
- assets/images/sparky/bumper/b/

@ -0,0 +1,36 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
const _path =
'https://github.com/VGVentures/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/';
extension StoryAddGame on Story {
void addGame({
required String title,
required String description,
required Game Function(DashbookContext) gameBuilder,
}) {
final _chapter = Chapter(
title,
(DashbookContext context) {
final game = gameBuilder(context);
if (game is Traceable) {
game.trace = context.boolProperty('Trace', true);
}
return GameWidget(game: game);
},
this,
codeLink: '$_path${name.toPath()}/${title.toPath()}',
info: description,
);
chapters.add(_chapter);
}
}
extension on String {
String toPath() {
return replaceAll(' ', '_')..toLowerCase();
}
}

@ -1,3 +1,3 @@
export 'add_game.dart';
export 'games.dart';
export 'methods.dart';
export 'trace.dart';

@ -5,16 +5,25 @@ import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
abstract class BasicGame extends Forge2DGame {
BasicGame() {
abstract class AssetsGame extends Forge2DGame {
AssetsGame({
List<String>? imagesFileNames,
}) : _imagesFileNames = imagesFileNames {
images.prefix = '';
}
}
abstract class BasicKeyboardGame extends BasicGame
with HasKeyboardHandlerComponents {}
final List<String>? _imagesFileNames;
@override
Future<void> onLoad() async {
await super.onLoad();
if (_imagesFileNames != null) {
await images.loadAll(_imagesFileNames!);
}
}
}
abstract class LineGame extends BasicGame with PanDetector {
abstract class LineGame extends AssetsGame with PanDetector {
Vector2? _lineEnd;
@override

@ -1,3 +0,0 @@
String buildSourceLink(String path) {
return 'https://github.com/VGVentures/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/$path';
}

@ -35,7 +35,7 @@ mixin Traceable on Forge2DGame {
.forEach((bodyComponent) => bodyComponent.trace());
descendants()
.whereType<SpriteComponent>()
.whereType<HasPaint>()
.forEach((sprite) => sprite.setOpacity(0.5));
}
}

@ -16,9 +16,6 @@ void main() {
addLayerStories(dashbook);
addEffectsStories(dashbook);
addFlipperStories(dashbook);
addSpaceshipStories(dashbook);
addSpaceshipRampStories(dashbook);
addSpaceshipRailStories(dashbook);
addBaseboardStories(dashbook);
addChromeDinoStories(dashbook);
addDashNestBumperStories(dashbook);
@ -27,12 +24,12 @@ void main() {
addSlingshotStories(dashbook);
addSparkyBumperStories(dashbook);
addAlienZoneStories(dashbook);
addZoomStories(dashbook);
addBoundariesStories(dashbook);
addGoogleWordStories(dashbook);
addLaunchRampStories(dashbook);
addScoreTextStories(dashbook);
addBackboardStories(dashbook);
addDinoWallStories(dashbook);
runApp(dashbook);
}

@ -4,10 +4,17 @@ import 'package:flame/extensions.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class AlienBumperAGame extends BasicBallGame {
AlienBumperAGame() : super(color: const Color(0xFF0000FF));
static const info = '''
class AlienBumperAGame extends BallGame {
AlienBumperAGame()
: super(
color: const Color(0xFF0000FF),
imagesFileNames: [
Assets.images.alienBumper.a.active.keyName,
Assets.images.alienBumper.a.inactive.keyName,
],
);
static const description = '''
Shows how a AlienBumperA is rendered.
- Activate the "trace" parameter to overlay the body.
@ -17,16 +24,10 @@ class AlienBumperAGame extends BasicBallGame {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.alienBumper.a.active.keyName,
Assets.images.alienBumper.a.inactive.keyName,
]);
final center = screenToWorld(camera.viewport.canvasSize! / 2);
final alienBumperA = AlienBumper.a()
..initialPosition = Vector2(center.x - 20, center.y - 20)
..priority = 1;
await add(alienBumperA);
camera.followVector2(Vector2.zero());
await add(
AlienBumper.a()..priority = 1,
);
await traceAllBodies();
}

@ -4,10 +4,17 @@ import 'package:flame/extensions.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class AlienBumperBGame extends BasicBallGame {
AlienBumperBGame() : super(color: const Color(0xFF0000FF));
static const info = '''
class AlienBumperBGame extends BallGame {
AlienBumperBGame()
: super(
color: const Color(0xFF0000FF),
imagesFileNames: [
Assets.images.alienBumper.b.active.keyName,
Assets.images.alienBumper.b.inactive.keyName,
],
);
static const description = '''
Shows how a AlienBumperB is rendered.
- Activate the "trace" parameter to overlay the body.
@ -17,16 +24,10 @@ class AlienBumperBGame extends BasicBallGame {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.alienBumper.b.active.keyName,
Assets.images.alienBumper.b.inactive.keyName,
]);
final center = screenToWorld(camera.viewport.canvasSize! / 2);
final alienBumperB = AlienBumper.b()
..initialPosition = Vector2(center.x - 10, center.y + 10)
..priority = 1;
await add(alienBumperB);
camera.followVector2(Vector2.zero());
await add(
AlienBumper.b()..priority = 1,
);
await traceAllBodies();
}

@ -6,8 +6,8 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/common/common.dart';
class BasicSpaceshipGame extends BasicGame with TapDetector {
static const info = '''
class SpaceshipGame extends AssetsGame with TapDetector {
static const description = '''
Shows how a Spaceship works.
- Tap anywhere on the screen to spawn a Ball into the game.
@ -18,10 +18,10 @@ class BasicSpaceshipGame extends BasicGame with TapDetector {
await super.onLoad();
camera.followVector2(Vector2.zero());
unawaited(
addFromBlueprint(Spaceship(position: Vector2.zero())),
await addFromBlueprint(
Spaceship(position: Vector2.zero()),
);
await ready();
}
@override

@ -6,7 +6,7 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SpaceshipRailGame extends BasicBallGame {
class SpaceshipRailGame extends BallGame {
SpaceshipRailGame()
: super(
color: Colors.blue,
@ -14,7 +14,7 @@ class SpaceshipRailGame extends BasicBallGame {
ballLayer: Layer.spaceshipExitRail,
);
static const info = '''
static const description = '''
Shows how SpaceshipRail are rendered.
- Activate the "trace" parameter to overlay the body.
@ -26,10 +26,8 @@ class SpaceshipRailGame extends BasicBallGame {
await super.onLoad();
camera.followVector2(Vector2(-30, -10));
final spaceshipRail = SpaceshipRail();
unawaited(addFromBlueprint(spaceshipRail));
await addFromBlueprint(SpaceshipRail());
await ready();
await traceAllBodies();
}
}

@ -7,15 +7,27 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SpaceshipRampGame extends BasicBallGame with KeyboardEvents {
class SpaceshipRampGame extends BallGame with KeyboardEvents {
SpaceshipRampGame()
: super(
color: Colors.blue,
ballPriority: RenderPriority.ballOnSpaceshipRamp,
ballLayer: Layer.spaceshipEntranceRamp,
imagesFileNames: [
Assets.images.spaceship.ramp.railingBackground.keyName,
Assets.images.spaceship.ramp.main.keyName,
Assets.images.spaceship.ramp.boardOpening.keyName,
Assets.images.spaceship.ramp.railingForeground.keyName,
Assets.images.spaceship.ramp.arrow.inactive.keyName,
Assets.images.spaceship.ramp.arrow.active1.keyName,
Assets.images.spaceship.ramp.arrow.active2.keyName,
Assets.images.spaceship.ramp.arrow.active3.keyName,
Assets.images.spaceship.ramp.arrow.active4.keyName,
Assets.images.spaceship.ramp.arrow.active5.keyName,
],
);
static const info = '''
static const description = '''
Shows how SpaceshipRamp is rendered.
- Activate the "trace" parameter to overlay the body.
@ -29,22 +41,10 @@ class SpaceshipRampGame extends BasicBallGame with KeyboardEvents {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.spaceship.ramp.railingBackground.keyName,
Assets.images.spaceship.ramp.main.keyName,
Assets.images.spaceship.ramp.boardOpening.keyName,
Assets.images.spaceship.ramp.railingForeground.keyName,
Assets.images.spaceship.ramp.arrow.inactive.keyName,
Assets.images.spaceship.ramp.arrow.active1.keyName,
Assets.images.spaceship.ramp.arrow.active2.keyName,
Assets.images.spaceship.ramp.arrow.active3.keyName,
Assets.images.spaceship.ramp.arrow.active4.keyName,
Assets.images.spaceship.ramp.arrow.active5.keyName,
]);
_spaceshipRamp = SpaceshipRamp();
await addFromBlueprint(_spaceshipRamp);
camera.followVector2(Vector2(-12, -50));
await addFromBlueprint(
_spaceshipRamp = SpaceshipRamp(),
);
await traceAllBodies();
}

@ -1,25 +1,36 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/alien_zone/alien_bumper_a_game.dart';
import 'package:sandbox/stories/alien_zone/alien_bumper_b_game.dart';
import 'package:sandbox/stories/alien_zone/spaceship_game.dart';
import 'package:sandbox/stories/alien_zone/spaceship_rail_game.dart';
import 'package:sandbox/stories/alien_zone/spaceship_ramp_game.dart';
void addAlienZoneStories(Dashbook dashbook) {
dashbook.storiesOf('Alien Zone')
..add(
'Alien Bumper A',
(context) => GameWidget(
game: AlienBumperAGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('alien_zone/alien_bumper_a.dart'),
info: AlienBumperAGame.info,
..addGame(
title: 'Alien Bumper A',
description: AlienBumperAGame.description,
gameBuilder: (_) => AlienBumperAGame(),
)
..add(
'Alien Bumper B',
(context) => GameWidget(
game: AlienBumperBGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('alien_zone/alien_bumper_b.dart'),
info: AlienBumperAGame.info,
..addGame(
title: 'Alien Bumper B',
description: AlienBumperBGame.description,
gameBuilder: (_) => AlienBumperBGame(),
)
..addGame(
title: 'Spaceship',
description: SpaceshipGame.description,
gameBuilder: (_) => SpaceshipGame(),
)
..addGame(
title: 'Spaceship Rail',
description: SpaceshipRailGame.description,
gameBuilder: (_) => SpaceshipRailGame(),
)
..addGame(
title: 'Spaceship Ramp',
description: SpaceshipRampGame.description,
gameBuilder: (_) => SpaceshipRampGame(),
);
}

@ -1,21 +1,22 @@
import 'package:flame/components.dart';
import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart' hide Assets;
import 'package:pinball_theme/pinball_theme.dart';
import 'package:sandbox/common/common.dart';
class BackboardGameOverGame extends BasicKeyboardGame {
BackboardGameOverGame(this.score, this.character);
class BackboardGameOverGame extends AssetsGame
with HasKeyboardHandlerComponents {
BackboardGameOverGame(this.score, this.character)
: super(
imagesFileNames: characterIconPaths.values.toList(),
);
static const info = '''
Simple example showing the game over mode of the backboard.
static const description = '''
Shows how the Backboard in game over mode is rendered.
- Select a character to update the character icon.
''';
final int score;
final String character;
static final characterIconPaths = <String, String>{
'Dash': Assets.images.dash.leaderboardIcon.keyName,
'Sparky': Assets.images.sparky.leaderboardIcon.keyName,
@ -23,14 +24,16 @@ class BackboardGameOverGame extends BasicKeyboardGame {
'Dino': Assets.images.dino.leaderboardIcon.keyName,
};
final int score;
final String character;
@override
Future<void> onLoad() async {
camera
..followVector2(Vector2.zero())
..zoom = 5;
await images.loadAll(characterIconPaths.values.toList());
await add(
Backboard.gameOver(
position: Vector2(0, 20),

@ -2,9 +2,14 @@ import 'package:flame/components.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class BackboardWaitingGame extends BasicGame {
static const info = '''
Simple example showing the waiting mode of the backboard.
class BackboardWaitingGame extends AssetsGame {
BackboardWaitingGame()
: super(
imagesFileNames: [],
);
static const description = '''
Shows how the Backboard in waiting mode is rendered.
''';
@override
@ -13,7 +18,8 @@ class BackboardWaitingGame extends BasicGame {
..followVector2(Vector2.zero())
..zoom = 5;
final backboard = Backboard.waiting(position: Vector2(0, 20));
await add(backboard);
await add(
Backboard.waiting(position: Vector2(0, 20)),
);
}
}

@ -1,32 +1,25 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/backboard/backboard_game_over_game.dart';
import 'package:sandbox/stories/backboard/backboard_waiting_game.dart';
void addBackboardStories(Dashbook dashbook) {
dashbook.storiesOf('Backboard')
..add(
'Waiting mode',
(context) => GameWidget(
game: BackboardWaitingGame(),
),
codeLink: buildSourceLink('backboard/waiting.dart'),
info: BackboardWaitingGame.info,
..addGame(
title: 'Waiting',
description: BackboardWaitingGame.description,
gameBuilder: (_) => BackboardWaitingGame(),
)
..add(
'Game over',
(context) => GameWidget(
game: BackboardGameOverGame(
context.numberProperty('Score', 9000000000).toInt(),
context.listProperty(
'Character',
BackboardGameOverGame.characterIconPaths.keys.first,
BackboardGameOverGame.characterIconPaths.keys.toList(),
),
..addGame(
title: 'Game over',
description: BackboardGameOverGame.description,
gameBuilder: (context) => BackboardGameOverGame(
context.numberProperty('Score', 9000000000).toInt(),
context.listProperty(
'Character',
BackboardGameOverGame.characterIconPaths.keys.first,
BackboardGameOverGame.characterIconPaths.keys.toList(),
),
),
codeLink: buildSourceLink('backboard/game_over.dart'),
info: BackboardGameOverGame.info,
);
}

@ -4,7 +4,7 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class BallBoosterGame extends LineGame {
static const info = '''
static const description = '''
Shows how a Ball with a boost works.
- Drag to launch a boosted Ball.

@ -3,14 +3,20 @@ import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class BasicBallGame extends BasicGame with TapDetector, Traceable {
BasicBallGame({
class BallGame extends AssetsGame with TapDetector, Traceable {
BallGame({
this.color = Colors.blue,
this.ballPriority = 0,
this.ballLayer = Layer.all,
});
List<String>? imagesFileNames,
}) : super(
imagesFileNames: [
Assets.images.ball.ball.keyName,
if (imagesFileNames != null) ...imagesFileNames,
],
);
static const info = '''
static const description = '''
Shows how a Ball works.
- Tap anywhere on the screen to spawn a ball into the game.

@ -1,5 +1,4 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/ball_booster_game.dart';
@ -7,22 +6,16 @@ import 'package:sandbox/stories/ball/basic_ball_game.dart';
void addBallStories(Dashbook dashbook) {
dashbook.storiesOf('Ball')
..add(
'Basic',
(context) => GameWidget(
game: BasicBallGame(
color: context.colorProperty('color', Colors.blue),
)..trace = context.boolProperty('Trace', true),
..addGame(
title: 'Colored',
description: BallGame.description,
gameBuilder: (context) => BallGame(
color: context.colorProperty('color', Colors.blue),
),
codeLink: buildSourceLink('ball/basic.dart'),
info: BasicBallGame.info,
)
..add(
'Booster',
(context) => GameWidget(
game: BallBoosterGame(),
),
codeLink: buildSourceLink('ball/ball_booster.dart'),
info: BallBoosterGame.info,
..addGame(
title: 'Booster',
description: BallBoosterGame.description,
gameBuilder: (context) => BallBoosterGame(),
);
}

@ -1,10 +1,17 @@
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class BaseboardGame extends BasicBallGame with Traceable {
static const info = '''
class BaseboardGame extends BallGame {
BaseboardGame()
: super(
imagesFileNames: [
Assets.images.baseboard.left.keyName,
Assets.images.baseboard.right.keyName,
],
);
static const description = '''
Shows how the Baseboards are rendered.
- Activate the "trace" parameter to overlay the body.
@ -15,22 +22,14 @@ class BaseboardGame extends BasicBallGame with Traceable {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.baseboard.left.keyName,
Assets.images.baseboard.right.keyName,
]);
final center = screenToWorld(camera.viewport.canvasSize! / 2);
final leftBaseboard = Baseboard(side: BoardSide.left)
..initialPosition = center - Vector2(25, 0)
..priority = 1;
final rightBaseboard = Baseboard(side: BoardSide.right)
..initialPosition = center + Vector2(25, 0)
..priority = 1;
await addAll([
leftBaseboard,
rightBaseboard,
Baseboard(side: BoardSide.left)
..initialPosition = center - Vector2(25, 0)
..priority = 1,
Baseboard(side: BoardSide.right)
..initialPosition = center + Vector2(25, 0)
..priority = 1,
]);
await traceAllBodies();

@ -1,15 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/baseboard/baseboard_game.dart';
void addBaseboardStories(Dashbook dashbook) {
dashbook.storiesOf('Baseboard').add(
'Basic',
(context) => GameWidget(
game: BaseboardGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('baseboard_game/basic.dart'),
info: BaseboardGame.info,
dashbook.storiesOf('Baseboard').addGame(
title: 'Traced',
description: BaseboardGame.description,
gameBuilder: (_) => BaseboardGame(),
);
}

@ -1,11 +1,19 @@
import 'package:flame/extensions.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class BoundariesGame extends BasicBallGame with Traceable {
static const info = '''
class BoundariesGame extends BallGame {
BoundariesGame()
: super(
imagesFileNames: [
Assets.images.boundary.outer.keyName,
Assets.images.boundary.outerBottom.keyName,
Assets.images.boundary.bottom.keyName,
],
);
static const description = '''
Shows how Boundaries are rendered.
- Activate the "trace" parameter to overlay the body.
@ -16,18 +24,11 @@ class BoundariesGame extends BasicBallGame with Traceable {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.boundary.outer.keyName,
Assets.images.boundary.bottom.keyName,
]);
await addFromBlueprint(Boundaries());
await ready();
camera
..followVector2(Vector2.zero())
..zoom = 6;
await addFromBlueprint(Boundaries());
await ready();
await traceAllBodies();
}
}

@ -1,15 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/boundaries/boundaries_game.dart';
void addBoundariesStories(Dashbook dashbook) {
dashbook.storiesOf('Boundaries').add(
'Basic',
(context) => GameWidget(
game: BoundariesGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('boundaries_game/basic.dart'),
info: BoundariesGame.info,
dashbook.storiesOf('Boundaries').addGame(
title: 'Traced',
description: BoundariesGame.description,
gameBuilder: (_) => BoundariesGame(),
);
}

@ -2,7 +2,7 @@ import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
class ChromeDinoGame extends Forge2DGame {
static const info = 'Shows how a ChromeDino is rendered.';
static const description = 'Shows how a ChromeDino is rendered.';
@override
Future<void> onLoad() async {

@ -1,15 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/chrome_dino/chrome_dino_game.dart';
void addChromeDinoStories(Dashbook dashbook) {
dashbook.storiesOf('Chrome Dino').add(
'Basic',
(context) => GameWidget(
game: ChromeDinoGame(),
),
codeLink: buildSourceLink('chrome_dino/basic.dart'),
info: ChromeDinoGame.info,
dashbook.storiesOf('Chrome Dino').addGame(
title: 'Trace',
description: ChromeDinoGame.description,
gameBuilder: (_) => ChromeDinoGame(),
);
}

@ -0,0 +1,31 @@
import 'dart:async';
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class DinoWallGame extends BallGame {
DinoWallGame() : super();
static const description = '''
Shows how DinoWalls are 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 {
await super.onLoad();
await images.loadAll([
Assets.images.dino.dinoLandTop.keyName,
Assets.images.dino.dinoLandBottom.keyName,
]);
await addFromBlueprint(DinoWalls());
camera.followVector2(Vector2.zero());
await traceAllBodies();
}
}

@ -0,0 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/dino_wall/dino_wall_game.dart';
void addDinoWallStories(Dashbook dashbook) {
dashbook.storiesOf('DinoWall').addGame(
title: 'Traced',
description: DinoWallGame.description,
gameBuilder: (_) => DinoWallGame(),
);
}

@ -3,8 +3,8 @@ import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class BasicCameraZoomGame extends BasicGame with TapDetector {
static const info = '''
class CameraZoomGame extends AssetsGame with TapDetector {
static const description = '''
Shows how CameraZoom can be used.
- Tap to zoom in/out.

@ -3,7 +3,7 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
class FireEffectGame extends LineGame {
static const info = '''
static const description = '''
Shows how the FireEffect renders.
- Drag a line to define the trail direction.

@ -1,13 +1,18 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/effects/camera_zoom_game.dart';
import 'package:sandbox/stories/effects/fire_effect_game.dart';
void addEffectsStories(Dashbook dashbook) {
dashbook.storiesOf('Effects').add(
'Fire Effect',
(context) => GameWidget(game: FireEffectGame()),
codeLink: buildSourceLink('effects/fire_effect.dart'),
info: FireEffectGame.info,
);
dashbook.storiesOf('Effects')
..addGame(
title: 'Fire',
description: FireEffectGame.description,
gameBuilder: (_) => FireEffectGame(),
)
..addGame(
title: 'CameraZoom',
description: CameraZoomGame.description,
gameBuilder: (_) => CameraZoomGame(),
);
}

@ -2,12 +2,19 @@ import 'package:flame/input.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class FlipperGame extends BasicBallGame with KeyboardEvents, Traceable {
static const info = '''
class FlipperGame extends BallGame with KeyboardEvents {
FlipperGame()
: super(
imagesFileNames: [
Assets.images.flipper.left.keyName,
Assets.images.flipper.right.keyName,
],
);
static const description = '''
Shows how Flippers are rendered.
- Activate the "trace" parameter to overlay the body.
@ -33,21 +40,12 @@ class FlipperGame extends BasicBallGame with KeyboardEvents, Traceable {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.flipper.left.keyName,
Assets.images.flipper.right.keyName,
]);
final center = screenToWorld(camera.viewport.canvasSize! / 2);
leftFlipper = Flipper(side: BoardSide.left)
..initialPosition = center - Vector2(Flipper.size.x, 0);
rightFlipper = Flipper(side: BoardSide.right)
..initialPosition = center + Vector2(Flipper.size.x, 0);
await addAll([
leftFlipper,
rightFlipper,
leftFlipper = Flipper(side: BoardSide.left)
..initialPosition = center - Vector2(Flipper.size.x, 0),
rightFlipper = Flipper(side: BoardSide.right)
..initialPosition = center + Vector2(Flipper.size.x, 0),
]);
await traceAllBodies();

@ -1,15 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/flipper/flipper_game.dart';
void addFlipperStories(Dashbook dashbook) {
dashbook.storiesOf('Flipper').add(
'Basic',
(context) => GameWidget(
game: FlipperGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('flipper/basic.dart'),
info: FlipperGame.info,
dashbook.storiesOf('Flipper').addGame(
title: 'Traced',
description: FlipperGame.description,
gameBuilder: (_) => FlipperGame(),
);
}

@ -2,11 +2,18 @@ import 'dart:async';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class BigDashNestBumperGame extends BasicBallGame with Traceable {
static const info = '''
class BigDashNestBumperGame extends BallGame {
BigDashNestBumperGame()
: super(
imagesFileNames: [
Assets.images.dash.bumper.main.active.keyName,
Assets.images.dash.bumper.main.inactive.keyName,
],
);
static const description = '''
Shows how a BigDashNestBumper is rendered.
- Activate the "trace" parameter to overlay the body.
@ -16,13 +23,10 @@ class BigDashNestBumperGame extends BasicBallGame with Traceable {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.dash.bumper.main.active.keyName,
Assets.images.dash.bumper.main.inactive.keyName,
]);
camera.followVector2(Vector2.zero());
await add(DashNestBumper.main()..priority = 1);
await add(
DashNestBumper.main()..priority = 1,
);
await traceAllBodies();
}
}

@ -2,11 +2,20 @@ import 'dart:async';
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SignpostGame extends BasicBallGame with Traceable, TapDetector {
static const info = '''
class SignpostGame extends BallGame {
SignpostGame()
: super(
imagesFileNames: [
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
],
);
static const description = '''
Shows how a Signpost is rendered.
- Activate the "trace" parameter to overlay the body.
@ -17,13 +26,6 @@ class SignpostGame extends BasicBallGame with Traceable, TapDetector {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.signpost.inactive.keyName,
Assets.images.signpost.active1.keyName,
Assets.images.signpost.active2.keyName,
Assets.images.signpost.active3.keyName,
]);
camera.followVector2(Vector2.zero());
await add(Signpost());
await traceAllBodies();

@ -2,11 +2,18 @@ import 'dart:async';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SmallDashNestBumperAGame extends BasicBallGame with Traceable {
static const info = '''
class SmallDashNestBumperAGame extends BallGame {
SmallDashNestBumperAGame()
: super(
imagesFileNames: [
Assets.images.dash.bumper.a.active.keyName,
Assets.images.dash.bumper.a.inactive.keyName,
],
);
static const description = '''
Shows how a SmallDashNestBumper ("a") is rendered.
- Activate the "trace" parameter to overlay the body.
@ -16,11 +23,6 @@ class SmallDashNestBumperAGame extends BasicBallGame with Traceable {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.dash.bumper.a.active.keyName,
Assets.images.dash.bumper.a.inactive.keyName,
]);
camera.followVector2(Vector2.zero());
await add(DashNestBumper.a()..priority = 1);
await traceAllBodies();

@ -2,11 +2,18 @@ import 'dart:async';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class SmallDashNestBumperBGame extends BasicBallGame with Traceable {
static const info = '''
class SmallDashNestBumperBGame extends BallGame {
SmallDashNestBumperBGame()
: super(
imagesFileNames: [
Assets.images.dash.bumper.b.active.keyName,
Assets.images.dash.bumper.b.inactive.keyName,
],
);
static const description = '''
Shows how a SmallDashNestBumper ("b") is rendered.
- Activate the "trace" parameter to overlay the body.
@ -16,11 +23,6 @@ class SmallDashNestBumperBGame extends BasicBallGame with Traceable {
Future<void> onLoad() async {
await super.onLoad();
await images.loadAll([
Assets.images.dash.bumper.b.active.keyName,
Assets.images.dash.bumper.b.inactive.keyName,
]);
camera.followVector2(Vector2.zero());
await add(DashNestBumper.b()..priority = 1);
await traceAllBodies();

@ -1,5 +1,4 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/flutter_forest/big_dash_nest_bumper_game.dart';
import 'package:sandbox/stories/flutter_forest/signpost_game.dart';
@ -8,39 +7,24 @@ import 'package:sandbox/stories/flutter_forest/small_dash_nest_bumper_b_game.dar
void addDashNestBumperStories(Dashbook dashbook) {
dashbook.storiesOf('Flutter Forest')
..add(
'Signpost',
(context) => GameWidget(
game: SignpostGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('flutter_forest/signpost.dart'),
info: SignpostGame.info,
..addGame(
title: 'Signpost',
description: SignpostGame.description,
gameBuilder: (_) => SignpostGame(),
)
..add(
'Big Dash Nest Bumper',
(context) => GameWidget(
game: BigDashNestBumperGame()
..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('flutter_forest/big_dash_nest_bumper.dart'),
info: BigDashNestBumperGame.info,
..addGame(
title: 'Big Dash Nest Bumper',
description: BigDashNestBumperGame.description,
gameBuilder: (_) => BigDashNestBumperGame(),
)
..add(
'Small Dash Nest Bumper A',
(context) => GameWidget(
game: SmallDashNestBumperAGame()
..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('flutter_forest/small_dash_nest_bumper_a.dart'),
info: SmallDashNestBumperAGame.info,
..addGame(
title: 'Small Dash Nest Bumper A',
description: SmallDashNestBumperAGame.description,
gameBuilder: (_) => SmallDashNestBumperAGame(),
)
..add(
'Small Dash Nest Bumper B',
(context) => GameWidget(
game: SmallDashNestBumperBGame()
..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('flutter_forest/small_dash_nest_bumper_b.dart'),
info: SmallDashNestBumperBGame.info,
..addGame(
title: 'Small Dash Nest Bumper B',
description: SmallDashNestBumperBGame.description,
gameBuilder: (_) => SmallDashNestBumperBGame(),
);
}

@ -5,10 +5,10 @@ import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class GoogleLetterGame extends BasicBallGame {
class GoogleLetterGame extends BallGame {
GoogleLetterGame() : super(color: const Color(0xFF009900));
static const info = '''
static const description = '''
Shows how a GoogleLetter is rendered.
- Tap anywhere on the screen to spawn a ball into the game.

@ -1,15 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/google_word/google_letter_game.dart';
void addGoogleWordStories(Dashbook dashbook) {
dashbook.storiesOf('Google Word').add(
'Letter',
(context) => GameWidget(
game: GoogleLetterGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('google_word/letter.dart'),
info: GoogleLetterGame.info,
dashbook.storiesOf('Google Word').addGame(
title: 'Letter 0',
description: GoogleLetterGame.description,
gameBuilder: (_) => GoogleLetterGame(),
);
}

@ -1,10 +1,9 @@
import 'package:flame/extensions.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class KickerGame extends BasicBallGame with Traceable {
static const info = '''
class KickerGame extends BallGame {
static const description = '''
Shows how Kickers are rendered.
- Activate the "trace" parameter to overlay the body.
@ -16,14 +15,14 @@ class KickerGame extends BasicBallGame with Traceable {
await super.onLoad();
final center = screenToWorld(camera.viewport.canvasSize! / 2);
final leftKicker = Kicker(side: BoardSide.left)
..initialPosition = Vector2(center.x - (Kicker.size.x * 2), center.y);
await add(leftKicker);
final rightKicker = Kicker(side: BoardSide.right)
..initialPosition = Vector2(center.x + (Kicker.size.x * 2), center.y);
await add(rightKicker);
await addAll(
[
Kicker(side: BoardSide.left)
..initialPosition = Vector2(center.x - (Kicker.size.x * 2), center.y),
Kicker(side: BoardSide.right)
..initialPosition = Vector2(center.x + (Kicker.size.x * 2), center.y),
],
);
await traceAllBodies();
}

@ -1,15 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/kicker/kicker_game.dart';
void addKickerStories(Dashbook dashbook) {
dashbook.storiesOf('Kickers').add(
'Basic',
(context) => GameWidget(
game: KickerGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('kicker_game/basic.dart'),
info: KickerGame.info,
dashbook.storiesOf('Kickers').addGame(
title: 'Traced',
description: KickerGame.description,
gameBuilder: (_) => KickerGame(),
);
}

@ -6,7 +6,7 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class LaunchRampGame extends BasicBallGame {
class LaunchRampGame extends BallGame {
LaunchRampGame()
: super(
color: Colors.blue,
@ -14,7 +14,7 @@ class LaunchRampGame extends BasicBallGame {
ballLayer: Layer.launcher,
);
static const info = '''
static const description = '''
Shows how LaunchRamp are rendered.
- Activate the "trace" parameter to overlay the body.
@ -28,10 +28,8 @@ class LaunchRampGame extends BasicBallGame {
camera
..followVector2(Vector2(0, 0))
..zoom = 7.5;
final launchRamp = LaunchRamp();
unawaited(addFromBlueprint(launchRamp));
await addFromBlueprint(LaunchRamp());
await ready();
await traceAllBodies();
}
}

@ -1,15 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/launch_ramp/launch_ramp_game.dart';
void addLaunchRampStories(Dashbook dashbook) {
dashbook.storiesOf('LaunchRamp').add(
'Basic',
(context) => GameWidget(
game: LaunchRampGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('launch_ramp/basic.dart'),
info: LaunchRampGame.info,
dashbook.storiesOf('LaunchRamp').addGame(
title: 'Traced',
description: LaunchRampGame.description,
gameBuilder: (_) => LaunchRampGame(),
);
}

@ -2,36 +2,35 @@ import 'package:flame/input.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class BasicLayerGame extends BasicGame with TapDetector {
BasicLayerGame({required this.color});
static const info = '''
class LayerGame extends BallGame with TapDetector {
static const description = '''
Shows how Layers work when a Ball hits other components.
- Tap anywhere on the screen to spawn a Ball into the game.
''';
final Color color;
@override
Future<void> onLoad() async {
await add(BigSquare()..initialPosition = Vector2(30, -40));
await add(SmallSquare()..initialPosition = Vector2(50, -40));
await add(UnlayeredSquare()..initialPosition = Vector2(60, -40));
}
@override
void onTapUp(TapUpInfo info) {
add(
Ball(baseColor: color)..initialPosition = info.eventPosition.game,
await addAll(
[
_BigSquare()..initialPosition = Vector2(30, -40),
_SmallSquare()..initialPosition = Vector2(50, -40),
_UnlayeredSquare()..initialPosition = Vector2(60, -40),
],
);
}
}
class BigSquare extends BodyComponent with InitialPosition, Layered {
BigSquare() {
class _BigSquare extends BodyComponent with InitialPosition, Layered {
_BigSquare()
: super(
children: [
_UnlayeredSquare()..initialPosition = Vector2.all(4),
_SmallSquare()..initialPosition = Vector2.all(-4),
],
) {
paint = Paint()
..color = const Color.fromARGB(255, 8, 218, 241)
..style = PaintingStyle.stroke;
@ -42,27 +41,13 @@ class BigSquare extends BodyComponent with InitialPosition, Layered {
Body createBody() {
final shape = PolygonShape()..setAsBoxXY(16, 16);
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef()..position = initialPosition;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
@override
Future<void> onLoad() async {
await super.onLoad();
await addAll(
[
UnlayeredSquare()..initialPosition = Vector2.all(4),
SmallSquare()..initialPosition = Vector2.all(-4),
],
);
}
}
class SmallSquare extends BodyComponent with InitialPosition, Layered {
SmallSquare() {
class _SmallSquare extends BodyComponent with InitialPosition, Layered {
_SmallSquare() {
paint = Paint()
..color = const Color.fromARGB(255, 27, 241, 8)
..style = PaintingStyle.stroke;
@ -73,15 +58,13 @@ class SmallSquare extends BodyComponent with InitialPosition, Layered {
Body createBody() {
final shape = PolygonShape()..setAsBoxXY(2, 2);
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef()..position = initialPosition;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
class UnlayeredSquare extends BodyComponent with InitialPosition {
UnlayeredSquare() {
class _UnlayeredSquare extends BodyComponent with InitialPosition {
_UnlayeredSquare() {
paint = Paint()
..color = const Color.fromARGB(255, 241, 8, 8)
..style = PaintingStyle.stroke;
@ -91,9 +74,7 @@ class UnlayeredSquare extends BodyComponent with InitialPosition {
Body createBody() {
final shape = PolygonShape()..setAsBoxXY(3, 3);
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef()..position = initialPosition;
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}

@ -1,18 +1,11 @@
import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/layer/basic_layer_game.dart';
import 'package:sandbox/stories/layer/layer_game.dart';
void addLayerStories(Dashbook dashbook) {
dashbook.storiesOf('Layer').add(
'Layer',
(context) => GameWidget(
game: BasicLayerGame(
color: context.colorProperty('color', Colors.blue),
),
),
codeLink: buildSourceLink('layer/basic.dart'),
info: BasicLayerGame.info,
dashbook.storiesOf('Layer').addGame(
title: 'Example',
description: LayerGame.description,
gameBuilder: (_) => LayerGame(),
);
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save