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
Before Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 15 KiB |
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,30 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
import 'package:flame/input.dart';
|
||||||
import 'package:pinball_components/pinball_components.dart';
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
import 'package:sandbox/common/common.dart';
|
import 'package:sandbox/common/common.dart';
|
||||||
import 'package:sandbox/stories/ball/basic_ball_game.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 = '''
|
static const info = '''
|
||||||
Shows how a FlutterSignPost is rendered.
|
Shows how a Signpost is rendered.
|
||||||
|
|
||||||
- Activate the "trace" parameter to overlay the body.
|
- Activate the "trace" parameter to overlay the body.
|
||||||
|
- Tap to progress the sprite.
|
||||||
''';
|
''';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onLoad() async {
|
Future<void> onLoad() async {
|
||||||
await super.onLoad();
|
await super.onLoad();
|
||||||
|
|
||||||
camera.followVector2(Vector2.zero());
|
camera.followVector2(Vector2.zero());
|
||||||
await add(FlutterSignPost()..priority = 1);
|
await add(Signpost()..priority = 1);
|
||||||
await traceAllBodies();
|
await traceAllBodies();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTap() {
|
||||||
|
super.onTap();
|
||||||
|
firstChild<Signpost>()!.progress();
|
||||||
|
}
|
||||||
}
|
}
|
@ -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);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 42 KiB |
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);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|