feat: adds Sprite states to SignPost (#205)

* feat: included new assets

* chore: update game assets loader

* feat: allowed progressing sprite

* feat(sanbox): updated games

* feat: updated tests

* feat: updated goldens

* feat: renamed FlutterSignPost to SignPost

* chore: updated goldens

* chore: renamed story

* refactor: removed caching

* refactor: changed how Sprites are loaded

* feat: made SignpostSpriteState visibleForTesting

* refactor: rename SignPost to Signpost

* chore: updated goldens

* refactor: improved test name

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* docs: documented new Signpost functionality

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>

* docs: improved progress doc

* feat: removed loop

Co-authored-by: Allison Ryan <77211884+allisonryan0002@users.noreply.github.com>
pull/211/head
Alejandro Santiago 2 years ago committed by GitHub
parent 0203927c49
commit 00e006bde3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

3
.gitignore vendored

@ -131,6 +131,3 @@ app.*.map.json
test/.test_runner.dart
web/__/firebase/init.js
# Application exceptions
!/packages/pinball_components/assets/images/flutter_sign_post.png

@ -25,7 +25,7 @@ class FlutterForest extends Component
await super.onLoad();
gameRef.addContactCallback(_DashNestBumperBallContactCallback());
final signPost = FlutterSignPost()..initialPosition = Vector2(8.35, -58.3);
final signpost = Signpost()..initialPosition = Vector2(8.35, -58.3);
final bigNest = _BigDashNestBumper()
..initialPosition = Vector2(18.55, -59.35);
@ -36,7 +36,7 @@ class FlutterForest extends Component
final dashAnimatronic = DashAnimatronic()..position = Vector2(20, -66);
await addAll([
signPost,
signpost,
smallLeftNest,
smallRightNest,
bigNest,

@ -9,7 +9,10 @@ extension PinballGameAssetsX on PinballGame {
return [
images.load(components.Assets.images.ball.ball.keyName),
images.load(components.Assets.images.ball.flameEffect.keyName),
images.load(components.Assets.images.flutterSignPost.keyName),
images.load(components.Assets.images.signpost.inactive.keyName),
images.load(components.Assets.images.signpost.active1.keyName),
images.load(components.Assets.images.signpost.active2.keyName),
images.load(components.Assets.images.signpost.active3.keyName),
images.load(components.Assets.images.flipper.left.keyName),
images.load(components.Assets.images.flipper.right.keyName),
images.load(components.Assets.images.baseboard.left.keyName),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@ -21,17 +21,13 @@ class $AssetsImagesGen {
$AssetsImagesDashGen get dash => const $AssetsImagesDashGen();
$AssetsImagesDinoGen get dino => const $AssetsImagesDinoGen();
$AssetsImagesFlipperGen get flipper => const $AssetsImagesFlipperGen();
/// File path: assets/images/flutter_sign_post.png
AssetGenImage get flutterSignPost =>
const AssetGenImage('assets/images/flutter_sign_post.png');
$AssetsImagesGoogleWordGen get googleWord =>
const $AssetsImagesGoogleWordGen();
$AssetsImagesKickerGen get kicker => const $AssetsImagesKickerGen();
$AssetsImagesLaunchRampGen get launchRamp =>
const $AssetsImagesLaunchRampGen();
$AssetsImagesPlungerGen get plunger => const $AssetsImagesPlungerGen();
$AssetsImagesSignpostGen get signpost => const $AssetsImagesSignpostGen();
$AssetsImagesSlingshotGen get slingshot => const $AssetsImagesSlingshotGen();
$AssetsImagesSpaceshipGen get spaceship => const $AssetsImagesSpaceshipGen();
$AssetsImagesSparkyGen get sparky => const $AssetsImagesSparkyGen();
@ -209,6 +205,26 @@ class $AssetsImagesPlungerGen {
const AssetGenImage('assets/images/plunger/rocket.png');
}
class $AssetsImagesSignpostGen {
const $AssetsImagesSignpostGen();
/// File path: assets/images/signpost/active1.png
AssetGenImage get active1 =>
const AssetGenImage('assets/images/signpost/active1.png');
/// File path: assets/images/signpost/active2.png
AssetGenImage get active2 =>
const AssetGenImage('assets/images/signpost/active2.png');
/// File path: assets/images/signpost/active3.png
AssetGenImage get active3 =>
const AssetGenImage('assets/images/signpost/active3.png');
/// File path: assets/images/signpost/inactive.png
AssetGenImage get inactive =>
const AssetGenImage('assets/images/signpost/inactive.png');
}
class $AssetsImagesSlingshotGen {
const $AssetsImagesSlingshotGen();

@ -12,7 +12,6 @@ export 'dash_nest_bumper.dart';
export 'dino_walls.dart';
export 'fire_effect.dart';
export 'flipper.dart';
export 'flutter_sign_post.dart';
export 'google_letter.dart';
export 'initial_position.dart';
export 'joint_anchor.dart';
@ -25,6 +24,7 @@ export 'render_priority.dart';
export 'rocket.dart';
export 'score_text.dart';
export 'shapes/shapes.dart';
export 'signpost.dart';
export 'slingshot.dart';
export 'spaceship.dart';
export 'spaceship_rail.dart';

@ -1,43 +0,0 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:pinball_components/pinball_components.dart';
/// {@template flutter_sign_post}
/// A sign, found in the Flutter Forest.
/// {@endtemplate}
class FlutterSignPost extends BodyComponent with InitialPosition {
/// {@macro flutter_sign_post}
FlutterSignPost()
: super(
priority: RenderPriority.flutterSignPost,
children: [_FlutterSignPostSpriteComponent()],
) {
renderBody = false;
}
@override
Body createBody() {
final shape = CircleShape()..radius = 0.25;
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef(
position: initialPosition,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
class _FlutterSignPostSpriteComponent extends SpriteComponent with HasGameRef {
@override
Future<void> onLoad() async {
await super.onLoad();
final sprite = await gameRef.loadSprite(
Assets.images.flutterSignPost.keyName,
);
this.sprite = sprite;
size = sprite.originalSize / 10;
anchor = Anchor.bottomCenter;
position = Vector2(0.65, 0.45);
}
}

@ -67,7 +67,7 @@ abstract class RenderPriority {
// Flutter Forest
static const int flutterSignPost = _above + launchRampForegroundRailing;
static const int signpost = _above + launchRampForegroundRailing;
static const int dashBumper = _above + ballOnBoard;

@ -0,0 +1,102 @@
import 'package:flame/components.dart';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart';
import 'package:pinball_components/pinball_components.dart';
/// Represents the [Signpost]'s current [Sprite] state.
@visibleForTesting
enum SignpostSpriteState {
/// Signpost with no active dashes.
inactive,
/// Signpost with a single sign of active dashes.
active1,
/// Signpost with two signs of active dashes.
active2,
/// Signpost with all signs of active dashes.
active3,
}
extension on SignpostSpriteState {
String get path {
switch (this) {
case SignpostSpriteState.inactive:
return Assets.images.signpost.inactive.keyName;
case SignpostSpriteState.active1:
return Assets.images.signpost.active1.keyName;
case SignpostSpriteState.active2:
return Assets.images.signpost.active2.keyName;
case SignpostSpriteState.active3:
return Assets.images.signpost.active3.keyName;
}
}
SignpostSpriteState get next {
return SignpostSpriteState
.values[(index + 1) % SignpostSpriteState.values.length];
}
}
/// {@template signpost}
/// A sign, found in the Flutter Forest.
///
/// Lights up a new sign whenever all three [DashNestBumper]s are hit.
/// {@endtemplate}
class Signpost extends BodyComponent with InitialPosition {
/// {@macro signpost}
Signpost()
: super(
priority: RenderPriority.signpost,
children: [_SignpostSpriteComponent()],
) {
renderBody = false;
}
/// Forwards the sprite to the next [SignpostSpriteState].
///
/// If the current state is the last one it cycles back to the initial state.
void progress() => firstChild<_SignpostSpriteComponent>()!.progress();
@override
Body createBody() {
final shape = CircleShape()..radius = 0.25;
final fixtureDef = FixtureDef(shape);
final bodyDef = BodyDef(
position: initialPosition,
);
return world.createBody(bodyDef)..createFixture(fixtureDef);
}
}
class _SignpostSpriteComponent extends SpriteGroupComponent<SignpostSpriteState>
with HasGameRef {
_SignpostSpriteComponent()
: super(
anchor: Anchor.bottomCenter,
position: Vector2(0.65, 0.45),
);
void progress() => current = current?.next;
@override
Future<void> onLoad() async {
await super.onLoad();
final sprites = <SignpostSpriteState, Sprite>{};
this.sprites = sprites;
for (final spriteState in SignpostSpriteState.values) {
// TODO(allisonryan0002): Support caching
// https://github.com/VGVentures/pinball/pull/204
// sprites[spriteState] = Sprite(
// gameRef.images.fromCache(spriteState.path),
// );
sprites[spriteState] = await gameRef.loadSprite(spriteState.path);
}
current = SignpostSpriteState.inactive;
size = sprites[current]!.originalSize / 10;
}
}

@ -64,6 +64,7 @@ flutter:
- assets/images/sparky/bumper/c/
- assets/images/backboard/
- assets/images/google_word/
- assets/images/signpost/
flutter_gen:
line_length: 80

@ -1,22 +1,30 @@
import 'dart:async';
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame/input.dart';
import 'package:pinball_components/pinball_components.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/ball/basic_ball_game.dart';
class FlutterSignPostGame extends BasicBallGame with Traceable {
class SignpostGame extends BasicBallGame with Traceable, TapDetector {
static const info = '''
Shows how a FlutterSignPost is rendered.
Shows how a Signpost is rendered.
- Activate the "trace" parameter to overlay the body.
- Tap to progress the sprite.
''';
@override
Future<void> onLoad() async {
await super.onLoad();
camera.followVector2(Vector2.zero());
await add(FlutterSignPost()..priority = 1);
await add(Signpost()..priority = 1);
await traceAllBodies();
}
@override
void onTap() {
super.onTap();
firstChild<Signpost>()!.progress();
}
}

@ -2,20 +2,19 @@ import 'package:dashbook/dashbook.dart';
import 'package:flame/game.dart';
import 'package:sandbox/common/common.dart';
import 'package:sandbox/stories/flutter_forest/big_dash_nest_bumper_game.dart';
import 'package:sandbox/stories/flutter_forest/flutter_sign_post_game.dart';
import 'package:sandbox/stories/flutter_forest/signpost_game.dart';
import 'package:sandbox/stories/flutter_forest/small_dash_nest_bumper_a_game.dart';
import 'package:sandbox/stories/flutter_forest/small_dash_nest_bumper_b_game.dart';
void addDashNestBumperStories(Dashbook dashbook) {
dashbook.storiesOf('Flutter Forest')
..add(
'Flutter Sign Post',
'Signpost',
(context) => GameWidget(
game: FlutterSignPostGame()
..trace = context.boolProperty('Trace', true),
game: SignpostGame()..trace = context.boolProperty('Trace', true),
),
codeLink: buildSourceLink('flutter_forest/flutter_sign_post.dart'),
info: FlutterSignPostGame.info,
codeLink: buildSourceLink('flutter_forest/signpost.dart'),
info: SignpostGame.info,
)
..add(
'Big Dash Nest Bumper',

@ -14,7 +14,7 @@ class BasicCameraZoomGame extends BasicGame with TapDetector {
@override
Future<void> onLoad() async {
final sprite = await loadSprite(Assets.images.flutterSignPost.keyName);
final sprite = await loadSprite(Assets.images.signpost.inactive.keyName);
await add(
SpriteComponent(

@ -17,7 +17,7 @@ void main() {
game.camera.followVector2(Vector2.zero());
game.camera.zoom = 10;
final sprite = await game.loadSprite(
Assets.images.flutterSignPost.keyName,
Assets.images.signpost.inactive.keyName,
);
await game.add(

@ -1,40 +0,0 @@
// ignore_for_file: cascade_invocations
import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group('FlutterSignPost', () {
flameTester.testGameWidget(
'renders correctly',
setUp: (game, tester) async {
await game.ensureAdd(FlutterSignPost());
game.camera.followVector2(Vector2.zero());
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/flutter-sign-post.png'),
);
},
);
flameTester.test(
'loads correctly',
(game) async {
final flutterSignPost = FlutterSignPost();
await game.ready();
await game.ensureAdd(flutterSignPost);
expect(game.contains(flutterSignPost), isTrue);
},
);
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

@ -0,0 +1,141 @@
// ignore_for_file: cascade_invocations
import 'package:flame/components.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(TestGame.new);
group('Signpost', () {
flameTester.test(
'loads correctly',
(game) async {
final signpost = Signpost();
await game.ready();
await game.ensureAdd(signpost);
expect(game.contains(signpost), isTrue);
},
);
group('renders correctly', () {
flameTester.testGameWidget(
'inactive sprite',
setUp: (game, tester) async {
final signpost = Signpost();
await game.ensureAdd(signpost);
expect(
signpost.firstChild<SpriteGroupComponent>()!.current,
SignpostSpriteState.inactive,
);
game.camera.followVector2(Vector2.zero());
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/signpost/inactive.png'),
);
},
);
flameTester.testGameWidget(
'active1 sprite',
setUp: (game, tester) async {
final signpost = Signpost();
await game.ensureAdd(signpost);
signpost.progress();
expect(
signpost.firstChild<SpriteGroupComponent>()!.current,
SignpostSpriteState.active1,
);
game.camera.followVector2(Vector2.zero());
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/signpost/active1.png'),
);
},
);
flameTester.testGameWidget(
'active2 sprite',
setUp: (game, tester) async {
final signpost = Signpost();
await game.ensureAdd(signpost);
signpost
..progress()
..progress();
expect(
signpost.firstChild<SpriteGroupComponent>()!.current,
SignpostSpriteState.active2,
);
game.camera.followVector2(Vector2.zero());
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/signpost/active2.png'),
);
},
);
flameTester.testGameWidget(
'active3 sprite',
setUp: (game, tester) async {
final signpost = Signpost();
await game.ensureAdd(signpost);
signpost
..progress()
..progress()
..progress();
expect(
signpost.firstChild<SpriteGroupComponent>()!.current,
SignpostSpriteState.active3,
);
game.camera.followVector2(Vector2.zero());
},
verify: (game, tester) async {
await expectLater(
find.byGame<TestGame>(),
matchesGoldenFile('golden/signpost/active3.png'),
);
},
);
});
flameTester.test(
'progress correctly cycles through all sprites',
(game) async {
final signpost = Signpost();
await game.ready();
await game.ensureAdd(signpost);
final spriteComponent = signpost.firstChild<SpriteGroupComponent>()!;
expect(spriteComponent.current, SignpostSpriteState.inactive);
signpost.progress();
expect(spriteComponent.current, SignpostSpriteState.active1);
signpost.progress();
expect(spriteComponent.current, SignpostSpriteState.active2);
signpost.progress();
expect(spriteComponent.current, SignpostSpriteState.active3);
signpost.progress();
expect(spriteComponent.current, SignpostSpriteState.inactive);
},
);
});
}

@ -27,13 +27,13 @@ void main() {
group('loads', () {
flameTester.test(
'a FlutterSignPost',
'a Signpost',
(game) async {
final flutterForest = FlutterForest();
await game.ensureAdd(flutterForest);
expect(
flutterForest.descendants().whereType<FlutterSignPost>().length,
flutterForest.descendants().whereType<Signpost>().length,
equals(1),
);
},

Loading…
Cancel
Save