refactor: simplified CameraFocusingBehavior

pull/346/head
alestiago 3 years ago
parent 11c5a3b8d3
commit 801deb9ffa

@ -6,7 +6,7 @@ import 'package:pinball_components/pinball_components.dart';
import 'package:pinball_flame/pinball_flame.dart'; import 'package:pinball_flame/pinball_flame.dart';
/// {@template focus_data} /// {@template focus_data}
/// Model class that defines a focus point of the camera. /// Defines a [Camera] focus point.
/// {@endtemplate} /// {@endtemplate}
class FocusData { class FocusData {
/// {@template focus_data} /// {@template focus_data}
@ -22,31 +22,33 @@ class FocusData {
final Vector2 position; final Vector2 position;
} }
/// /// Changes the game focus when the [GameBloc] status changes.
class CameraFocusingBehavior extends Component class CameraFocusingBehavior extends Component
with ParentIsA<FlameGame>, BlocComponent<GameBloc, GameState> { with ParentIsA<FlameGame>, BlocComponent<GameBloc, GameState> {
final Map<String, FocusData> _focuses = {}; final Map<String, FocusData> _focuses = {};
// @override @override
// bool listenWhen(GameState? previousState, GameState newState) { bool listenWhen(GameState? previousState, GameState newState) {
// print('listen'); return previousState?.status != newState.status;
// return true; }
// return previousState?.isGameOver != newState.isGameOver;
// }
@override @override
void onNewState(GameState state) { void onNewState(GameState state) {
print(state); switch (state.status) {
if (state.isGameOver) { case GameStatus.waiting:
_zoom(_focuses['backbox']!); break;
} else { case GameStatus.playing:
_zoom(_focuses['game']!); _zoom(_focuses['game']!);
break;
case GameStatus.gameOver:
_zoom(_focuses['backbox']!);
break;
} }
} }
@override @override
void onGameResize(Vector2 size) { Future<void> onLoad() async {
super.onGameResize(size); await super.onLoad();
_focuses['game'] = FocusData( _focuses['game'] = FocusData(
zoom: parent.size.y / 16, zoom: parent.size.y / 16,
position: Vector2(0, -7.8), position: Vector2(0, -7.8),
@ -59,11 +61,7 @@ class CameraFocusingBehavior extends Component
zoom: parent.size.y / 10, zoom: parent.size.y / 10,
position: Vector2(0, -111), position: Vector2(0, -111),
); );
}
@override
Future<void> onLoad() async {
await super.onLoad();
_snap(_focuses['waiting']!); _snap(_focuses['waiting']!);
} }
@ -79,6 +77,6 @@ class CameraFocusingBehavior extends Component
zoom.completed.then((_) { zoom.completed.then((_) {
parent.camera.moveTo(data.position); parent.camera.moveTo(data.position);
}); });
parent.add(zoom); add(zoom);
} }
} }

@ -53,7 +53,7 @@ class GameState extends Equatable {
totalScore = 0, totalScore = 0,
roundScore = 0, roundScore = 0,
multiplier = 1, multiplier = 1,
rounds = 3, rounds = 1,
bonusHistory = const []; bonusHistory = const [];
/// The score for the current round of the game. /// The score for the current round of the game.

@ -18,7 +18,6 @@ class GameBlocStatusListener extends Component
break; break;
case GameStatus.playing: case GameStatus.playing:
gameRef.player.play(PinballAudio.backgroundMusic); gameRef.player.play(PinballAudio.backgroundMusic);
gameRef.firstChild<CameraController>()?.focusOnGame();
gameRef.overlays.remove(PinballGame.playButtonOverlay); gameRef.overlays.remove(PinballGame.playButtonOverlay);
break; break;
case GameStatus.gameOver: case GameStatus.gameOver:
@ -26,7 +25,6 @@ class GameBlocStatusListener extends Component
score: state.displayScore, score: state.displayScore,
characterIconPath: gameRef.characterTheme.leaderboardIcon.keyName, characterIconPath: gameRef.characterTheme.leaderboardIcon.keyName,
); );
gameRef.firstChild<CameraController>()!.focusOnGameOverBackbox();
break; break;
} }
} }

