feat: implemented new `FlutterForestBonusBehavior` logic (#303)
* refactor: moved Signpost to own folder * feat: defined SignpostCubit * feat: implemented new FlutterForestBonus logic * chore: changed assets * test: updated Signpost test and goldens * feat: updated zoom effect goldens * feat: adjusted signpost_test * refactor: defined isFullyProgressed method * test: tested FlutterForestBonusBehavior * refactor: uncommented GameHud * docs: updated FlutterForestBonusBehavior * test: used canvas * docs: enhanced documentation * refactor: swapped active and inactive assets * test: included canvaspull/307/head
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 45 KiB |
@ -1,101 +0,0 @@
|
|||||||
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({
|
|
||||||
Iterable<Component>? children,
|
|
||||||
}) : super(
|
|
||||||
renderBody: false,
|
|
||||||
children: [
|
|
||||||
_SignpostSpriteComponent(),
|
|
||||||
...?children,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
/// 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) {
|
|
||||||
sprites[spriteState] = Sprite(
|
|
||||||
gameRef.images.fromCache(spriteState.path),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
current = SignpostSpriteState.inactive;
|
|
||||||
size = sprites[current]!.originalSize / 10;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,18 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
import 'package:bloc/bloc.dart';
|
||||||
|
|
||||||
|
part 'signpost_state.dart';
|
||||||
|
|
||||||
|
class SignpostCubit extends Cubit<SignpostState> {
|
||||||
|
SignpostCubit() : super(SignpostState.inactive);
|
||||||
|
|
||||||
|
void onProgressed() {
|
||||||
|
final index = SignpostState.values.indexOf(state);
|
||||||
|
emit(
|
||||||
|
SignpostState.values[(index + 1) % SignpostState.values.length],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFullyProgressed() => state == SignpostState.active3;
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
|
part of 'signpost_cubit.dart';
|
||||||
|
|
||||||
|
enum SignpostState {
|
||||||
|
/// Signpost with no active eggs.
|
||||||
|
inactive,
|
||||||
|
|
||||||
|
/// Signpost with a single sign of lit up eggs.
|
||||||
|
active1,
|
||||||
|
|
||||||
|
/// Signpost with two signs of lit up eggs.
|
||||||
|
active2,
|
||||||
|
|
||||||
|
/// Signpost with all signs of lit up eggs.
|
||||||
|
active3,
|
||||||
|
}
|
@ -0,0 +1,109 @@
|
|||||||
|
import 'package:flame/components.dart';
|
||||||
|
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
import 'package:pinball_flame/pinball_flame.dart';
|
||||||
|
|
||||||
|
export 'cubit/signpost_cubit.dart';
|
||||||
|
|
||||||
|
/// {@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({
|
||||||
|
Iterable<Component>? children,
|
||||||
|
}) : this._(
|
||||||
|
children: children,
|
||||||
|
bloc: SignpostCubit(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Signpost._({
|
||||||
|
Iterable<Component>? children,
|
||||||
|
required this.bloc,
|
||||||
|
}) : super(
|
||||||
|
renderBody: false,
|
||||||
|
children: [
|
||||||
|
_SignpostSpriteComponent(
|
||||||
|
current: bloc.state,
|
||||||
|
),
|
||||||
|
...?children,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Creates a [Signpost] without any children.
|
||||||
|
///
|
||||||
|
/// This can be used for testing [Signpost]'s behaviors in isolation.
|
||||||
|
// TODO(alestiago): Refactor injecting bloc once the following is merged:
|
||||||
|
// https://github.com/flame-engine/flame/pull/1538
|
||||||
|
@visibleForTesting
|
||||||
|
Signpost.test({
|
||||||
|
required this.bloc,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO(alestiago): Consider refactoring once the following is merged:
|
||||||
|
// https://github.com/flame-engine/flame/pull/1538
|
||||||
|
// ignore: public_member_api_docs
|
||||||
|
final SignpostCubit bloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onRemove() {
|
||||||
|
bloc.close();
|
||||||
|
super.onRemove();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Body createBody() {
|
||||||
|
final shape = CircleShape()..radius = 0.25;
|
||||||
|
final bodyDef = BodyDef(
|
||||||
|
position: initialPosition,
|
||||||
|
);
|
||||||
|
|
||||||
|
return world.createBody(bodyDef)..createFixtureFromShape(shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SignpostSpriteComponent extends SpriteGroupComponent<SignpostState>
|
||||||
|
with HasGameRef, ParentIsA<Signpost> {
|
||||||
|
_SignpostSpriteComponent({
|
||||||
|
required SignpostState current,
|
||||||
|
}) : super(
|
||||||
|
anchor: Anchor.bottomCenter,
|
||||||
|
position: Vector2(0.65, 0.45),
|
||||||
|
current: current,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onLoad() async {
|
||||||
|
await super.onLoad();
|
||||||
|
parent.bloc.stream.listen((state) => current = state);
|
||||||
|
|
||||||
|
final sprites = <SignpostState, Sprite>{};
|
||||||
|
this.sprites = sprites;
|
||||||
|
for (final spriteState in SignpostState.values) {
|
||||||
|
sprites[spriteState] = Sprite(
|
||||||
|
gameRef.images.fromCache(spriteState.path),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
current = SignpostState.inactive;
|
||||||
|
size = sprites[current]!.originalSize / 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on SignpostState {
|
||||||
|
String get path {
|
||||||
|
switch (this) {
|
||||||
|
case SignpostState.inactive:
|
||||||
|
return Assets.images.signpost.inactive.keyName;
|
||||||
|
case SignpostState.active1:
|
||||||
|
return Assets.images.signpost.active1.keyName;
|
||||||
|
case SignpostState.active2:
|
||||||
|
return Assets.images.signpost.active2.keyName;
|
||||||
|
case SignpostState.active3:
|
||||||
|
return Assets.images.signpost.active3.keyName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:pinball_components/pinball_components.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('SignpostCubit', () {
|
||||||
|
blocTest<SignpostCubit, SignpostState>(
|
||||||
|
'onProgressed progresses',
|
||||||
|
build: SignpostCubit.new,
|
||||||
|
act: (bloc) {
|
||||||
|
bloc
|
||||||
|
..onProgressed()
|
||||||
|
..onProgressed()
|
||||||
|
..onProgressed()
|
||||||
|
..onProgressed();
|
||||||
|
},
|
||||||
|
expect: () => [
|
||||||
|
SignpostState.active1,
|
||||||
|
SignpostState.active2,
|
||||||
|
SignpostState.active3,
|
||||||
|
SignpostState.inactive,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
test('isFullyProgressed when on active3', () {
|
||||||
|
final bloc = SignpostCubit();
|
||||||
|
expect(bloc.isFullyProgressed(), isFalse);
|
||||||
|
|
||||||
|
bloc.onProgressed();
|
||||||
|
expect(bloc.isFullyProgressed(), isFalse);
|
||||||
|
|
||||||
|
bloc.onProgressed();
|
||||||
|
expect(bloc.isFullyProgressed(), isFalse);
|
||||||
|
|
||||||
|
bloc.onProgressed();
|
||||||
|
expect(bloc.isFullyProgressed(), isTrue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|