feat: implemented FlutterForest controllers

pull/119/head
alestiago 4 years ago
parent 682b6173f2
commit 37d583ba6d

@ -1,11 +1,10 @@
// ignore_for_file: avoid_renaming_method_parameters // ignore_for_file: avoid_renaming_method_parameters
import 'dart:math' as math;
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_bloc/flame_bloc.dart';
import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_forge2d/flame_forge2d.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pinball/flame/flame.dart';
import 'package:pinball/game/game.dart'; import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart'; import 'package:pinball_components/pinball_components.dart';
@ -16,41 +15,27 @@ import 'package:pinball_components/pinball_components.dart';
/// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest] /// When all [DashNestBumper]s are hit at least once, the [GameBonus.dashNest]
/// is awarded, and the [BigDashNestBumper] releases a new [Ball]. /// is awarded, and the [BigDashNestBumper] releases a new [Ball].
/// {@endtemplate} /// {@endtemplate}
// TODO(alestiago): Make a [Blueprint] once nesting [Blueprint] is implemented. // TODO(alestiago): Make a [Blueprint] once [Blueprint] inherits from [Component].
class FlutterForest extends Component class FlutterForest extends Component
with HasGameRef<PinballGame>, BlocComponent<GameBloc, GameState> { with Controls<FlutterForestController>, HasGameRef<PinballGame> {
/// {@macro flutter_forest} /// {@macro flutter_forest}
FlutterForest() {
@override controller = FlutterForestController(this);
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);
add(
ControlledBall.bonus(
theme: gameRef.theme,
)..initialPosition = Vector2(17.2, 52.7),
);
} }
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
gameRef.addContactCallback(DashNestBumperBallContactCallback()); await super.onLoad();
final signPost = FlutterSignPost()..initialPosition = Vector2(8.35, 58.3); final signPost = FlutterSignPost()..initialPosition = Vector2(8.35, 58.3);
final bigNest = BigDashNestBumper(id: 'big_nest_bumper') final bigNest = ControlledBigDashNestBumper(id: 'big_nest_bumper')
..initialPosition = Vector2(18.55, 59.35); ..initialPosition = Vector2(18.55, 59.35);
final smallLeftNest = SmallDashNestBumper.a(id: 'small_nest_bumper_a') final smallLeftNest =
..initialPosition = Vector2(8.95, 51.95); ControlledSmallDashNestBumper.a(id: 'small_nest_bumper_a')
final smallRightNest = SmallDashNestBumper.b(id: 'small_nest_bumper_b') ..initialPosition = Vector2(8.95, 51.95);
..initialPosition = Vector2(23.3, 46.75); final smallRightNest =
ControlledSmallDashNestBumper.b(id: 'small_nest_bumper_b')
..initialPosition = Vector2(23.3, 46.75);
await addAll([ await addAll([
signPost, signPost,
@ -61,193 +46,125 @@ class FlutterForest extends Component
} }
} }
/// {@template dash_nest_bumper} /// {@template flutter_forest_controller}
/// Bumper located in the [FlutterForest]. ///
/// {@endtemplate} /// {@endtemplate}
@visibleForTesting class FlutterForestController extends ComponentController<FlutterForest>
abstract class DashNestBumper extends BodyComponent<PinballGame> with BlocComponent<GameBloc, GameState>, HasGameRef<PinballGame> {
with ScorePoints, InitialPosition, BlocComponent<GameBloc, GameState> { /// {@macro flutter_forest_controller}
/// {@macro dash_nest_bumper} FlutterForestController(FlutterForest flutterForest) : super(flutterForest);
DashNestBumper({
required this.id,
required String activeAssetPath,
required String inactiveAssetPath,
required SpriteComponent spriteComponent,
}) : _activeAssetPath = activeAssetPath,
_inactiveAssetPath = inactiveAssetPath,
_spriteComponent = spriteComponent;
/// Unique identifier for this [DashNestBumper].
///
/// Used to identify [DashNestBumper]s in [GameState.activatedDashNests].
final String id;
final String _activeAssetPath;
late final Sprite _activeSprite;
final String _inactiveAssetPath;
late final Sprite _inactiveSprite;
final SpriteComponent _spriteComponent;
Future<void> _loadSprites() async {
// TODO(alestiago): I think ideally we would like to do:
// Sprite(path).load so we don't require to store the activeAssetPath and
// the inactive assetPath.
_inactiveSprite = await gameRef.loadSprite(_inactiveAssetPath);
_activeSprite = await gameRef.loadSprite(_activeAssetPath);
}
/// Activates the [DashNestBumper]. @override
void activate() { Future<void> onLoad() async {
_spriteComponent await super.onLoad();
..sprite = _activeSprite gameRef.addContactCallback(ControlledDashNestBumperBallContactCallback());
..size = _activeSprite.originalSize / 10;
}
/// Deactivates the [DashNestBumper].
void deactivate() {
_spriteComponent
..sprite = _inactiveSprite
..size = _inactiveSprite.originalSize / 10;
} }
@override @override
bool listenWhen(GameState? previousState, GameState newState) { bool listenWhen(GameState? previousState, GameState newState) {
final wasActive = previousState?.activatedDashNests.contains(id) ?? false; return (previousState?.bonusHistory.length ?? 0) <
final isActive = newState.activatedDashNests.contains(id); newState.bonusHistory.length &&
newState.bonusHistory.last == GameBonus.dashNest;
return wasActive != isActive;
} }
@override @override
void onNewState(GameState state) { void onNewState(GameState state) {
super.onNewState(state); super.onNewState(state);
if (state.activatedDashNests.contains(id)) { component.add(
activate(); ControlledBall.bonus(theme: gameRef.theme)
} else { ..initialPosition = Vector2(17.2, 52.7),
deactivate(); );
} }
}
/// {@template controlled_big_dash_nest_bumper}
/// A [BigDashNestBumper] controlled by a [DashNestBumperController].
/// {@endtemplate}
class ControlledBigDashNestBumper extends BigDashNestBumper
with Controls<DashNestBumperController>, ScorePoints {
/// {@macro controlled_big_dash_nest_bumper}
ControlledBigDashNestBumper({required String id}) : super() {
controller = DashNestBumperController(this, id: id);
} }
@override @override
Future<void> onLoad() async { int get points => 20;
await super.onLoad(); }
await _loadSprites();
/// {@template controlled_small_dash_nest_bumper}
/// A [SmallDashNestBumper] controlled by a [DashNestBumperController].
/// {@endtemplate}
class ControlledSmallDashNestBumper extends SmallDashNestBumper
with Controls<DashNestBumperController>, ScorePoints {
/// {@macro controlled_small_dash_nest_bumper}
ControlledSmallDashNestBumper.a({required String id}) : super.a() {
controller = DashNestBumperController(this, id: id);
}
// TODO(erickzanardo): Look into using onNewState instead. /// {@macro controlled_small_dash_nest_bumper}
// Currently doing: onNewState(gameRef.read<GameState>()) will throw an ControlledSmallDashNestBumper.b({required String id}) : super.b() {
// `Exception: build context is not available yet` controller = DashNestBumperController(this, id: id);
deactivate();
await add(_spriteComponent);
} }
}
/// Listens when a [Ball] bounces bounces against a [DashNestBumper].
@visibleForTesting
class DashNestBumperBallContactCallback
extends ContactCallback<DashNestBumper, Ball> {
@override @override
void begin(DashNestBumper dashNestBumper, Ball _, Contact __) { int get points => 10;
dashNestBumper.gameRef.read<GameBloc>().add(
DashNestActivated(dashNestBumper.id),
);
}
} }
/// {@macro dash_nest_bumper} /// {@template dash_nest_bumper_controller}
@visibleForTesting /// Controls a [DashNestBumper].
class BigDashNestBumper extends DashNestBumper { /// {@endtemplate}
/// {@macro dash_nest_bumper} class DashNestBumperController extends ComponentController<DashNestBumper>
BigDashNestBumper({required String id}) with BlocComponent<GameBloc, GameState>, HasGameRef<PinballGame> {
: super( /// {@macro dash_nest_bumper_controller}
id: id, DashNestBumperController(
activeAssetPath: Assets.images.dashBumper.main.active.keyName, DashNestBumper dashNestBumper, {
inactiveAssetPath: Assets.images.dashBumper.main.inactive.keyName, required String id,
spriteComponent: SpriteComponent( }) : _id = id,
anchor: Anchor.center, super(dashNestBumper);
),
); /// Unique identifier for the controlled [DashNestBumper].
///
/// Used to identify [DashNestBumper]s in [GameState.activatedDashNests].
final String _id;
@override @override
int get points => 20; bool listenWhen(GameState? previousState, GameState newState) {
final wasActive = previousState?.activatedDashNests.contains(_id) ?? false;
final isActive = newState.activatedDashNests.contains(_id);
return wasActive != isActive;
}
@override @override
Body createBody() { void onNewState(GameState state) {
final shape = EllipseShape( super.onNewState(state);
center: Vector2.zero(),
majorRadius: 4.85, if (state.activatedDashNests.contains(_id)) {
minorRadius: 3.95, component.activate();
)..rotate(math.pi / 2); } else {
final fixtureDef = FixtureDef(shape); component.deactivate();
}
final bodyDef = BodyDef() }
..position = initialPosition
..userData = this; /// Registers when a [DashNestBumper] is hit by a [Ball].
///
return world.createBody(bodyDef)..createFixture(fixtureDef); /// Triggered by [ControlledDashNestBumperBallContactCallback].
void hit() {
gameRef.read<GameBloc>().add(DashNestActivated(_id));
} }
} }
/// {@macro dash_nest_bumper} /// Listens when a [Ball] bounces bounces against a [DashNestBumper]
@visibleForTesting @visibleForTesting
class SmallDashNestBumper extends DashNestBumper { class ControlledDashNestBumperBallContactCallback
/// {@macro dash_nest_bumper} extends ContactCallback<Controls<DashNestBumperController>, Ball> {
SmallDashNestBumper._({
required String id,
required String activeAssetPath,
required String inactiveAssetPath,
required SpriteComponent spriteComponent,
}) : super(
id: id,
activeAssetPath: activeAssetPath,
inactiveAssetPath: inactiveAssetPath,
spriteComponent: spriteComponent,
);
/// {@macro dash_nest_bumper}
SmallDashNestBumper.a({
required String id,
}) : this._(
id: id,
activeAssetPath: Assets.images.dashBumper.a.active.keyName,
inactiveAssetPath: Assets.images.dashBumper.a.inactive.keyName,
spriteComponent: SpriteComponent(
anchor: Anchor.center,
position: Vector2(0.35, -1.2),
),
);
/// {@macro dash_nest_bumper}
SmallDashNestBumper.b({
required String id,
}) : this._(
id: id,
activeAssetPath: Assets.images.dashBumper.b.active.keyName,
inactiveAssetPath: Assets.images.dashBumper.b.inactive.keyName,
spriteComponent: SpriteComponent(
anchor: Anchor.center,
position: Vector2(0.35, -1.2),
),
);
@override
int get points => 10;
@override @override
Body createBody() { void begin(
final shape = EllipseShape( Controls<DashNestBumperController> controlledDashNestBumper,
center: Vector2.zero(), Ball _,
majorRadius: 3, Contact __,
minorRadius: 2.25, ) {
)..rotate(math.pi / 2); controlledDashNestBumper.controller.hit();
final fixtureDef = FixtureDef(shape)
..friction = 0
..restitution = 4;
final bodyDef = BodyDef()
..position = initialPosition
..userData = this;
return world.createBody(bodyDef)..createFixture(fixtureDef);
} }
} }

Loading…
Cancel
Save