@ -0,0 +1,122 @@
// ignore_for_file: cascade_invocations
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/behaviors/camera_focusing_behavior.dart';
import 'package:pinball/game/game.dart';
import 'package:pinball_components/pinball_components.dart';
import '../../helpers/helpers.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group(
'CameraFocusingBehavior',
() {
final flameTester = FlameTester(
EmptyPinballTestGame.new,
);
test('can be instantiated', () {
expect(
CameraFocusingBehavior(),
isA<CameraFocusingBehavior>(),
);
});
flameTester.test('loads', (game) async {
final behavior = CameraFocusingBehavior();
await game.ensureAdd(behavior);
expect(game.contains(behavior), isTrue);
});
flameTester.test(
'changes focus when loaded',
(game) async {
final behavior = CameraFocusingBehavior();
final previousZoom = game.camera.zoom;
expect(game.camera.follow, isNull);
await game.ensureAdd(behavior);
expect(game.camera.follow, isNotNull);
expect(game.camera.zoom, isNot(equals(previousZoom)));
},
);
flameTester.test(
'listenWhen only listens when status changes',
(game) async {
final behavior = CameraFocusingBehavior();
const waiting = GameState.initial();
final playing =
const GameState.initial().copyWith(status: GameStatus.playing);
final gameOver =
const GameState.initial().copyWith(status: GameStatus.gameOver);
expect(behavior.listenWhen(waiting, waiting), isFalse);
expect(behavior.listenWhen(waiting, playing), isTrue);
expect(behavior.listenWhen(waiting, gameOver), isTrue);
expect(behavior.listenWhen(playing, playing), isFalse);
expect(behavior.listenWhen(playing, waiting), isTrue);
expect(behavior.listenWhen(playing, gameOver), isTrue);
expect(behavior.listenWhen(gameOver, gameOver), isFalse);
expect(behavior.listenWhen(gameOver, waiting), isTrue);
expect(behavior.listenWhen(gameOver, playing), isTrue);
},
);
group('onNewState', () {
flameTester.test(
'zooms when started playing',
(game) async {
final playing =
const GameState.initial().copyWith(status: GameStatus.playing);
final behavior = CameraFocusingBehavior();
await game.ensureAdd(behavior);
behavior.onNewState(playing);
final previousPosition = game.camera.position.clone();
await game.ready();
final zoom = behavior.children.whereType<CameraZoom>().single;
game.update(zoom.controller.duration!);
expect(
game.camera.position,
isNot(equals(previousPosition)),
);
},
);
flameTester.test(
'zooms when lost',
(game) async {
final playing = const GameState.initial().copyWith(
status: GameStatus.gameOver,
);
final behavior = CameraFocusingBehavior();
await game.ensureAdd(behavior);
behavior.onNewState(playing);
final previousPosition = game.camera.position.clone();
await game.ready();
final zoom = behavior.children.whereType<CameraZoom>().single;
game.update(zoom.controller.duration!);
expect(
game.camera.position,
isNot(equals(previousPosition)),
);
},
);
});
},
);
}

@ -1,113 +0,0 @@
// ignore_for_file: cascade_invocations
import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pinball/game/components/camera_controller.dart';
import 'package:pinball_components/pinball_components.dart';
void main() {
group('CameraController', () {
late FlameGame game;
late CameraController controller;
setUp(() async {
game = FlameGame()..onGameResize(Vector2(100, 200));
controller = CameraController(game);
await game.ensureAdd(controller);
});
test('loads correctly', () async {
expect(game.firstChild<CameraController>(), isNotNull);
});
test('correctly calculates the zooms', () async {
expect(controller.gameFocus.zoom.toInt(), equals(12));
expect(controller.waitingBackboxFocus.zoom.toInt(), equals(11));
});
test('correctly sets the initial zoom and position', () async {
expect(game.camera.zoom, equals(controller.waitingBackboxFocus.zoom));
expect(
game.camera.follow,
equals(controller.waitingBackboxFocus.position),
);
});
group('focusOnGame', () {
test('changes the zoom', () async {
controller.focusOnGame();
await game.ready();
final zoom = game.firstChild<CameraZoom>();
expect(zoom, isNotNull);
expect(zoom?.value, equals(controller.gameFocus.zoom));
});
test('moves the camera after the zoom is completed', () async {
controller.focusOnGame();
await game.ready();
final cameraZoom = game.firstChild<CameraZoom>()!;
final future = cameraZoom.completed;
game.update(10);
game.update(0); // Ensure that the component was removed
await future;
expect(game.camera.position, Vector2(-4, -120));
});
});
group('focusOnWaitingBackbox', () {
test('changes the zoom', () async {
controller.focusOnWaitingBackbox();
await game.ready();
final zoom = game.firstChild<CameraZoom>();
expect(zoom, isNotNull);
expect(zoom?.value, equals(controller.waitingBackboxFocus.zoom));
});
test('moves the camera after the zoom is completed', () async {
controller.focusOnWaitingBackbox();
await game.ready();
final cameraZoom = game.firstChild<CameraZoom>()!;
final future = cameraZoom.completed;
game.update(10);
game.update(0); // Ensure that the component was removed
await future;
expect(game.camera.position, Vector2(-4.5, -121));
});
});
group('focusOnGameOverBackbox', () {
test('changes the zoom', () async {
controller.focusOnGameOverBackbox();
await game.ready();
final zoom = game.firstChild<CameraZoom>();
expect(zoom, isNotNull);
expect(zoom?.value, equals(controller.gameOverBackboxFocus.zoom));
});
test('moves the camera after the zoom is completed', () async {
controller.focusOnGameOverBackbox();
await game.ready();
final cameraZoom = game.firstChild<CameraZoom>()!;
final future = cameraZoom.completed;
game.update(10);
game.update(0); // Ensure that the component was removed
await future;
expect(game.camera.position, Vector2(-2.5, -117));
});
});
});
}

