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);
|
||||
});
|
||||
});
|
||||
}
|