Before Width: | Height: | Size: 7.7 KiB |
@ -0,0 +1,131 @@
|
|||||||
|
// ignore_for_file: avoid_renaming_method_parameters
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_bloc/flame_bloc.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball/flame/blueprint.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
/// {@template flutter_forest}
|
||||||
|
/// Area positioned at the top right of the [Board] where the [Ball]
|
||||||
|
/// can bounce off [DashNestBumper]s.
|
||||||
|
///
|
||||||
|
/// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest]
|
||||||
|
/// is awarded, and the [BigDashNestBumper] releases a new [Ball].
|
||||||
|
/// {@endtemplate}
|
||||||
|
// TODO(alestiago): Make a [Blueprint] once nesting [Blueprint] is implemented.
|
||||||
|
class FlutterForest extends Component
|
||||||
|
with HasGameRef<PinballGame>, BlocComponent<GameBloc, GameState> {
|
||||||
|
/// {@macro flutter_forest}
|
||||||
|
FlutterForest({
|
||||||
|
required this.position,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The position of the [FlutterForest] on the [Board].
|
||||||
|
final Vector2 position;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool listenWhen(GameState? previousState, GameState newState) {
|
||||||
|
return (previousState?.bonusHistory.length ?? 0) <
|
||||||
|
newState.bonusHistory.length &&
|
||||||
|
newState.bonusHistory.last == GameBonus.dashNest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onNewState(GameState state) {
|
||||||
|
super.onNewState(state);
|
||||||
|
gameRef.addFromBlueprint(BallBlueprint(position: position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
gameRef.addContactCallback(DashNestBumperBallContactCallback());
|
||||||
|
|
||||||
|
// TODO(alestiago): adjust positioning once sprites are added.
|
||||||
|
final smallLeftNest = SmallDashNestBumper(id: 'small_left_nest')
|
||||||
|
..initialPosition = position + Vector2(-4.8, 2.8);
|
||||||
|
final smallRightNest = SmallDashNestBumper(id: 'small_right_nest')
|
||||||
|
..initialPosition = position + Vector2(0.5, -5.5);
|
||||||
|
final bigNest = BigDashNestBumper(id: 'big_nest')
|
||||||
|
..initialPosition = position;
|
||||||
|
|
||||||
|
await addAll([
|
||||||
|
smallLeftNest,
|
||||||
|
smallRightNest,
|
||||||
|
bigNest,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@template dash_nest_bumper}
|
||||||
|
/// Bumper located in the [FlutterForest].
|
||||||
|
/// {@endtemplate}
|
||||||
|
@visibleForTesting
|
||||||
|
abstract class DashNestBumper extends BodyComponent<PinballGame>
|
||||||
|
with ScorePoints, InitialPosition {
|
||||||
|
/// {@macro dash_nest_bumper}
|
||||||
|
DashNestBumper({required this.id});
|
||||||
|
|
||||||
|
/// Unique identifier for this [DashNestBumper].
|
||||||
|
///
|
||||||
|
/// Used to identify [DashNestBumper]s in [GameState.activatedDashNests].
|
||||||
|
final String id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Listens when a [Ball] bounces bounces against a [DashNestBumper].
|
||||||
|
@visibleForTesting
|
||||||
|
class DashNestBumperBallContactCallback
|
||||||
|
extends ContactCallback<DashNestBumper, Ball> {
|
||||||
|
@override
|
||||||
|
void begin(DashNestBumper dashNestBumper, Ball ball, Contact _) {
|
||||||
|
dashNestBumper.gameRef.read<GameBloc>().add(
|
||||||
|
DashNestActivated(dashNestBumper.id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro dash_nest_bumper}
|
||||||
|
@visibleForTesting
|
||||||
|
class BigDashNestBumper extends DashNestBumper {
|
||||||
|
/// {@macro dash_nest_bumper}
|
||||||
|
BigDashNestBumper({required String id}) : super(id: id);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get points => 20;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = CircleShape()..radius = 2.5;
|
||||||
|
final fixtureDef = FixtureDef(shape);
|
||||||
|
|
||||||
|
final bodyDef = BodyDef()
|
||||||
|
..position = initialPosition
|
||||||
|
..userData = this;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// {@macro dash_nest_bumper}
|
||||||
|
@visibleForTesting
|
||||||
|
class SmallDashNestBumper extends DashNestBumper {
|
||||||
|
/// {@macro dash_nest_bumper}
|
||||||
|
SmallDashNestBumper({required String id}) : super(id: id);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get points => 10;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = CircleShape()..radius = 1;
|
||||||
|
final fixtureDef = FixtureDef(shape);
|
||||||
|
|
||||||
|
final bodyDef = BodyDef()
|
||||||
|
..position = initialPosition
|
||||||
|
..userData = this;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
}
|
@ -1,169 +0,0 @@
|
|||||||
import 'package:flame/extensions.dart';
|
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:geometry/geometry.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
|
|
||||||
/// {@template pathway}
|
|
||||||
/// [Pathway] creates lines of various shapes.
|
|
||||||
///
|
|
||||||
/// [BodyComponent]s such as a Ball can collide and move along a [Pathway].
|
|
||||||
/// {@endtemplate}
|
|
||||||
class Pathway extends BodyComponent with InitialPosition, Layered {
|
|
||||||
Pathway._({
|
|
||||||
// TODO(ruialonso): remove color when assets added.
|
|
||||||
Color? color,
|
|
||||||
required List<List<Vector2>> paths,
|
|
||||||
}) : _paths = paths {
|
|
||||||
paint = Paint()
|
|
||||||
..color = color ?? const Color.fromARGB(0, 0, 0, 0)
|
|
||||||
..style = PaintingStyle.stroke;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a uniform unidirectional (straight) [Pathway].
|
|
||||||
///
|
|
||||||
/// Does so with two [ChainShape] separated by a [width]. Can
|
|
||||||
/// be rotated by a given [rotation] in radians.
|
|
||||||
///
|
|
||||||
/// If [singleWall] is true, just one [ChainShape] is created.
|
|
||||||
factory Pathway.straight({
|
|
||||||
Color? color,
|
|
||||||
required Vector2 start,
|
|
||||||
required Vector2 end,
|
|
||||||
required double width,
|
|
||||||
double rotation = 0,
|
|
||||||
bool singleWall = false,
|
|
||||||
}) {
|
|
||||||
final paths = <List<Vector2>>[];
|
|
||||||
|
|
||||||
// TODO(ruialonso): Refactor repetitive logic
|
|
||||||
final firstWall = [
|
|
||||||
start.clone(),
|
|
||||||
end.clone(),
|
|
||||||
].map((vector) => vector..rotate(rotation)).toList();
|
|
||||||
paths.add(firstWall);
|
|
||||||
|
|
||||||
if (!singleWall) {
|
|
||||||
final secondWall = [
|
|
||||||
start + Vector2(width, 0),
|
|
||||||
end + Vector2(width, 0),
|
|
||||||
].map((vector) => vector..rotate(rotation)).toList();
|
|
||||||
paths.add(secondWall);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pathway._(
|
|
||||||
color: color,
|
|
||||||
paths: paths,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an arc [Pathway].
|
|
||||||
///
|
|
||||||
/// The [angle], in radians, specifies the size of the arc. For example, two
|
|
||||||
/// pi returns a complete circumference.
|
|
||||||
///
|
|
||||||
/// Does so with two [ChainShape] separated by a [width]. Which can be
|
|
||||||
/// rotated by a given [rotation] in radians.
|
|
||||||
///
|
|
||||||
/// The outer radius is specified by [radius], whilst the inner one is
|
|
||||||
/// equivalent to the [radius] minus the [width].
|
|
||||||
///
|
|
||||||
/// If [singleWall] is true, just one [ChainShape] is created.
|
|
||||||
factory Pathway.arc({
|
|
||||||
Color? color,
|
|
||||||
required Vector2 center,
|
|
||||||
required double width,
|
|
||||||
required double radius,
|
|
||||||
required double angle,
|
|
||||||
double rotation = 0,
|
|
||||||
bool singleWall = false,
|
|
||||||
}) {
|
|
||||||
final paths = <List<Vector2>>[];
|
|
||||||
|
|
||||||
// TODO(ruialonso): Refactor repetitive logic
|
|
||||||
final outerWall = calculateArc(
|
|
||||||
center: center,
|
|
||||||
radius: radius,
|
|
||||||
angle: angle,
|
|
||||||
offsetAngle: rotation,
|
|
||||||
);
|
|
||||||
paths.add(outerWall);
|
|
||||||
|
|
||||||
if (!singleWall) {
|
|
||||||
final innerWall = calculateArc(
|
|
||||||
center: center,
|
|
||||||
radius: radius - width,
|
|
||||||
angle: angle,
|
|
||||||
offsetAngle: rotation,
|
|
||||||
);
|
|
||||||
paths.add(innerWall);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pathway._(
|
|
||||||
color: color,
|
|
||||||
paths: paths,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a bezier curve [Pathway].
|
|
||||||
///
|
|
||||||
/// Does so with two [ChainShape] separated by a [width]. Which can be
|
|
||||||
/// rotated by a given [rotation] in radians.
|
|
||||||
///
|
|
||||||
/// First and last [controlPoints] set the beginning and end of the curve,
|
|
||||||
/// inner points between them set its final shape.
|
|
||||||
///
|
|
||||||
/// If [singleWall] is true, just one [ChainShape] is created.
|
|
||||||
factory Pathway.bezierCurve({
|
|
||||||
Color? color,
|
|
||||||
required List<Vector2> controlPoints,
|
|
||||||
required double width,
|
|
||||||
double rotation = 0,
|
|
||||||
bool singleWall = false,
|
|
||||||
}) {
|
|
||||||
final paths = <List<Vector2>>[];
|
|
||||||
|
|
||||||
// TODO(ruialonso): Refactor repetitive logic
|
|
||||||
final firstWall = calculateBezierCurve(controlPoints: controlPoints)
|
|
||||||
.map((vector) => vector..rotate(rotation))
|
|
||||||
.toList();
|
|
||||||
paths.add(firstWall);
|
|
||||||
|
|
||||||
if (!singleWall) {
|
|
||||||
final secondWall = calculateBezierCurve(
|
|
||||||
controlPoints: controlPoints
|
|
||||||
.map((vector) => vector + Vector2(width, -width))
|
|
||||||
.toList(),
|
|
||||||
).map((vector) => vector..rotate(rotation)).toList();
|
|
||||||
paths.add(secondWall);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pathway._(
|
|
||||||
color: color,
|
|
||||||
paths: paths,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<List<Vector2>> _paths;
|
|
||||||
|
|
||||||
/// Constructs different [ChainShape]s to form the [Pathway] shape.
|
|
||||||
List<FixtureDef> createFixtureDefs() {
|
|
||||||
final fixturesDef = <FixtureDef>[];
|
|
||||||
|
|
||||||
for (final path in _paths) {
|
|
||||||
final chain = ChainShape()..createChain(path);
|
|
||||||
fixturesDef.add(FixtureDef(chain));
|
|
||||||
}
|
|
||||||
|
|
||||||
return fixturesDef;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Body createBody() {
|
|
||||||
final bodyDef = BodyDef()..position = initialPosition;
|
|
||||||
final body = world.createBody(bodyDef);
|
|
||||||
createFixtureDefs().forEach(body.createFixture);
|
|
||||||
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
|
|
||||||
/// {@template round_bumper}
|
|
||||||
/// Circular body that repels a [Ball] on contact, increasing the score.
|
|
||||||
/// {@endtemplate}
|
|
||||||
class RoundBumper extends BodyComponent with ScorePoints, InitialPosition {
|
|
||||||
/// {@macro round_bumper}
|
|
||||||
RoundBumper({
|
|
||||||
required double radius,
|
|
||||||
required int points,
|
|
||||||
}) : _radius = radius,
|
|
||||||
_points = points;
|
|
||||||
|
|
||||||
/// The radius of the [RoundBumper].
|
|
||||||
final double _radius;
|
|
||||||
|
|
||||||
/// Points awarded from hitting this [RoundBumper].
|
|
||||||
final int _points;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get points => _points;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Body createBody() {
|
|
||||||
final shape = CircleShape()..radius = _radius;
|
|
||||||
|
|
||||||
final fixtureDef = FixtureDef(shape)..restitution = 1;
|
|
||||||
|
|
||||||
final bodyDef = BodyDef()..position = initialPosition;
|
|
||||||
|
|
||||||
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +1,3 @@
|
|||||||
export 'bloc/leaderboard_bloc.dart';
|
export 'bloc/leaderboard_bloc.dart';
|
||||||
export 'models/leader_board_entry.dart';
|
export 'models/leader_board_entry.dart';
|
||||||
|
export 'view/leaderboard_page.dart';
|
||||||
|
@ -0,0 +1,306 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
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_theme/pinball_theme.dart';
|
||||||
|
|
||||||
|
class LeaderboardPage extends StatelessWidget {
|
||||||
|
const LeaderboardPage({Key? key, required this.theme}) : super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme theme;
|
||||||
|
|
||||||
|
static Route route({required CharacterTheme theme}) {
|
||||||
|
return MaterialPageRoute<void>(
|
||||||
|
builder: (_) => LeaderboardPage(theme: theme),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => LeaderboardBloc(
|
||||||
|
context.read<LeaderboardRepository>(),
|
||||||
|
)..add(const Top10Fetched()),
|
||||||
|
child: LeaderboardView(theme: theme),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LeaderboardView extends StatelessWidget {
|
||||||
|
const LeaderboardView({Key? key, required this.theme}) : super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme theme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 80),
|
||||||
|
Text(
|
||||||
|
l10n.leaderboard,
|
||||||
|
style: Theme.of(context).textTheme.headline3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 80),
|
||||||
|
BlocBuilder<LeaderboardBloc, LeaderboardState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
switch (state.status) {
|
||||||
|
case LeaderboardStatus.loading:
|
||||||
|
return _LeaderboardLoading(theme: theme);
|
||||||
|
case LeaderboardStatus.success:
|
||||||
|
return _LeaderboardRanking(
|
||||||
|
ranking: state.leaderboard,
|
||||||
|
theme: theme,
|
||||||
|
);
|
||||||
|
case LeaderboardStatus.error:
|
||||||
|
return _LeaderboardError(theme: theme);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).push<void>(
|
||||||
|
CharacterSelectionPage.route(),
|
||||||
|
),
|
||||||
|
child: Text(l10n.retry),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderboardLoading extends StatelessWidget {
|
||||||
|
const _LeaderboardLoading({Key? key, required this.theme}) : super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme theme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderboardError extends StatelessWidget {
|
||||||
|
const _LeaderboardError({Key? key, required this.theme}) : super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme theme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Text(
|
||||||
|
'There was en error loading data!',
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.headline6?.copyWith(color: Colors.red),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderboardRanking extends StatelessWidget {
|
||||||
|
const _LeaderboardRanking({
|
||||||
|
Key? key,
|
||||||
|
required this.ranking,
|
||||||
|
required this.theme,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final List<LeaderboardEntry> ranking;
|
||||||
|
final CharacterTheme theme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_LeaderboardHeaders(theme: theme),
|
||||||
|
_LeaderboardList(
|
||||||
|
ranking: ranking,
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderboardHeaders extends StatelessWidget {
|
||||||
|
const _LeaderboardHeaders({Key? key, required this.theme}) : super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme theme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_LeaderboardHeaderItem(title: l10n.rank, theme: theme),
|
||||||
|
_LeaderboardHeaderItem(title: l10n.character, theme: theme),
|
||||||
|
_LeaderboardHeaderItem(title: l10n.username, theme: theme),
|
||||||
|
_LeaderboardHeaderItem(title: l10n.score, theme: theme),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderboardHeaderItem extends StatelessWidget {
|
||||||
|
const _LeaderboardHeaderItem({
|
||||||
|
Key? key,
|
||||||
|
required this.title,
|
||||||
|
required this.theme,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme theme;
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.ballColor,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.headline5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderboardList extends StatelessWidget {
|
||||||
|
const _LeaderboardList({
|
||||||
|
Key? key,
|
||||||
|
required this.ranking,
|
||||||
|
required this.theme,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final List<LeaderboardEntry> ranking;
|
||||||
|
final CharacterTheme theme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemBuilder: (_, index) => _LeaderBoardCompetitor(
|
||||||
|
entry: ranking[index],
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
|
itemCount: ranking.length,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderBoardCompetitor extends StatelessWidget {
|
||||||
|
const _LeaderBoardCompetitor({
|
||||||
|
Key? key,
|
||||||
|
required this.entry,
|
||||||
|
required this.theme,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme theme;
|
||||||
|
|
||||||
|
final LeaderboardEntry entry;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_LeaderboardCompetitorField(
|
||||||
|
text: entry.rank,
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
|
_LeaderboardCompetitorCharacter(
|
||||||
|
characterAsset: entry.character,
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
|
_LeaderboardCompetitorField(
|
||||||
|
text: entry.playerInitials,
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
|
_LeaderboardCompetitorField(
|
||||||
|
text: entry.score.toString(),
|
||||||
|
theme: theme,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderboardCompetitorField extends StatelessWidget {
|
||||||
|
const _LeaderboardCompetitorField({
|
||||||
|
Key? key,
|
||||||
|
required this.text,
|
||||||
|
required this.theme,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme theme;
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: theme.ballColor,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Text(text),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LeaderboardCompetitorCharacter extends StatelessWidget {
|
||||||
|
const _LeaderboardCompetitorCharacter({
|
||||||
|
Key? key,
|
||||||
|
required this.characterAsset,
|
||||||
|
required this.theme,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final CharacterTheme theme;
|
||||||
|
final AssetGenImage characterAsset;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Expanded(
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: theme.ballColor,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 30,
|
||||||
|
child: characterAsset.image(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,4 @@
|
|||||||
include: package:very_good_analysis/analysis_options.2.4.0.yaml
|
include: package:very_good_analysis/analysis_options.2.4.0.yaml
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
- lib/**/*.gen.dart
|
||||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@ -0,0 +1,68 @@
|
|||||||
|
/// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
/// *****************************************************
|
||||||
|
/// FlutterGen
|
||||||
|
/// *****************************************************
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class $AssetsImagesGen {
|
||||||
|
const $AssetsImagesGen();
|
||||||
|
|
||||||
|
AssetGenImage get ball => const AssetGenImage('assets/images/ball.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
class Assets {
|
||||||
|
Assets._();
|
||||||
|
|
||||||
|
static const $AssetsImagesGen images = $AssetsImagesGen();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssetGenImage extends AssetImage {
|
||||||
|
const AssetGenImage(String assetName)
|
||||||
|
: super(assetName, package: 'pinball_components');
|
||||||
|
|
||||||
|
Image image({
|
||||||
|
Key? key,
|
||||||
|
ImageFrameBuilder? frameBuilder,
|
||||||
|
ImageLoadingBuilder? loadingBuilder,
|
||||||
|
ImageErrorWidgetBuilder? errorBuilder,
|
||||||
|
String? semanticLabel,
|
||||||
|
bool excludeFromSemantics = false,
|
||||||
|
double? width,
|
||||||
|
double? height,
|
||||||
|
Color? color,
|
||||||
|
BlendMode? colorBlendMode,
|
||||||
|
BoxFit? fit,
|
||||||
|
AlignmentGeometry alignment = Alignment.center,
|
||||||
|
ImageRepeat repeat = ImageRepeat.noRepeat,
|
||||||
|
Rect? centerSlice,
|
||||||
|
bool matchTextDirection = false,
|
||||||
|
bool gaplessPlayback = false,
|
||||||
|
bool isAntiAlias = false,
|
||||||
|
FilterQuality filterQuality = FilterQuality.low,
|
||||||
|
}) {
|
||||||
|
return Image(
|
||||||
|
key: key,
|
||||||
|
image: this,
|
||||||
|
frameBuilder: frameBuilder,
|
||||||
|
loadingBuilder: loadingBuilder,
|
||||||
|
errorBuilder: errorBuilder,
|
||||||
|
semanticLabel: semanticLabel,
|
||||||
|
excludeFromSemantics: excludeFromSemantics,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
color: color,
|
||||||
|
colorBlendMode: colorBlendMode,
|
||||||
|
fit: fit,
|
||||||
|
alignment: alignment,
|
||||||
|
repeat: repeat,
|
||||||
|
centerSlice: centerSlice,
|
||||||
|
matchTextDirection: matchTextDirection,
|
||||||
|
gaplessPlayback: gaplessPlayback,
|
||||||
|
isAntiAlias: isAntiAlias,
|
||||||
|
filterQuality: filterQuality,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get path => assetName;
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
library pinball_components;
|
library pinball_components;
|
||||||
|
|
||||||
|
export 'gen/assets.gen.dart';
|
||||||
export 'src/pinball_components.dart';
|
export 'src/pinball_components.dart';
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
/// {@template ball}
|
||||||
|
/// A solid, [BodyType.dynamic] sphere that rolls and bounces around
|
||||||
|
/// {@endtemplate}
|
||||||
|
class Ball<T extends Forge2DGame> extends BodyComponent<T>
|
||||||
|
with Layered, InitialPosition {
|
||||||
|
/// {@macro ball_body}
|
||||||
|
Ball({
|
||||||
|
required this.baseColor,
|
||||||
|
}) {
|
||||||
|
// TODO(ruimiguel): while developing Ball can be launched by clicking mouse,
|
||||||
|
// and default layer is Layer.all. But on final game Ball will be always be
|
||||||
|
// be launched from Plunger and LauncherRamp will modify it to Layer.board.
|
||||||
|
// We need to see what happens if Ball appears from other place like nest
|
||||||
|
// bumper, it will need to explicit change layer to Layer.board then.
|
||||||
|
layer = Layer.board;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The size of the [Ball]
|
||||||
|
final Vector2 size = Vector2.all(3);
|
||||||
|
|
||||||
|
/// The base [Color] used to tint this [Ball]
|
||||||
|
final Color baseColor;
|
||||||
|
|
||||||
|
double _boostTimer = 0;
|
||||||
|
static const _boostDuration = 2.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
final sprite = await gameRef.loadSprite(Assets.images.ball.keyName);
|
||||||
|
final tint = baseColor.withOpacity(0.5);
|
||||||
|
await add(
|
||||||
|
SpriteComponent(
|
||||||
|
sprite: sprite,
|
||||||
|
size: size,
|
||||||
|
anchor: Anchor.center,
|
||||||
|
)..tint(tint),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = CircleShape()..radius = size.x / 2;
|
||||||
|
|
||||||
|
final fixtureDef = FixtureDef(shape)..density = 1;
|
||||||
|
|
||||||
|
final bodyDef = BodyDef()
|
||||||
|
..position = initialPosition
|
||||||
|
..userData = this
|
||||||
|
..type = BodyType.dynamic;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Immediatly and completly [stop]s the ball.
|
||||||
|
///
|
||||||
|
/// The [Ball] will no longer be affected by any forces, including it's
|
||||||
|
/// weight and those emitted from collisions.
|
||||||
|
void stop() {
|
||||||
|
body.setType(BodyType.static);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allows the [Ball] to be affected by forces.
|
||||||
|
///
|
||||||
|
/// If previously [stop]ed, the previous ball's velocity is not kept.
|
||||||
|
void resume() {
|
||||||
|
body.setType(BodyType.dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
if (_boostTimer > 0) {
|
||||||
|
_boostTimer -= dt;
|
||||||
|
final direction = body.linearVelocity.normalized();
|
||||||
|
final effect = FireEffect(
|
||||||
|
burstPower: _boostTimer,
|
||||||
|
direction: direction,
|
||||||
|
position: body.position,
|
||||||
|
);
|
||||||
|
|
||||||
|
unawaited(gameRef.add(effect));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies a boost on this [Ball]
|
||||||
|
void boost(Vector2 impulse) {
|
||||||
|
body.applyLinearImpulse(impulse);
|
||||||
|
_boostTimer = _boostDuration;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export 'ball.dart';
|
||||||
|
export 'fire_effect.dart';
|
||||||
|
export 'initial_position.dart';
|
||||||
|
export 'layer.dart';
|
||||||
|
export 'shapes/shapes.dart';
|
@ -0,0 +1,113 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame/particles.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart' hide Particle;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
const _particleRadius = 0.25;
|
||||||
|
|
||||||
|
// TODO(erickzanardo): This component could just be a ParticleComponet,
|
||||||
|
/// unfortunately there is a Particle Component is not a PositionComponent,
|
||||||
|
/// which makes it hard to be used since we have camera transformations and on
|
||||||
|
// top of that, PositionComponent has a bug inside forge 2d games
|
||||||
|
///
|
||||||
|
/// https://github.com/flame-engine/flame/issues/1484
|
||||||
|
/// https://github.com/flame-engine/flame/issues/1484
|
||||||
|
|
||||||
|
/// {@template fire_effect}
|
||||||
|
/// A [BodyComponent] which creates a fire trail effect using the given
|
||||||
|
/// parameters
|
||||||
|
/// {@endtemplate}
|
||||||
|
class FireEffect extends BodyComponent {
|
||||||
|
/// {@macro fire_effect}
|
||||||
|
FireEffect({
|
||||||
|
required this.burstPower,
|
||||||
|
required this.position,
|
||||||
|
required this.direction,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// A [double] value that will define how "strong" the burst of particles
|
||||||
|
/// will be
|
||||||
|
final double burstPower;
|
||||||
|
|
||||||
|
/// The position of the burst
|
||||||
|
final Vector2 position;
|
||||||
|
|
||||||
|
/// Which direction the burst will aim
|
||||||
|
final Vector2 direction;
|
||||||
|
late Particle _particle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final bodyDef = BodyDef()..position = position;
|
||||||
|
|
||||||
|
final fixtureDef = FixtureDef(CircleShape()..radius = 0)..isSensor = true;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
final children = [
|
||||||
|
...List.generate(4, (index) {
|
||||||
|
return CircleParticle(
|
||||||
|
radius: _particleRadius,
|
||||||
|
paint: Paint()..color = Colors.yellow.darken((index + 1) / 4),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
...List.generate(4, (index) {
|
||||||
|
return CircleParticle(
|
||||||
|
radius: _particleRadius,
|
||||||
|
paint: Paint()..color = Colors.red.darken((index + 1) / 4),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
...List.generate(4, (index) {
|
||||||
|
return CircleParticle(
|
||||||
|
radius: _particleRadius,
|
||||||
|
paint: Paint()..color = Colors.orange.darken((index + 1) / 4),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
final rng = math.Random();
|
||||||
|
final spreadTween = Tween<double>(begin: -0.2, end: 0.2);
|
||||||
|
|
||||||
|
_particle = Particle.generate(
|
||||||
|
count: (rng.nextDouble() * (burstPower * 10)).toInt(),
|
||||||
|
generator: (_) {
|
||||||
|
final spread = Vector2(
|
||||||
|
spreadTween.transform(rng.nextDouble()),
|
||||||
|
spreadTween.transform(rng.nextDouble()),
|
||||||
|
);
|
||||||
|
final finalDirection = Vector2(direction.x, -direction.y) + spread;
|
||||||
|
final speed = finalDirection * (burstPower * 20);
|
||||||
|
|
||||||
|
return AcceleratedParticle(
|
||||||
|
lifespan: 5 / burstPower,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
speed: speed,
|
||||||
|
child: children[rng.nextInt(children.length)],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
_particle.update(dt);
|
||||||
|
|
||||||
|
if (_particle.shouldRemove) {
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
super.render(canvas);
|
||||||
|
|
||||||
|
_particle.render(canvas);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:geometry/geometry.dart';
|
||||||
|
|
||||||
|
/// {@template arc_shape}
|
||||||
|
/// Creates an arc.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class ArcShape extends ChainShape {
|
||||||
|
/// {@macro arc_shape}
|
||||||
|
ArcShape({
|
||||||
|
required this.center,
|
||||||
|
required this.arcRadius,
|
||||||
|
required this.angle,
|
||||||
|
this.rotation = 0,
|
||||||
|
}) {
|
||||||
|
createChain(
|
||||||
|
calculateArc(
|
||||||
|
center: center,
|
||||||
|
radius: arcRadius,
|
||||||
|
angle: angle,
|
||||||
|
offsetAngle: rotation,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The center of the arc.
|
||||||
|
final Vector2 center;
|
||||||
|
|
||||||
|
/// The radius of the arc.
|
||||||
|
// TODO(alestiago): Check if modifying the parent radius makes sense.
|
||||||
|
final double arcRadius;
|
||||||
|
|
||||||
|
/// Specifies the size of the arc, in radians.
|
||||||
|
///
|
||||||
|
/// For example, two pi returns a complete circumference.
|
||||||
|
final double angle;
|
||||||
|
|
||||||
|
/// Angle in radians to rotate the arc around its [center].
|
||||||
|
final double rotation;
|
||||||
|
|
||||||
|
ArcShape copyWith({
|
||||||
|
Vector2? center,
|
||||||
|
double? arcRadius,
|
||||||
|
double? angle,
|
||||||
|
double? rotation,
|
||||||
|
}) =>
|
||||||
|
ArcShape(
|
||||||
|
center: center ?? this.center,
|
||||||
|
arcRadius: arcRadius ?? this.arcRadius,
|
||||||
|
angle: angle ?? this.angle,
|
||||||
|
rotation: rotation ?? this.rotation,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:geometry/geometry.dart';
|
||||||
|
|
||||||
|
/// {@template bezier_curve_shape}
|
||||||
|
/// Creates a bezier curve.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class BezierCurveShape extends ChainShape {
|
||||||
|
/// {@macro bezier_curve_shape}
|
||||||
|
BezierCurveShape({
|
||||||
|
required this.controlPoints,
|
||||||
|
}) {
|
||||||
|
createChain(calculateBezierCurve(controlPoints: controlPoints));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Specifies the control points of the curve.
|
||||||
|
///
|
||||||
|
/// First and last [controlPoints] set the beginning and end of the curve,
|
||||||
|
/// inner points between them set its final shape.
|
||||||
|
final List<Vector2> controlPoints;
|
||||||
|
|
||||||
|
/// Rotates the bezier curve by a given [angle] in radians.
|
||||||
|
void rotate(double angle) {
|
||||||
|
vertices.map((vector) => vector..rotate(angle)).toList();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:geometry/geometry.dart';
|
||||||
|
|
||||||
|
/// {@template ellipse_shape}
|
||||||
|
/// Creates an ellipse.
|
||||||
|
/// {@endtemplate}
|
||||||
|
class EllipseShape extends ChainShape {
|
||||||
|
/// {@macro ellipse_shape}
|
||||||
|
EllipseShape({
|
||||||
|
required this.center,
|
||||||
|
required this.majorRadius,
|
||||||
|
required this.minorRadius,
|
||||||
|
}) {
|
||||||
|
createChain(
|
||||||
|
calculateEllipse(
|
||||||
|
center: center,
|
||||||
|
majorRadius: majorRadius,
|
||||||
|
minorRadius: minorRadius,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The top left corner of the ellipse.
|
||||||
|
///
|
||||||
|
/// Where the initial painting begins.
|
||||||
|
// TODO(ruialonso): Change to use appropiate center.
|
||||||
|
final Vector2 center;
|
||||||
|
|
||||||
|
/// Major radius is specified by [majorRadius].
|
||||||
|
final double majorRadius;
|
||||||
|
|
||||||
|
/// Minor radius is specified by [minorRadius].
|
||||||
|
final double minorRadius;
|
||||||
|
|
||||||
|
/// Rotates the ellipse by a given [angle] in radians.
|
||||||
|
void rotate(double angle) {
|
||||||
|
vertices.map((vector) => vector..rotate(angle)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
EllipseShape copyWith({
|
||||||
|
Vector2? center,
|
||||||
|
double? majorRadius,
|
||||||
|
double? minorRadius,
|
||||||
|
}) =>
|
||||||
|
EllipseShape(
|
||||||
|
center: center ?? this.center,
|
||||||
|
majorRadius: majorRadius ?? this.majorRadius,
|
||||||
|
minorRadius: minorRadius ?? this.minorRadius,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
export 'arc_shape.dart';
|
||||||
|
export 'bezier_curve_shape.dart';
|
||||||
|
export 'ellipse_shape.dart';
|
@ -1,7 +1 @@
|
|||||||
/// {@template pinball_components}
|
export 'components/components.dart';
|
||||||
/// Package with the UI game components for the Pinball Game
|
|
||||||
/// {@endtemplate}
|
|
||||||
class PinballComponents {
|
|
||||||
/// {@macro pinball_components}
|
|
||||||
const PinballComponents();
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,127 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.lock
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
# Visual Studio Code related
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.settings/
|
||||||
|
.vscode/*
|
||||||
|
|
||||||
|
# Flutter repo-specific
|
||||||
|
/bin/cache/
|
||||||
|
/bin/mingit/
|
||||||
|
/dev/benchmarks/mega_gallery/
|
||||||
|
/dev/bots/.recipe_deps
|
||||||
|
/dev/bots/android_tools/
|
||||||
|
/dev/docs/doc/
|
||||||
|
/dev/docs/flutter.docs.zip
|
||||||
|
/dev/docs/lib/
|
||||||
|
/dev/docs/pubspec.yaml
|
||||||
|
/dev/integration_tests/**/xcuserdata
|
||||||
|
/dev/integration_tests/**/Pods
|
||||||
|
/packages/flutter/coverage/
|
||||||
|
version
|
||||||
|
|
||||||
|
# packages file containing multi-root paths
|
||||||
|
.packages.generated
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
build/
|
||||||
|
flutter_*.png
|
||||||
|
linked_*.ds
|
||||||
|
unlinked.ds
|
||||||
|
unlinked_spec.ds
|
||||||
|
.fvm/
|
||||||
|
|
||||||
|
# Android related
|
||||||
|
**/android/**/gradle-wrapper.jar
|
||||||
|
**/android/.gradle
|
||||||
|
**/android/captures/
|
||||||
|
**/android/gradlew
|
||||||
|
**/android/gradlew.bat
|
||||||
|
**/android/local.properties
|
||||||
|
**/android/**/GeneratedPluginRegistrant.java
|
||||||
|
**/android/key.properties
|
||||||
|
**/android/.idea/
|
||||||
|
*.jks
|
||||||
|
|
||||||
|
# iOS/XCode related
|
||||||
|
**/ios/**/*.mode1v3
|
||||||
|
**/ios/**/*.mode2v3
|
||||||
|
**/ios/**/*.moved-aside
|
||||||
|
**/ios/**/*.pbxuser
|
||||||
|
**/ios/**/*.perspectivev3
|
||||||
|
**/ios/**/*sync/
|
||||||
|
**/ios/**/.sconsign.dblite
|
||||||
|
**/ios/**/.tags*
|
||||||
|
**/ios/**/.vagrant/
|
||||||
|
**/ios/**/DerivedData/
|
||||||
|
**/ios/**/Icon?
|
||||||
|
**/ios/**/Pods/
|
||||||
|
**/ios/**/.symlinks/
|
||||||
|
**/ios/**/profile
|
||||||
|
**/ios/**/xcuserdata
|
||||||
|
**/ios/.generated/
|
||||||
|
**/ios/Flutter/App.framework
|
||||||
|
**/ios/Flutter/Flutter.framework
|
||||||
|
**/ios/Flutter/Flutter.podspec
|
||||||
|
**/ios/Flutter/Generated.xcconfig
|
||||||
|
**/ios/Flutter/app.flx
|
||||||
|
**/ios/Flutter/app.zip
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
**/ios/Flutter/flutter_assets/
|
||||||
|
**/ios/Flutter/flutter_export_environment.sh
|
||||||
|
**/ios/ServiceDefinitions.json
|
||||||
|
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
coverage/
|
||||||
|
|
||||||
|
# Submodules
|
||||||
|
!pubspec.lock
|
||||||
|
packages/**/pubspec.lock
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Exceptions to the above rules.
|
||||||
|
!**/ios/**/default.mode1v3
|
||||||
|
!**/ios/**/default.mode2v3
|
||||||
|
!**/ios/**/default.pbxuser
|
||||||
|
!**/ios/**/default.perspectivev3
|
||||||
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
!/dev/ci/**/Gemfile.lock
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.idea/codeStyles/
|
||||||
|
!.idea/dictionaries/
|
||||||
|
!.idea/runConfigurations/
|
@ -0,0 +1,10 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: app
|
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Very Good Ventures
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
@ -0,0 +1,164 @@
|
|||||||
|
# Sandbox
|
||||||
|
|
||||||
|
![coverage][coverage_badge]
|
||||||
|
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
|
||||||
|
[![License: MIT][license_badge]][license_link]
|
||||||
|
|
||||||
|
Generated by the [Very Good CLI][very_good_cli_link] 🤖
|
||||||
|
|
||||||
|
A sanbox application where components are showcased and developed in an isolated way
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started 🚀
|
||||||
|
|
||||||
|
This project contains 3 flavors:
|
||||||
|
|
||||||
|
- development
|
||||||
|
- staging
|
||||||
|
- production
|
||||||
|
|
||||||
|
To run the desired flavor either use the launch configuration in VSCode/Android Studio or use the following commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Development
|
||||||
|
$ flutter run --flavor development --target lib/main_development.dart
|
||||||
|
|
||||||
|
# Staging
|
||||||
|
$ flutter run --flavor staging --target lib/main_staging.dart
|
||||||
|
|
||||||
|
# Production
|
||||||
|
$ flutter run --flavor production --target lib/main_production.dart
|
||||||
|
```
|
||||||
|
|
||||||
|
_\*Sandbox works on iOS, Android, Web, and Windows._
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Running Tests 🧪
|
||||||
|
|
||||||
|
To run all unit and widget tests use the following command:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ flutter test --coverage --test-randomize-ordering-seed random
|
||||||
|
```
|
||||||
|
|
||||||
|
To view the generated coverage report you can use [lcov](https://github.com/linux-test-project/lcov).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Generate Coverage Report
|
||||||
|
$ genhtml coverage/lcov.info -o coverage/
|
||||||
|
|
||||||
|
# Open Coverage Report
|
||||||
|
$ open coverage/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Working with Translations 🌐
|
||||||
|
|
||||||
|
This project relies on [flutter_localizations][flutter_localizations_link] and follows the [official internationalization guide for Flutter][internationalization_link].
|
||||||
|
|
||||||
|
### Adding Strings
|
||||||
|
|
||||||
|
1. To add a new localizable string, open the `app_en.arb` file at `lib/l10n/arb/app_en.arb`.
|
||||||
|
|
||||||
|
```arb
|
||||||
|
{
|
||||||
|
"@@locale": "en",
|
||||||
|
"counterAppBarTitle": "Counter",
|
||||||
|
"@counterAppBarTitle": {
|
||||||
|
"description": "Text shown in the AppBar of the Counter Page"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Then add a new key/value and description
|
||||||
|
|
||||||
|
```arb
|
||||||
|
{
|
||||||
|
"@@locale": "en",
|
||||||
|
"counterAppBarTitle": "Counter",
|
||||||
|
"@counterAppBarTitle": {
|
||||||
|
"description": "Text shown in the AppBar of the Counter Page"
|
||||||
|
},
|
||||||
|
"helloWorld": "Hello World",
|
||||||
|
"@helloWorld": {
|
||||||
|
"description": "Hello World Text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Use the new string
|
||||||
|
|
||||||
|
```dart
|
||||||
|
import 'package:sandbox/l10n/l10n.dart';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final l10n = context.l10n;
|
||||||
|
return Text(l10n.helloWorld);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Supported Locales
|
||||||
|
|
||||||
|
Update the `CFBundleLocalizations` array in the `Info.plist` at `ios/Runner/Info.plist` to include the new locale.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
...
|
||||||
|
|
||||||
|
<key>CFBundleLocalizations</key>
|
||||||
|
<array>
|
||||||
|
<string>en</string>
|
||||||
|
<string>es</string>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding Translations
|
||||||
|
|
||||||
|
1. For each supported locale, add a new ARB file in `lib/l10n/arb`.
|
||||||
|
|
||||||
|
```
|
||||||
|
├── l10n
|
||||||
|
│ ├── arb
|
||||||
|
│ │ ├── app_en.arb
|
||||||
|
│ │ └── app_es.arb
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add the translated strings to each `.arb` file:
|
||||||
|
|
||||||
|
`app_en.arb`
|
||||||
|
|
||||||
|
```arb
|
||||||
|
{
|
||||||
|
"@@locale": "en",
|
||||||
|
"counterAppBarTitle": "Counter",
|
||||||
|
"@counterAppBarTitle": {
|
||||||
|
"description": "Text shown in the AppBar of the Counter Page"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`app_es.arb`
|
||||||
|
|
||||||
|
```arb
|
||||||
|
{
|
||||||
|
"@@locale": "es",
|
||||||
|
"counterAppBarTitle": "Contador",
|
||||||
|
"@counterAppBarTitle": {
|
||||||
|
"description": "Texto mostrado en la AppBar de la página del contador"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[coverage_badge]: coverage_badge.svg
|
||||||
|
[flutter_localizations_link]: https://api.flutter.dev/flutter/flutter_localizations/flutter_localizations-library.html
|
||||||
|
[internationalization_link]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization
|
||||||
|
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
|
[license_link]: https://opensource.org/licenses/MIT
|
||||||
|
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
|
||||||
|
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
|
||||||
|
[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli
|
@ -0,0 +1,4 @@
|
|||||||
|
include: package:very_good_analysis/analysis_options.2.4.0.yaml
|
||||||
|
linter:
|
||||||
|
rules:
|
||||||
|
public_member_api_docs: false
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,2 @@
|
|||||||
|
export 'games.dart';
|
||||||
|
export 'methods.dart';
|
@ -0,0 +1,74 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame/input.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class BasicGame extends Forge2DGame {
|
||||||
|
BasicGame() {
|
||||||
|
images.prefix = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class LineGame extends BasicGame with PanDetector {
|
||||||
|
Vector2? _lineEnd;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
|
||||||
|
camera.followVector2(Vector2.zero());
|
||||||
|
unawaited(add(_PreviewLine()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanStart(DragStartInfo info) {
|
||||||
|
_lineEnd = info.eventPosition.game;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanUpdate(DragUpdateInfo info) {
|
||||||
|
_lineEnd = info.eventPosition.game;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onPanEnd(DragEndInfo info) {
|
||||||
|
if (_lineEnd != null) {
|
||||||
|
final line = _lineEnd! - Vector2.zero();
|
||||||
|
onLine(line);
|
||||||
|
_lineEnd = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onLine(Vector2 line);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewLine extends PositionComponent with HasGameRef<LineGame> {
|
||||||
|
static final _previewLinePaint = Paint()
|
||||||
|
..color = Colors.pink
|
||||||
|
..strokeWidth = 0.2
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
|
||||||
|
Vector2? lineEnd;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
|
||||||
|
lineEnd = gameRef._lineEnd?.clone()?..multiply(Vector2(1, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void render(Canvas canvas) {
|
||||||
|
super.render(canvas);
|
||||||
|
|
||||||
|
if (lineEnd != null) {
|
||||||
|
canvas.drawLine(
|
||||||
|
Vector2.zero().toOffset(),
|
||||||
|
lineEnd!.toOffset(),
|
||||||
|
_previewLinePaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
String buildSourceLink(String path) {
|
||||||
|
return 'https://github.com/VGVentures/pinball/tree/main/packages/pinball_components/sandbox/lib/stories/$path';
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright (c) 2022, Very Good Ventures
|
||||||
|
// https://verygood.ventures
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by an MIT-style
|
||||||
|
// license that can be found in the LICENSE file or at
|
||||||
|
// https://opensource.org/licenses/MIT.
|
||||||
|
import 'package:dashbook/dashbook.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sandbox/stories/effects/effects.dart';
|
||||||
|
import 'package:sandbox/stories/stories.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final dashbook = Dashbook(theme: ThemeData.dark());
|
||||||
|
|
||||||
|
addBallStories(dashbook);
|
||||||
|
addLayerStories(dashbook);
|
||||||
|
addEffectsStories(dashbook);
|
||||||
|
runApp(dashbook);
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
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.dart';
|
||||||
|
import 'package:sandbox/stories/ball/basic.dart';
|
||||||
|
|
||||||
|
void addBallStories(Dashbook dashbook) {
|
||||||
|
dashbook.storiesOf('Ball')
|
||||||
|
..add(
|
||||||
|
'Basic',
|
||||||
|
(context) => GameWidget(
|
||||||
|
game: BasicBallGame(
|
||||||
|
color: context.colorProperty('color', Colors.blue),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
codeLink: buildSourceLink('ball/basic.dart'),
|
||||||
|
info: BasicBallGame.info,
|
||||||
|
)
|
||||||
|
..add(
|
||||||
|
'Booster',
|
||||||
|
(context) => GameWidget(
|
||||||
|
game: BallBoosterExample(),
|
||||||
|
),
|
||||||
|
codeLink: buildSourceLink('ball/ball_booster.dart'),
|
||||||
|
info: BallBoosterExample.info,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
|
||||||
|
class BallBoosterExample extends LineGame {
|
||||||
|
static const info = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onLine(Vector2 line) {
|
||||||
|
final ball = Ball(baseColor: Colors.transparent);
|
||||||
|
add(ball);
|
||||||
|
|
||||||
|
ball.mounted.then((value) => ball.boost(line * -1 * 20));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
import 'package:flame/input.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
|
||||||
|
class BasicBallGame extends BasicGame with TapDetector {
|
||||||
|
BasicBallGame({required this.color});
|
||||||
|
|
||||||
|
static const info = '''
|
||||||
|
Basic example of how a Ball works, tap anywhere on the
|
||||||
|
screen to spawn a ball into the game.
|
||||||
|
''';
|
||||||
|
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTapUp(TapUpInfo info) {
|
||||||
|
add(
|
||||||
|
Ball(baseColor: color)..initialPosition = info.eventPosition.game,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:dashbook/dashbook.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
import 'package:sandbox/stories/effects/fire_effect.dart';
|
||||||
|
|
||||||
|
void addEffectsStories(Dashbook dashbook) {
|
||||||
|
dashbook.storiesOf('Effects').add(
|
||||||
|
'Fire Effect',
|
||||||
|
(context) => GameWidget(game: FireEffectExample()),
|
||||||
|
codeLink: buildSourceLink('effects/fire_effect.dart'),
|
||||||
|
info: FireEffectExample.info,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:sandbox/common/common.dart';
|
||||||
|
|
||||||
|
class FireEffectExample extends LineGame {
|
||||||
|
static const info = 'Demonstrate the fire trail effect '
|
||||||
|
'drag a line to define the trail direction';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onLine(Vector2 line) {
|
||||||
|
add(_EffectEmitter(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EffectEmitter extends Component {
|
||||||
|
_EffectEmitter(this.line) {
|
||||||
|
_direction = line.normalized();
|
||||||
|
_force = line.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const _timerLimit = 2.0;
|
||||||
|
var _timer = _timerLimit;
|
||||||
|
|
||||||
|
final Vector2 line;
|
||||||
|
|
||||||
|
late Vector2 _direction;
|
||||||
|
late double _force;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(double dt) {
|
||||||
|
super.update(dt);
|
||||||
|
|
||||||
|
if (_timer > 0) {
|
||||||
|
add(
|
||||||
|
FireEffect(
|
||||||
|
burstPower: (_timer / _timerLimit) * _force,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
direction: _direction,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_timer -= dt;
|
||||||
|
} else {
|
||||||
|
removeFromParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class BasicLayerGame extends BasicGame with TapDetector {
|
||||||
|
BasicLayerGame({required this.color});
|
||||||
|
|
||||||
|
static const info = '''
|
||||||
|
Basic example of how layers work with a Ball hitting 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BigSquare extends BodyComponent with InitialPosition, Layered {
|
||||||
|
BigSquare() {
|
||||||
|
paint = Paint()
|
||||||
|
..color = const Color.fromARGB(255, 8, 218, 241)
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
layer = Layer.jetpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
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() {
|
||||||
|
paint = Paint()
|
||||||
|
..color = const Color.fromARGB(255, 27, 241, 8)
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
layer = Layer.board;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
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() {
|
||||||
|
paint = Paint()
|
||||||
|
..color = const Color.fromARGB(255, 241, 8, 8)
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = PolygonShape()..setAsBoxXY(3, 3);
|
||||||
|
final fixtureDef = FixtureDef(shape);
|
||||||
|
|
||||||
|
final bodyDef = BodyDef()..position = initialPosition;
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixture(fixtureDef);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
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.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,
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export 'ball/ball.dart';
|
||||||
|
export 'layer/layer.dart';
|
@ -0,0 +1,453 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.8.2"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
charcode:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: charcode
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.15.0"
|
||||||
|
dashbook:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dashbook
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.7"
|
||||||
|
device_frame:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: device_frame
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.2"
|
||||||
|
flame:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flame
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0-releasecandidate.6"
|
||||||
|
flame_forge2d:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flame_forge2d
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.0-releasecandidate.6"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_colorpicker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_colorpicker
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
|
flutter_markdown:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_markdown
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.9"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
forge2d:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: forge2d
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.0"
|
||||||
|
freezed_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: freezed_annotation
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
geometry:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
path: "../../geometry"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0+1"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.3"
|
||||||
|
json_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.4.0"
|
||||||
|
markdown:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: markdown
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.1"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.11"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.7.0"
|
||||||
|
ordered_set:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ordered_set
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.8.0"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.5"
|
||||||
|
pinball_components:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: ".."
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0+1"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
process:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: process
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.4"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.13"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.11"
|
||||||
|
shared_preferences_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_ios
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
shared_preferences_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.99"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.8.1"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.0"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.8"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
url_launcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.20"
|
||||||
|
url_launcher_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_android
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.15"
|
||||||
|
url_launcher_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_ios
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.15"
|
||||||
|
url_launcher_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_linux
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
url_launcher_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_macos
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
url_launcher_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_platform_interface
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.5"
|
||||||
|
url_launcher_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_web
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.9"
|
||||||
|
url_launcher_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: url_launcher_windows
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
very_good_analysis:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: very_good_analysis
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.4"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0+1"
|
||||||
|
sdks:
|
||||||
|
dart: ">=2.16.0 <3.0.0"
|
||||||
|
flutter: ">=2.10.0"
|
@ -0,0 +1,24 @@
|
|||||||
|
name: sandbox
|
||||||
|
description: A sanbox application where components are showcased and developed in an isolated way
|
||||||
|
version: 1.0.0+1
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.16.0 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
dashbook: ^0.1.7
|
||||||
|
flame: ^1.1.0-releasecandidate.6
|
||||||
|
flame_forge2d: ^0.9.0-releasecandidate.6
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
pinball_components:
|
||||||
|
path: ../
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
very_good_analysis: ^2.4.0
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
uses-material-design: true
|
After Width: | Height: | Size: 917 B |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,104 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!--
|
||||||
|
If you are serving your web app in a path other than the root, change the
|
||||||
|
href value below to reflect the base path you are serving from.
|
||||||
|
|
||||||
|
The path provided below has to start and end with a slash "/" in order for
|
||||||
|
it to work correctly.
|
||||||
|
|
||||||
|
For more details:
|
||||||
|
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
|
||||||
|
|
||||||
|
This is a placeholder for base href that will be replaced by the value of
|
||||||
|
the `--base-href` argument provided to `flutter build`.
|
||||||
|
-->
|
||||||
|
<base href="$FLUTTER_BASE_HREF">
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta name="description" content="A new Flutter project.">
|
||||||
|
|
||||||
|
<!-- iOS meta tags & icons -->
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="sandbox">
|
||||||
|
<link rel="apple-touch-icon" href="icons/Icon-192.png">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
|
|
||||||
|
<title>sandbox</title>
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- This script installs service_worker.js to provide PWA functionality to
|
||||||
|
application. For more information, see:
|
||||||
|
https://developers.google.com/web/fundamentals/primers/service-workers -->
|
||||||
|
<script>
|
||||||
|
var serviceWorkerVersion = null;
|
||||||
|
var scriptLoaded = false;
|
||||||
|
function loadMainDartJs() {
|
||||||
|
if (scriptLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scriptLoaded = true;
|
||||||
|
var scriptTag = document.createElement('script');
|
||||||
|
scriptTag.src = 'main.dart.js';
|
||||||
|
scriptTag.type = 'application/javascript';
|
||||||
|
document.body.append(scriptTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
// Service workers are supported. Use them.
|
||||||
|
window.addEventListener('load', function () {
|
||||||
|
// Wait for registration to finish before dropping the <script> tag.
|
||||||
|
// Otherwise, the browser will load the script multiple times,
|
||||||
|
// potentially different versions.
|
||||||
|
var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
|
||||||
|
navigator.serviceWorker.register(serviceWorkerUrl)
|
||||||
|
.then((reg) => {
|
||||||
|
function waitForActivation(serviceWorker) {
|
||||||
|
serviceWorker.addEventListener('statechange', () => {
|
||||||
|
if (serviceWorker.state == 'activated') {
|
||||||
|
console.log('Installed new service worker.');
|
||||||
|
loadMainDartJs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!reg.active && (reg.installing || reg.waiting)) {
|
||||||
|
// No active web worker and we have installed or are installing
|
||||||
|
// one for the first time. Simply wait for it to activate.
|
||||||
|
waitForActivation(reg.installing || reg.waiting);
|
||||||
|
} else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
|
||||||
|
// When the app updates the serviceWorkerVersion changes, so we
|
||||||
|
// need to ask the service worker to update.
|
||||||
|
console.log('New service worker available.');
|
||||||
|
reg.update();
|
||||||
|
waitForActivation(reg.installing);
|
||||||
|
} else {
|
||||||
|
// Existing service worker is still good.
|
||||||
|
console.log('Loading app from service worker.');
|
||||||
|
loadMainDartJs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If service worker doesn't succeed in a reasonable amount of time,
|
||||||
|
// fallback to plaint <script> tag.
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!scriptLoaded) {
|
||||||
|
console.warn(
|
||||||
|
'Failed to load app from service worker. Falling back to plain <script> tag.',
|
||||||
|
);
|
||||||
|
loadMainDartJs();
|
||||||
|
}
|
||||||
|
}, 4000);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service workers not supported. Just drop the <script> tag.
|
||||||
|
loadMainDartJs();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "sandbox",
|
||||||
|
"short_name": "sandbox",
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0175C2",
|
||||||
|
"theme_color": "#0175C2",
|
||||||
|
"description": "A new Flutter project.",
|
||||||
|
"orientation": "portrait-primary",
|
||||||
|
"prefer_related_applications": false,
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "icons/Icon-maskable-512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
export 'mocks.dart';
|
||||||
|
export 'test_game.dart';
|
@ -0,0 +1,5 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
class MockCanvas extends Mock implements Canvas {}
|
@ -0,0 +1,7 @@
|
|||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
|
||||||
|
class TestGame extends Forge2DGame {
|
||||||
|
TestGame() {
|
||||||
|
images.prefix = '';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,186 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
group('Ball', () {
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ready();
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
expect(game.contains(ball), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('body', () {
|
||||||
|
flameTester.test(
|
||||||
|
'is dynamic',
|
||||||
|
(game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
expect(ball.body.bodyType, equals(BodyType.dynamic));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('can be moved', () {
|
||||||
|
flameTester.test('by its weight', (game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
game.update(1);
|
||||||
|
expect(ball.body.position, isNot(equals(ball.initialPosition)));
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('by applying velocity', (game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
ball.body.gravityScale = 0;
|
||||||
|
ball.body.linearVelocity.setValues(10, 10);
|
||||||
|
game.update(1);
|
||||||
|
expect(ball.body.position, isNot(equals(ball.initialPosition)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('fixture', () {
|
||||||
|
flameTester.test(
|
||||||
|
'exists',
|
||||||
|
(game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
expect(ball.body.fixtures[0], isA<Fixture>());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'is dense',
|
||||||
|
(game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
final fixture = ball.body.fixtures[0];
|
||||||
|
expect(fixture.density, greaterThan(0));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'shape is circular',
|
||||||
|
(game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
final fixture = ball.body.fixtures[0];
|
||||||
|
expect(fixture.shape.shapeType, equals(ShapeType.circle));
|
||||||
|
expect(fixture.shape.radius, equals(1.5));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'has Layer.all as default filter maskBits',
|
||||||
|
(game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ready();
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
final fixture = ball.body.fixtures[0];
|
||||||
|
expect(fixture.filterData.maskBits, equals(Layer.board.maskBits));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('stop', () {
|
||||||
|
group("can't be moved", () {
|
||||||
|
flameTester.test('by its weight', (game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
ball.stop();
|
||||||
|
|
||||||
|
game.update(1);
|
||||||
|
expect(ball.body.position, equals(ball.initialPosition));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('by applying velocity', (game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
ball.stop();
|
||||||
|
|
||||||
|
ball.body.linearVelocity.setValues(10, 10);
|
||||||
|
game.update(1);
|
||||||
|
expect(ball.body.position, equals(ball.initialPosition));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('resume', () {
|
||||||
|
group('can move', () {
|
||||||
|
flameTester.test(
|
||||||
|
'by its weight when previously stopped',
|
||||||
|
(game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
ball.stop();
|
||||||
|
ball.resume();
|
||||||
|
|
||||||
|
game.update(1);
|
||||||
|
expect(ball.body.position, isNot(equals(ball.initialPosition)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'by applying velocity when previously stopped',
|
||||||
|
(game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
ball.stop();
|
||||||
|
ball.resume();
|
||||||
|
|
||||||
|
ball.body.gravityScale = 0;
|
||||||
|
ball.body.linearVelocity.setValues(10, 10);
|
||||||
|
game.update(1);
|
||||||
|
expect(ball.body.position, isNot(equals(ball.initialPosition)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('boost', () {
|
||||||
|
flameTester.test('applies an impulse to the ball', (game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
expect(ball.body.linearVelocity, equals(Vector2.zero()));
|
||||||
|
|
||||||
|
ball.boost(Vector2.all(10));
|
||||||
|
expect(ball.body.linearVelocity.x, greaterThan(0));
|
||||||
|
expect(ball.body.linearVelocity.y, greaterThan(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('adds fire effect components to the game', (game) async {
|
||||||
|
final ball = Ball(baseColor: Colors.blue);
|
||||||
|
await game.ensureAdd(ball);
|
||||||
|
|
||||||
|
ball.boost(Vector2.all(10));
|
||||||
|
game.update(0);
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
expect(game.children.whereType<FireEffect>().length, greaterThan(0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(TestGame.new);
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
registerFallbackValue(Offset.zero);
|
||||||
|
registerFallbackValue(Paint());
|
||||||
|
});
|
||||||
|
|
||||||
|
group('FireEffect', () {
|
||||||
|
flameTester.test('is removed once its particles are done', (game) async {
|
||||||
|
await game.ensureAdd(
|
||||||
|
FireEffect(
|
||||||
|
burstPower: 1,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
direction: Vector2.all(2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await game.ready();
|
||||||
|
expect(game.children.whereType<FireEffect>().length, equals(1));
|
||||||
|
game.update(5);
|
||||||
|
|
||||||
|
await game.ready();
|
||||||
|
expect(game.children.whereType<FireEffect>().length, equals(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
flameTester.test('render circles on the canvas', (game) async {
|
||||||
|
final effect = FireEffect(
|
||||||
|
burstPower: 1,
|
||||||
|
position: Vector2.zero(),
|
||||||
|
direction: Vector2.all(2),
|
||||||
|
);
|
||||||
|
await game.ensureAdd(effect);
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
final canvas = MockCanvas();
|
||||||
|
effect.render(canvas);
|
||||||
|
|
||||||
|
verify(() => canvas.drawCircle(any(), any(), any()))
|
||||||
|
.called(greaterThan(0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/src/components/components.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('ArcShape', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
ArcShape(
|
||||||
|
center: Vector2.zero(),
|
||||||
|
arcRadius: 10,
|
||||||
|
angle: 2 * math.pi,
|
||||||
|
),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('copyWith', () {
|
||||||
|
test(
|
||||||
|
'copies correctly '
|
||||||
|
'when no argument specified', () {
|
||||||
|
final arcShape = ArcShape(
|
||||||
|
center: Vector2.zero(),
|
||||||
|
arcRadius: 10,
|
||||||
|
angle: 2 * math.pi,
|
||||||
|
);
|
||||||
|
final arcShapeCopied = arcShape.copyWith();
|
||||||
|
|
||||||
|
for (var index = 0; index < arcShape.vertices.length; index++) {
|
||||||
|
expect(
|
||||||
|
arcShape.vertices[index],
|
||||||
|
equals(arcShapeCopied.vertices[index]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
'copies correctly '
|
||||||
|
'when all arguments specified', () {
|
||||||
|
final arcShapeExpected = ArcShape(
|
||||||
|
center: Vector2.all(10),
|
||||||
|
arcRadius: 15,
|
||||||
|
angle: 2 * math.pi,
|
||||||
|
);
|
||||||
|
final arcShapeCopied = ArcShape(
|
||||||
|
center: Vector2.zero(),
|
||||||
|
arcRadius: 10,
|
||||||
|
angle: math.pi,
|
||||||
|
).copyWith(
|
||||||
|
center: Vector2.all(10),
|
||||||
|
arcRadius: 15,
|
||||||
|
angle: 2 * math.pi,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var index = 0; index < arcShapeCopied.vertices.length; index++) {
|
||||||
|
expect(
|
||||||
|
arcShapeCopied.vertices[index],
|
||||||
|
equals(arcShapeExpected.vertices[index]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/src/components/components.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('BezierCurveShape', () {
|
||||||
|
final controlPoints = [
|
||||||
|
Vector2(0, 0),
|
||||||
|
Vector2(10, 0),
|
||||||
|
Vector2(0, 10),
|
||||||
|
Vector2(10, 10),
|
||||||
|
];
|
||||||
|
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
BezierCurveShape(
|
||||||
|
controlPoints: controlPoints,
|
||||||
|
),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('rotate', () {
|
||||||
|
test('returns vertices rotated', () {
|
||||||
|
const rotationAngle = 2 * math.pi;
|
||||||
|
final controlPoints = [
|
||||||
|
Vector2(0, 0),
|
||||||
|
Vector2(10, 0),
|
||||||
|
Vector2(0, 10),
|
||||||
|
Vector2(10, 10),
|
||||||
|
];
|
||||||
|
|
||||||
|
final bezierCurveShape = BezierCurveShape(
|
||||||
|
controlPoints: controlPoints,
|
||||||
|
);
|
||||||
|
final bezierCurveShapeRotated = BezierCurveShape(
|
||||||
|
controlPoints: controlPoints,
|
||||||
|
)..rotate(rotationAngle);
|
||||||
|
|
||||||
|
for (var index = 0; index < bezierCurveShape.vertices.length; index++) {
|
||||||
|
expect(
|
||||||
|
bezierCurveShape.vertices[index]..rotate(rotationAngle),
|
||||||
|
equals(bezierCurveShapeRotated.vertices[index]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'package:flame/extensions.dart';
|
||||||
|
import 'package:flame/game.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/src/components/components.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('EllipseShape', () {
|
||||||
|
test('can be instantiated', () {
|
||||||
|
expect(
|
||||||
|
EllipseShape(
|
||||||
|
center: Vector2.zero(),
|
||||||
|
majorRadius: 10,
|
||||||
|
minorRadius: 8,
|
||||||
|
),
|
||||||
|
isNotNull,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('rotate', () {
|
||||||
|
test('returns vertices rotated', () {
|
||||||
|
const rotationAngle = 2 * math.pi;
|
||||||
|
final ellipseShape = EllipseShape(
|
||||||
|
center: Vector2.zero(),
|
||||||
|
majorRadius: 10,
|
||||||
|
minorRadius: 8,
|
||||||
|
);
|
||||||
|
final ellipseShapeRotated = EllipseShape(
|
||||||
|
center: Vector2.zero(),
|
||||||
|
majorRadius: 10,
|
||||||
|
minorRadius: 8,
|
||||||
|
)..rotate(rotationAngle);
|
||||||
|
|
||||||
|
for (var index = 0; index < ellipseShape.vertices.length; index++) {
|
||||||
|
expect(
|
||||||
|
ellipseShape.vertices[index]..rotate(rotationAngle),
|
||||||
|
equals(ellipseShapeRotated.vertices[index]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('copyWith', () {
|
||||||
|
test('returns same shape when no properties are passed', () {
|
||||||
|
final ellipseShape = EllipseShape(
|
||||||
|
center: Vector2.zero(),
|
||||||
|
majorRadius: 10,
|
||||||
|
minorRadius: 8,
|
||||||
|
);
|
||||||
|
final ellipseShapeCopied = ellipseShape.copyWith();
|
||||||
|
|
||||||
|
for (var index = 0; index < ellipseShape.vertices.length; index++) {
|
||||||
|
expect(
|
||||||
|
ellipseShape.vertices[index],
|
||||||
|
equals(ellipseShapeCopied.vertices[index]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns object with updated properties when are passed', () {
|
||||||
|
final ellipseShapeExpected = EllipseShape(
|
||||||
|
center: Vector2.all(10),
|
||||||
|
majorRadius: 10,
|
||||||
|
minorRadius: 8,
|
||||||
|
);
|
||||||
|
final ellipseShapeCopied = EllipseShape(
|
||||||
|
center: Vector2.zero(),
|
||||||
|
majorRadius: 10,
|
||||||
|
minorRadius: 8,
|
||||||
|
).copyWith(center: Vector2.all(10));
|
||||||
|
|
||||||
|
for (var index = 0;
|
||||||
|
index < ellipseShapeCopied.vertices.length;
|
||||||
|
index++) {
|
||||||
|
expect(
|
||||||
|
ellipseShapeCopied.vertices[index],
|
||||||
|
equals(ellipseShapeExpected.vertices[index]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,11 +0,0 @@
|
|||||||
// ignore_for_file: prefer_const_constructors
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('PinballComponents', () {
|
|
||||||
test('can be instantiated', () {
|
|
||||||
expect(PinballComponents(), isNotNull);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -0,0 +1,125 @@
|
|||||||
|
// ignore_for_file: cascade_invocations
|
||||||
|
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flame_test/flame_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:pinball/game/game.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
import '../../helpers/helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final flameTester = FlameTester(PinballGameTest.create);
|
||||||
|
|
||||||
|
group('FlutterForest', () {
|
||||||
|
flameTester.test(
|
||||||
|
'loads correctly',
|
||||||
|
(game) async {
|
||||||
|
await game.ready();
|
||||||
|
final flutterForest = FlutterForest(position: Vector2(0, 0));
|
||||||
|
await game.ensureAdd(flutterForest);
|
||||||
|
|
||||||
|
expect(game.contains(flutterForest), isTrue);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
flameTester.test(
|
||||||
|
'onNewState adds a new ball',
|
||||||
|
(game) async {
|
||||||
|
final flutterForest = FlutterForest(position: Vector2(0, 0));
|
||||||
|
await game.ready();
|
||||||
|
await game.ensureAdd(flutterForest);
|
||||||
|
|
||||||
|
final previousBalls = game.descendants().whereType<Ball>().length;
|
||||||
|
flutterForest.onNewState(MockGameState());
|
||||||
|
await game.ready();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
game.descendants().whereType<Ball>().length,
|
||||||
|
greaterThan(previousBalls),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('listenWhen', () {
|
||||||
|
final gameBloc = MockGameBloc();
|
||||||
|
final tester = flameBlocTester(gameBloc: () => gameBloc);
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
whenListen(
|
||||||
|
gameBloc,
|
||||||
|
const Stream<GameState>.empty(),
|
||||||
|
initialState: const GameState.initial(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tester.widgetTest(
|
||||||
|
'listens when a Bonus.dashNest is added',
|
||||||
|
(game, tester) async {
|
||||||
|
await game.ready();
|
||||||
|
final flutterForest =
|
||||||
|
game.descendants().whereType<FlutterForest>().first;
|
||||||
|
|
||||||
|
const state = GameState(
|
||||||
|
score: 0,
|
||||||
|
balls: 3,
|
||||||
|
activatedBonusLetters: [],
|
||||||
|
activatedDashNests: {},
|
||||||
|
bonusHistory: [GameBonus.dashNest],
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
flutterForest.listenWhen(const GameState.initial(), state),
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('DashNestBumperBallContactCallback', () {
|
||||||
|
final gameBloc = MockGameBloc();
|
||||||
|
final tester = flameBlocTester(gameBloc: () => gameBloc);
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
whenListen(
|
||||||
|
gameBloc,
|
||||||
|
const Stream<GameState>.empty(),
|
||||||
|
initialState: const GameState.initial(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
tester.widgetTest(
|
||||||
|
'adds a DashNestActivated event with DashNestBumper.id',
|
||||||
|
(game, tester) async {
|
||||||
|
final contactCallback = DashNestBumperBallContactCallback();
|
||||||
|
const id = '0';
|
||||||
|
final dashNestBumper = MockDashNestBumper();
|
||||||
|
when(() => dashNestBumper.id).thenReturn(id);
|
||||||
|
when(() => dashNestBumper.gameRef).thenReturn(game);
|
||||||
|
|
||||||
|
contactCallback.begin(dashNestBumper, MockBall(), MockContact());
|
||||||
|
|
||||||
|
verify(() => gameBloc.add(DashNestActivated(dashNestBumper.id)))
|
||||||
|
.called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('BigDashNestBumper', () {
|
||||||
|
test('has points', () {
|
||||||
|
final dashNestBumper = BigDashNestBumper(id: '');
|
||||||
|
expect(dashNestBumper.points, greaterThan(0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('SmallDashNestBumper', () {
|
||||||
|
test('has points', () {
|
||||||
|
final dashNestBumper = SmallDashNestBumper(id: '');
|
||||||
|
expect(dashNestBumper.points, greaterThan(0));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
// ignore_for_file: cascade_invocations
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:flame_test/flame_test.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
|
|
||||||
import '../../helpers/helpers.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
final flameTester = FlameTester(PinballGameTest.create);
|
|
||||||
|
|
||||||
group('JetpackRamp', () {
|
|
||||||
flameTester.test(
|
|
||||||
'loads correctly',
|
|
||||||
(game) async {
|
|
||||||
final ramp = JetpackRamp(
|
|
||||||
position: Vector2.zero(),
|
|
||||||
);
|
|
||||||
await game.ready();
|
|
||||||
await game.ensureAdd(ramp);
|
|
||||||
|
|
||||||
expect(game.contains(ramp), isTrue);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
group('children', () {
|
|
||||||
flameTester.test(
|
|
||||||
'has only one Pathway.arc',
|
|
||||||
(game) async {
|
|
||||||
final ramp = JetpackRamp(
|
|
||||||
position: Vector2.zero(),
|
|
||||||
);
|
|
||||||
await game.ready();
|
|
||||||
await game.ensureAdd(ramp);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
() => ramp.children.singleWhere(
|
|
||||||
(component) => component is Pathway,
|
|
||||||
),
|
|
||||||
returnsNormally,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
flameTester.test(
|
|
||||||
'has a two RampOpenings for the ramp',
|
|
||||||
(game) async {
|
|
||||||
final ramp = JetpackRamp(
|
|
||||||
position: Vector2.zero(),
|
|
||||||
);
|
|
||||||
await game.ready();
|
|
||||||
await game.ensureAdd(ramp);
|
|
||||||
|
|
||||||
final rampAreas = ramp.children.whereType<RampOpening>();
|
|
||||||
expect(rampAreas.length, 2);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
// ignore_for_file: cascade_invocations
|
|
||||||
|
|
||||||
import 'package:flame/components.dart';
|
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
|
||||||
import 'package:flame_test/flame_test.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pinball/game/game.dart';
|
|
||||||
|
|
||||||
import '../../helpers/helpers.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
final flameTester = FlameTester(PinballGameTest.create);
|
|
||||||
|
|
||||||
group('LauncherRamp', () {
|
|
||||||
flameTester.test(
|
|
||||||
'loads correctly',
|
|
||||||
(game) async {
|
|
||||||
final ramp = LauncherRamp(
|
|
||||||
position: Vector2.zero(),
|
|
||||||
);
|
|
||||||
await game.ready();
|
|
||||||
await game.ensureAdd(ramp);
|
|
||||||
|
|
||||||
expect(game.contains(ramp), isTrue);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
group('constructor', () {
|
|
||||||
flameTester.test(
|
|
||||||
'positions correctly',
|
|
||||||
(game) async {
|
|
||||||
final position = Vector2.all(10);
|
|
||||||
final ramp = LauncherRamp(
|
|
||||||
position: position,
|
|
||||||
);
|
|
||||||
await game.ensureAdd(ramp);
|
|
||||||
|
|
||||||
expect(ramp.position, equals(position));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
group('children', () {
|
|
||||||
flameTester.test(
|
|
||||||
'has two Pathway',
|
|
||||||
(game) async {
|
|
||||||
final ramp = LauncherRamp(
|
|
||||||
position: Vector2.zero(),
|
|
||||||
);
|
|
||||||
await game.ready();
|
|
||||||
await game.ensureAdd(ramp);
|
|
||||||
|
|
||||||
final pathways = ramp.children.whereType<Pathway>().toList();
|
|
||||||
expect(pathways.length, 2);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
flameTester.test(
|
|
||||||
'has a two RampOpenings for the ramp',
|
|
||||||
(game) async {
|
|
||||||
final ramp = LauncherRamp(
|
|
||||||
position: Vector2.zero(),
|
|
||||||
);
|
|
||||||
await game.ready();
|
|
||||||
await game.ensureAdd(ramp);
|
|
||||||
|
|
||||||
final rampAreas = ramp.children.whereType<RampOpening>().toList();
|
|
||||||
expect(rampAreas.length, 2);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|