From bcf7dd3559a179cea2e25a698fb95cd667ce973d Mon Sep 17 00:00:00 2001 From: alestiago Date: Wed, 4 May 2022 22:28:31 +0100 Subject: [PATCH] feat: implemented CanvasComponent --- lib/game/behaviors/scoring_behavior.dart | 15 +- .../flutter_forest_bonus_behavior.dart | 2 +- lib/game/pinball_game.dart | 28 ++-- packages/pinball_flame/lib/pinball_flame.dart | 2 +- .../pinball_flame/lib/src/canvas/canvas.dart | 2 + .../lib/src/canvas/canvas_component.dart | 15 +- .../lib/src/canvas/canvas_wrapper.dart | 2 + .../src/canvas/canvas_component_test.dart | 144 ++++++++++++++++++ .../test/src/canvas/canvas_wrapper_test.dart | 3 +- .../src/canvas/z_canvas_component_test.dart | 16 +- .../test/src/goldens/rendering/blue_red.png | Bin 0 -> 22364 bytes .../test/src/goldens/rendering/red_blue.png | Bin 0 -> 22395 bytes test/game/pinball_game_test.dart | 51 ++++--- 13 files changed, 231 insertions(+), 49 deletions(-) create mode 100644 packages/pinball_flame/test/src/canvas/canvas_component_test.dart create mode 100644 packages/pinball_flame/test/src/goldens/rendering/blue_red.png create mode 100644 packages/pinball_flame/test/src/goldens/rendering/red_blue.png diff --git a/lib/game/behaviors/scoring_behavior.dart b/lib/game/behaviors/scoring_behavior.dart index 84597838..eddcb580 100644 --- a/lib/game/behaviors/scoring_behavior.dart +++ b/lib/game/behaviors/scoring_behavior.dart @@ -40,13 +40,14 @@ class ScoringBehavior extends Component with HasGameRef { @override Future onLoad() async { gameRef.read().add(Scored(points: _points.value)); - await gameRef.firstChild()!.add( - ScoreComponent( - points: _points, - position: _position, - effectController: _effectController, - ), - ); + final canvas = gameRef.descendants().whereType().single; + await canvas.add( + ScoreComponent( + points: _points, + position: _position, + effectController: _effectController, + ), + ); } } diff --git a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart index 8f1b46e8..c06e6f87 100644 --- a/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart +++ b/lib/game/components/flutter_forest/behaviors/flutter_forest_bonus_behavior.dart @@ -17,7 +17,7 @@ class FlutterForestBonusBehavior extends Component final bumpers = parent.children.whereType(); final signpost = parent.firstChild()!; final animatronic = parent.firstChild()!; - final canvas = gameRef.firstChild()!; + final canvas = gameRef.descendants().whereType().single; for (final bumper in bumpers) { // TODO(alestiago): Refactor subscription management once the following is diff --git a/lib/game/pinball_game.dart b/lib/game/pinball_game.dart index 0cd130ca..1e7ebbc7 100644 --- a/lib/game/pinball_game.dart +++ b/lib/game/pinball_game.dart @@ -62,14 +62,23 @@ class PinballGame extends PinballForge2DGame ]; await add( - ZCanvasComponent( + CanvasComponent( + onSpritePainted: (paint) { + if (paint.filterQuality != FilterQuality.high) { + paint.filterQuality = FilterQuality.high; + } + }, children: [ - ...machine, - ...decals, - ...characterAreas, - Drain(), - BottomGroup(), - Launcher(), + ZCanvasComponent( + children: [ + ...machine, + ...decals, + ...characterAreas, + Drain(), + BottomGroup(), + Launcher(), + ], + ), ], ), ); @@ -158,7 +167,7 @@ class _GameBallsController extends ComponentController plunger.body.position.x, plunger.body.position.y - Ball.size.y, ); - component.firstChild()?.add(ball); + component.descendants().whereType().single.add(ball); }); } } @@ -185,9 +194,10 @@ class DebugPinballGame extends PinballGame with FPSCounter { super.onTapUp(pointerId, info); if (info.raw.kind == PointerDeviceKind.mouse) { + final canvas = descendants().whereType().single; final ball = ControlledBall.debug() ..initialPosition = info.eventPosition.game; - firstChild()?.add(ball); + canvas.add(ball); } } } diff --git a/packages/pinball_flame/lib/pinball_flame.dart b/packages/pinball_flame/lib/pinball_flame.dart index 4265ee95..6f8a40f7 100644 --- a/packages/pinball_flame/lib/pinball_flame.dart +++ b/packages/pinball_flame/lib/pinball_flame.dart @@ -1,9 +1,9 @@ library pinball_flame; +export 'src/canvas/canvas.dart'; export 'src/component_controller.dart'; export 'src/contact_behavior.dart'; export 'src/keyboard_input_controller.dart'; export 'src/parent_is_a.dart'; export 'src/pinball_forge2d_game.dart'; export 'src/sprite_animation.dart'; -export 'src/canvas/z_canvas_component.dart'; diff --git a/packages/pinball_flame/lib/src/canvas/canvas.dart b/packages/pinball_flame/lib/src/canvas/canvas.dart index e69de29b..9c0c7a70 100644 --- a/packages/pinball_flame/lib/src/canvas/canvas.dart +++ b/packages/pinball_flame/lib/src/canvas/canvas.dart @@ -0,0 +1,2 @@ +export 'canvas_component.dart'; +export 'z_canvas_component.dart'; diff --git a/packages/pinball_flame/lib/src/canvas/canvas_component.dart b/packages/pinball_flame/lib/src/canvas/canvas_component.dart index a5cede54..54eca8bf 100644 --- a/packages/pinball_flame/lib/src/canvas/canvas_component.dart +++ b/packages/pinball_flame/lib/src/canvas/canvas_component.dart @@ -11,13 +11,16 @@ typedef PaintFunction = void Function(Paint)?; /// {@template canvas} /// Allows listening before the rendering of [Sprite]s. +/// +/// The existance of this is class is to hack around the fact that Flame doesn't +/// privide a way to modify the default [Paint] before drawing a [Sprite]. /// {@endtemplate} class CanvasComponent extends Component { /// {@macro canvas} - CanvasComponent( - PaintFunction? beforePainting, + CanvasComponent({ + PaintFunction? onSpritePainted, Iterable? children, - ) : _canvas = _Canvas(beforePainting: beforePainting), + }) : _canvas = _Canvas(onSpritePainted: onSpritePainted), super(children: children); final _Canvas _canvas; @@ -30,13 +33,13 @@ class CanvasComponent extends Component { } class _Canvas extends CanvasWrapper { - _Canvas({PaintFunction beforePainting}) : _beforePainting = beforePainting; + _Canvas({PaintFunction onSpritePainted}) : _onSpritePainted = onSpritePainted; - final PaintFunction _beforePainting; + final PaintFunction _onSpritePainted; @override void drawImageRect(Image image, Rect src, Rect dst, Paint paint) { - _beforePainting?.call(paint); + _onSpritePainted?.call(paint); super.drawImageRect(image, src, dst, paint); } } diff --git a/packages/pinball_flame/lib/src/canvas/canvas_wrapper.dart b/packages/pinball_flame/lib/src/canvas/canvas_wrapper.dart index 73378d62..883527d2 100644 --- a/packages/pinball_flame/lib/src/canvas/canvas_wrapper.dart +++ b/packages/pinball_flame/lib/src/canvas/canvas_wrapper.dart @@ -1,3 +1,5 @@ +// ignore_for_file: public_member_api_docs + import 'dart:typed_data'; import 'dart:ui'; diff --git a/packages/pinball_flame/test/src/canvas/canvas_component_test.dart b/packages/pinball_flame/test/src/canvas/canvas_component_test.dart new file mode 100644 index 00000000..7bf7fd88 --- /dev/null +++ b/packages/pinball_flame/test/src/canvas/canvas_component_test.dart @@ -0,0 +1,144 @@ +// ignore_for_file: cascade_invocations + +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pinball_flame/src/canvas/canvas_component.dart'; + +class _TestSpriteComponent extends SpriteComponent {} + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('CanvasComponent', () { + final flameTester = FlameTester(FlameGame.new); + + test('can be instantiated', () { + expect( + CanvasComponent(), + isA(), + ); + }); + + flameTester.test('loads correctly', (game) async { + final component = CanvasComponent(); + await game.ensureAdd(component); + expect(game.contains(component), isTrue); + }); + + flameTester.test( + 'adds children', + (game) async { + final component = Component(); + final canvas = CanvasComponent( + onSpritePainted: (paint) => paint.filterQuality = FilterQuality.high, + children: [component], + ); + + await game.ensureAdd(canvas); + + expect( + canvas.children.contains(component), + isTrue, + ); + }, + ); + + flameTester.testGameWidget( + 'calls onSpritePainted when paiting a sprite', + setUp: (game, tester) async { + final spriteComponent = _TestSpriteComponent(); + + final completer = Completer(); + decodeImageFromList( + Uint8List.fromList(_image), + completer.complete, + ); + spriteComponent.sprite = Sprite(await completer.future); + + var calls = 0; + final canvas = CanvasComponent( + onSpritePainted: (paint) => calls++, + children: [spriteComponent], + ); + + await game.ensureAdd(canvas); + await tester.pump(); + + expect(calls, equals(1)); + }, + ); + }); +} + +const List _image = [ + 0x89, + 0x50, + 0x4E, + 0x47, + 0x0D, + 0x0A, + 0x1A, + 0x0A, + 0x00, + 0x00, + 0x00, + 0x0D, + 0x49, + 0x48, + 0x44, + 0x52, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x01, + 0x08, + 0x06, + 0x00, + 0x00, + 0x00, + 0x1F, + 0x15, + 0xC4, + 0x89, + 0x00, + 0x00, + 0x00, + 0x0A, + 0x49, + 0x44, + 0x41, + 0x54, + 0x78, + 0x9C, + 0x63, + 0x00, + 0x01, + 0x00, + 0x00, + 0x05, + 0x00, + 0x01, + 0x0D, + 0x0A, + 0x2D, + 0xB4, + 0x00, + 0x00, + 0x00, + 0x00, + 0x49, + 0x45, + 0x4E, + 0x44, + 0xAE, +]; diff --git a/packages/pinball_flame/test/src/canvas/canvas_wrapper_test.dart b/packages/pinball_flame/test/src/canvas/canvas_wrapper_test.dart index 774e7519..58da1ecd 100644 --- a/packages/pinball_flame/test/src/canvas/canvas_wrapper_test.dart +++ b/packages/pinball_flame/test/src/canvas/canvas_wrapper_test.dart @@ -286,10 +286,11 @@ void main() { }); test("getSaveCount calls Canvas's getSaveCount", () { - final canvasWrapper = CanvasWrapper().canvas = canvas; + final canvasWrapper = CanvasWrapper()..canvas = canvas; when(() => canvas.getSaveCount()).thenReturn(1); canvasWrapper.getSaveCount(); verify(() => canvas.getSaveCount()).called(1); + expect(canvasWrapper.getSaveCount(), 1); }); test("restore calls Canvas's restore", () { diff --git a/packages/pinball_flame/test/src/canvas/z_canvas_component_test.dart b/packages/pinball_flame/test/src/canvas/z_canvas_component_test.dart index d24041d1..67c45ec7 100644 --- a/packages/pinball_flame/test/src/canvas/z_canvas_component_test.dart +++ b/packages/pinball_flame/test/src/canvas/z_canvas_component_test.dart @@ -17,10 +17,18 @@ class _TestCircleComponent extends CircleComponent with ZIndex { void main() { TestWidgetsFlutterBinding.ensureInitialized(); - final flameTester = FlameTester(FlameGame.new); - const goldenPrefix = 'golden/rendering/'; group('ZCanvasComponent', () { + final flameTester = FlameTester(FlameGame.new); + const goldensFilePath = '../goldens/rendering/'; + + test('can be instantiated', () { + expect( + ZCanvasComponent(), + isA(), + ); + }); + flameTester.test('loads correctly', (game) async { final component = ZCanvasComponent(); await game.ensureAdd(component); @@ -43,7 +51,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('${goldenPrefix}red_blue.png'), + matchesGoldenFile('${goldensFilePath}red_blue.png'), ); }, ); @@ -64,7 +72,7 @@ void main() { verify: (game, tester) async { await expectLater( find.byGame(), - matchesGoldenFile('${goldenPrefix}blue_red.png'), + matchesGoldenFile('${goldensFilePath}blue_red.png'), ); }, ); diff --git a/packages/pinball_flame/test/src/goldens/rendering/blue_red.png b/packages/pinball_flame/test/src/goldens/rendering/blue_red.png new file mode 100644 index 0000000000000000000000000000000000000000..4ca86375e8dffc98cb915c5a9eca4ecf94a8677c GIT binary patch literal 22364 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 X+Ve~i*X!6e;Kp4a`Fip_7@z(Vr{)6r literal 0 HcmV?d00001 diff --git a/test/game/pinball_game_test.dart b/test/game/pinball_game_test.dart index 98aac670..ec27b3c5 100644 --- a/test/game/pinball_game_test.dart +++ b/test/game/pinball_game_test.dart @@ -1,6 +1,9 @@ // ignore_for_file: cascade_invocations +import 'dart:ui'; + import 'package:bloc_test/bloc_test.dart'; +import 'package:flame/components.dart'; import 'package:flame/input.dart'; import 'package:flame_test/flame_test.dart'; import 'package:flutter/gestures.dart'; @@ -281,26 +284,34 @@ void main() { ); }); - group( - 'onNewState', - () { - flameTester.test( - 'spawns a ball', - (game) async { - final previousBalls = - game.descendants().whereType().toList(); - - game.controller.onNewState(_MockGameState()); - await game.ready(); - final currentBalls = - game.descendants().whereType().toList(); - - expect( - currentBalls.length, - equals(previousBalls.length + 1), - ); - }, - ); + group('onNewState', () { + flameTester.test( + 'spawns a ball', + (game) async { + final previousBalls = + game.descendants().whereType().toList(); + + game.controller.onNewState(_MockGameState()); + await game.ready(); + final currentBalls = + game.descendants().whereType().toList(); + + expect( + currentBalls.length, + equals(previousBalls.length + 1), + ); + }, + ); + }); + + flameTester.testGameWidget( + 'sets FilterQuality.high to all components', + setUp: (game, tester) async { + await game.ready(); + await tester.pump(); + game.descendants().whereType().forEach((component) { + expect(component.paint.filterQuality, equals(FilterQuality.high)); + }); }, ); });