From 37d583ba6dae8b554162daf0c56afafa558fcce5 Mon Sep 17 00:00:00 2001 From: alestiago Date: Thu, 31 Mar 2022 17:34:17 +0100 Subject: [PATCH] feat: implemented FlutterForest controllers --- lib/game/components/flutter_forest.dart | 289 +++++++++--------------- 1 file changed, 103 insertions(+), 186 deletions(-) diff --git a/lib/game/components/flutter_forest.dart b/lib/game/components/flutter_forest.dart index 37ae6229..ad6cfea1 100644 --- a/lib/game/components/flutter_forest.dart +++ b/lib/game/components/flutter_forest.dart @@ -1,11 +1,10 @@ // ignore_for_file: avoid_renaming_method_parameters -import 'dart:math' as math; - import 'package:flame/components.dart'; import 'package:flame_bloc/flame_bloc.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flutter/material.dart'; +import 'package:pinball/flame/flame.dart'; import 'package:pinball/game/game.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] /// is awarded, and the [BigDashNestBumper] releases a new [Ball]. /// {@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 - with HasGameRef, BlocComponent { + with Controls, HasGameRef { /// {@macro flutter_forest} - - @override - 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), - ); + FlutterForest() { + controller = FlutterForestController(this); } @override Future onLoad() async { - gameRef.addContactCallback(DashNestBumperBallContactCallback()); - + await super.onLoad(); 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); - final smallLeftNest = SmallDashNestBumper.a(id: 'small_nest_bumper_a') - ..initialPosition = Vector2(8.95, 51.95); - final smallRightNest = SmallDashNestBumper.b(id: 'small_nest_bumper_b') - ..initialPosition = Vector2(23.3, 46.75); + final smallLeftNest = + ControlledSmallDashNestBumper.a(id: 'small_nest_bumper_a') + ..initialPosition = Vector2(8.95, 51.95); + final smallRightNest = + ControlledSmallDashNestBumper.b(id: 'small_nest_bumper_b') + ..initialPosition = Vector2(23.3, 46.75); await addAll([ signPost, @@ -61,193 +46,125 @@ class FlutterForest extends Component } } -/// {@template dash_nest_bumper} -/// Bumper located in the [FlutterForest]. +/// {@template flutter_forest_controller} +/// /// {@endtemplate} -@visibleForTesting -abstract class DashNestBumper extends BodyComponent - with ScorePoints, InitialPosition, BlocComponent { - /// {@macro dash_nest_bumper} - 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 _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); - } +class FlutterForestController extends ComponentController + with BlocComponent, HasGameRef { + /// {@macro flutter_forest_controller} + FlutterForestController(FlutterForest flutterForest) : super(flutterForest); - /// Activates the [DashNestBumper]. - void activate() { - _spriteComponent - ..sprite = _activeSprite - ..size = _activeSprite.originalSize / 10; - } - - /// Deactivates the [DashNestBumper]. - void deactivate() { - _spriteComponent - ..sprite = _inactiveSprite - ..size = _inactiveSprite.originalSize / 10; + @override + Future onLoad() async { + await super.onLoad(); + gameRef.addContactCallback(ControlledDashNestBumperBallContactCallback()); } @override bool listenWhen(GameState? previousState, GameState newState) { - final wasActive = previousState?.activatedDashNests.contains(id) ?? false; - final isActive = newState.activatedDashNests.contains(id); - - return wasActive != isActive; + return (previousState?.bonusHistory.length ?? 0) < + newState.bonusHistory.length && + newState.bonusHistory.last == GameBonus.dashNest; } @override void onNewState(GameState state) { super.onNewState(state); - if (state.activatedDashNests.contains(id)) { - activate(); - } else { - deactivate(); - } + component.add( + ControlledBall.bonus(theme: gameRef.theme) + ..initialPosition = Vector2(17.2, 52.7), + ); + } +} + +/// {@template controlled_big_dash_nest_bumper} +/// A [BigDashNestBumper] controlled by a [DashNestBumperController]. +/// {@endtemplate} +class ControlledBigDashNestBumper extends BigDashNestBumper + with Controls, ScorePoints { + /// {@macro controlled_big_dash_nest_bumper} + ControlledBigDashNestBumper({required String id}) : super() { + controller = DashNestBumperController(this, id: id); } @override - Future onLoad() async { - await super.onLoad(); - await _loadSprites(); + int get points => 20; +} + +/// {@template controlled_small_dash_nest_bumper} +/// A [SmallDashNestBumper] controlled by a [DashNestBumperController]. +/// {@endtemplate} +class ControlledSmallDashNestBumper extends SmallDashNestBumper + with Controls, 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. - // Currently doing: onNewState(gameRef.read()) will throw an - // `Exception: build context is not available yet` - deactivate(); - await add(_spriteComponent); + /// {@macro controlled_small_dash_nest_bumper} + ControlledSmallDashNestBumper.b({required String id}) : super.b() { + controller = DashNestBumperController(this, id: id); } -} -/// Listens when a [Ball] bounces bounces against a [DashNestBumper]. -@visibleForTesting -class DashNestBumperBallContactCallback - extends ContactCallback { @override - void begin(DashNestBumper dashNestBumper, Ball _, Contact __) { - dashNestBumper.gameRef.read().add( - DashNestActivated(dashNestBumper.id), - ); - } + int get points => 10; } -/// {@macro dash_nest_bumper} -@visibleForTesting -class BigDashNestBumper extends DashNestBumper { - /// {@macro dash_nest_bumper} - BigDashNestBumper({required String id}) - : super( - id: id, - activeAssetPath: Assets.images.dashBumper.main.active.keyName, - inactiveAssetPath: Assets.images.dashBumper.main.inactive.keyName, - spriteComponent: SpriteComponent( - anchor: Anchor.center, - ), - ); +/// {@template dash_nest_bumper_controller} +/// Controls a [DashNestBumper]. +/// {@endtemplate} +class DashNestBumperController extends ComponentController + with BlocComponent, HasGameRef { + /// {@macro dash_nest_bumper_controller} + DashNestBumperController( + DashNestBumper dashNestBumper, { + required String id, + }) : _id = id, + super(dashNestBumper); + + /// Unique identifier for the controlled [DashNestBumper]. + /// + /// Used to identify [DashNestBumper]s in [GameState.activatedDashNests]. + final String _id; @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 - Body createBody() { - final shape = EllipseShape( - center: Vector2.zero(), - majorRadius: 4.85, - minorRadius: 3.95, - )..rotate(math.pi / 2); - final fixtureDef = FixtureDef(shape); - - final bodyDef = BodyDef() - ..position = initialPosition - ..userData = this; - - return world.createBody(bodyDef)..createFixture(fixtureDef); + void onNewState(GameState state) { + super.onNewState(state); + + if (state.activatedDashNests.contains(_id)) { + component.activate(); + } else { + component.deactivate(); + } + } + + /// Registers when a [DashNestBumper] is hit by a [Ball]. + /// + /// Triggered by [ControlledDashNestBumperBallContactCallback]. + void hit() { + gameRef.read().add(DashNestActivated(_id)); } } -/// {@macro dash_nest_bumper} +/// Listens when a [Ball] bounces bounces against a [DashNestBumper] @visibleForTesting -class SmallDashNestBumper extends DashNestBumper { - /// {@macro dash_nest_bumper} - 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; - +class ControlledDashNestBumperBallContactCallback + extends ContactCallback, Ball> { @override - Body createBody() { - final shape = EllipseShape( - center: Vector2.zero(), - majorRadius: 3, - minorRadius: 2.25, - )..rotate(math.pi / 2); - final fixtureDef = FixtureDef(shape) - ..friction = 0 - ..restitution = 4; - - final bodyDef = BodyDef() - ..position = initialPosition - ..userData = this; - - return world.createBody(bodyDef)..createFixture(fixtureDef); + void begin( + Controls controlledDashNestBumper, + Ball _, + Contact __, + ) { + controlledDashNestBumper.controller.hit(); } }