@ -11,8 +11,6 @@ class _MockPinballGame extends Mock implements PinballGame {}
class _MockBackbox extends Mock implements Backbox {} class _MockBackbox extends Mock implements Backbox {}
class _MockCameraController extends Mock implements CameraController {}
class _MockActiveOverlaysNotifier extends Mock class _MockActiveOverlaysNotifier extends Mock
implements ActiveOverlaysNotifier {} implements ActiveOverlaysNotifier {}
@ -42,20 +40,18 @@ void main() {
group('onNewState', () { group('onNewState', () {
late PinballGame game; late PinballGame game;
late Backbox backbox; late Backbox backbox;
late CameraController cameraController; late GameBlocStatusListener gameBlocStatusListener;
late GameBlocStatusListener gameFlowController;
late PinballPlayer pinballPlayer; late PinballPlayer pinballPlayer;
late ActiveOverlaysNotifier overlays; late ActiveOverlaysNotifier overlays;
setUp(() { setUp(() {
game = _MockPinballGame(); game = _MockPinballGame();
backbox = _MockBackbox(); backbox = _MockBackbox();
cameraController = _MockCameraController(); gameBlocStatusListener = GameBlocStatusListener();
gameFlowController = GameBlocStatusListener();
overlays = _MockActiveOverlaysNotifier(); overlays = _MockActiveOverlaysNotifier();
pinballPlayer = _MockPinballPlayer(); pinballPlayer = _MockPinballPlayer();
gameFlowController.mockGameRef(game); gameBlocStatusListener.mockGameRef(game);
when( when(
() => backbox.initialsInput( () => backbox.initialsInput(
@ -64,22 +60,18 @@ void main() {
onSubmit: any(named: 'onSubmit'), onSubmit: any(named: 'onSubmit'),
), ),
).thenAnswer((_) async {}); ).thenAnswer((_) async {});
when(cameraController.focusOnWaitingBackbox).thenAnswer((_) async {});
when(cameraController.focusOnGame).thenAnswer((_) async {});
when(() => overlays.remove(any())).thenAnswer((_) => true); when(() => overlays.remove(any())).thenAnswer((_) => true);
when(() => game.descendants().whereType<Backbox>()) when(() => game.descendants().whereType<Backbox>())
.thenReturn([backbox]); .thenReturn([backbox]);
when(game.firstChild<CameraController>).thenReturn(cameraController);
when(() => game.overlays).thenReturn(overlays); when(() => game.overlays).thenReturn(overlays);
when(() => game.characterTheme).thenReturn(DashTheme()); when(() => game.characterTheme).thenReturn(DashTheme());
when(() => game.player).thenReturn(pinballPlayer); when(() => game.player).thenReturn(pinballPlayer);
}); });
test( test(
'changes the backbox display and camera correctly ' 'changes the backbox display when the game is over',
'when the game is over',
() { () {
final state = GameState( final state = GameState(
totalScore: 0, totalScore: 0,
@ -89,7 +81,7 @@ void main() {
bonusHistory: const [], bonusHistory: const [],
status: GameStatus.gameOver, status: GameStatus.gameOver,
); );
gameFlowController.onNewState(state); gameBlocStatusListener.onNewState(state);
verify( verify(
() => backbox.initialsInput( () => backbox.initialsInput(
@ -98,18 +90,16 @@ void main() {
onSubmit: any(named: 'onSubmit'), onSubmit: any(named: 'onSubmit'),
), ),
).called(1); ).called(1);
verify(cameraController.focusOnGameOverBackbox).called(1);
}, },
); );
test( test(
'changes the backbox and camera correctly when it is not a game over', 'changes the backbox when it is not a game over',
() { () {
gameFlowController.onNewState( gameBlocStatusListener.onNewState(
GameState.initial().copyWith(status: GameStatus.playing), GameState.initial().copyWith(status: GameStatus.playing),
); );
verify(cameraController.focusOnGame).called(1);
verify(() => overlays.remove(PinballGame.playButtonOverlay)) verify(() => overlays.remove(PinballGame.playButtonOverlay))
.called(1); .called(1);
}, },
@ -118,7 +108,7 @@ void main() {
test( test(
'plays the background music on start', 'plays the background music on start',
() { () {
gameFlowController.onNewState( gameBlocStatusListener.onNewState(
GameState.initial().copyWith(status: GameStatus.playing), GameState.initial().copyWith(status: GameStatus.playing),
); );

Loading…
Cancel
Save