feat: implemented CanvasComponent

pull/334/head
alestiago 3 years ago
parent ed03eba2fa
commit bcf7dd3559

@ -40,13 +40,14 @@ class ScoringBehavior extends Component with HasGameRef<PinballGame> {
@override @override
Future<void> onLoad() async { Future<void> onLoad() async {
gameRef.read<GameBloc>().add(Scored(points: _points.value)); gameRef.read<GameBloc>().add(Scored(points: _points.value));
await gameRef.firstChild<ZCanvasComponent>()!.add( final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
ScoreComponent( await canvas.add(
points: _points, ScoreComponent(
position: _position, points: _points,
effectController: _effectController, position: _position,
), effectController: _effectController,
); ),
);
} }
} }

@ -17,7 +17,7 @@ class FlutterForestBonusBehavior extends Component
final bumpers = parent.children.whereType<DashNestBumper>(); final bumpers = parent.children.whereType<DashNestBumper>();
final signpost = parent.firstChild<Signpost>()!; final signpost = parent.firstChild<Signpost>()!;
final animatronic = parent.firstChild<DashAnimatronic>()!; final animatronic = parent.firstChild<DashAnimatronic>()!;
final canvas = gameRef.firstChild<ZCanvasComponent>()!; final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
for (final bumper in bumpers) { for (final bumper in bumpers) {
// TODO(alestiago): Refactor subscription management once the following is // TODO(alestiago): Refactor subscription management once the following is

@ -62,14 +62,23 @@ class PinballGame extends PinballForge2DGame
]; ];
await add( await add(
ZCanvasComponent( CanvasComponent(
onSpritePainted: (paint) {
if (paint.filterQuality != FilterQuality.high) {
paint.filterQuality = FilterQuality.high;
}
},
children: [ children: [
...machine, ZCanvasComponent(
...decals, children: [
...characterAreas, ...machine,
Drain(), ...decals,
BottomGroup(), ...characterAreas,
Launcher(), Drain(),
BottomGroup(),
Launcher(),
],
),
], ],
), ),
); );
@ -158,7 +167,7 @@ class _GameBallsController extends ComponentController<PinballGame>
plunger.body.position.x, plunger.body.position.x,
plunger.body.position.y - Ball.size.y, plunger.body.position.y - Ball.size.y,
); );
component.firstChild<ZCanvasComponent>()?.add(ball); component.descendants().whereType<ZCanvasComponent>().single.add(ball);
}); });
} }
} }
@ -185,9 +194,10 @@ class DebugPinballGame extends PinballGame with FPSCounter {
super.onTapUp(pointerId, info); super.onTapUp(pointerId, info);
if (info.raw.kind == PointerDeviceKind.mouse) { if (info.raw.kind == PointerDeviceKind.mouse) {
final canvas = descendants().whereType<ZCanvasComponent>().single;
final ball = ControlledBall.debug() final ball = ControlledBall.debug()
..initialPosition = info.eventPosition.game; ..initialPosition = info.eventPosition.game;
firstChild<ZCanvasComponent>()?.add(ball); canvas.add(ball);
} }
} }
} }

@ -1,9 +1,9 @@
library pinball_flame; library pinball_flame;
export 'src/canvas/canvas.dart';
export 'src/component_controller.dart'; export 'src/component_controller.dart';
export 'src/contact_behavior.dart'; export 'src/contact_behavior.dart';
export 'src/keyboard_input_controller.dart'; export 'src/keyboard_input_controller.dart';
export 'src/parent_is_a.dart'; export 'src/parent_is_a.dart';
export 'src/pinball_forge2d_game.dart'; export 'src/pinball_forge2d_game.dart';
export 'src/sprite_animation.dart'; export 'src/sprite_animation.dart';
export 'src/canvas/z_canvas_component.dart';

@ -0,0 +1,2 @@
export 'canvas_component.dart';
export 'z_canvas_component.dart';

@ -11,13 +11,16 @@ typedef PaintFunction = void Function(Paint)?;
/// {@template canvas} /// {@template canvas}
/// Allows listening before the rendering of [Sprite]s. /// 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} /// {@endtemplate}
class CanvasComponent extends Component { class CanvasComponent extends Component {
/// {@macro canvas} /// {@macro canvas}
CanvasComponent( CanvasComponent({
PaintFunction? beforePainting, PaintFunction? onSpritePainted,
Iterable<Component>? children, Iterable<Component>? children,
) : _canvas = _Canvas(beforePainting: beforePainting), }) : _canvas = _Canvas(onSpritePainted: onSpritePainted),
super(children: children); super(children: children);
final _Canvas _canvas; final _Canvas _canvas;
@ -30,13 +33,13 @@ class CanvasComponent extends Component {
} }
class _Canvas extends CanvasWrapper { class _Canvas extends CanvasWrapper {
_Canvas({PaintFunction beforePainting}) : _beforePainting = beforePainting; _Canvas({PaintFunction onSpritePainted}) : _onSpritePainted = onSpritePainted;
final PaintFunction _beforePainting; final PaintFunction _onSpritePainted;
@override @override
void drawImageRect(Image image, Rect src, Rect dst, Paint paint) { void drawImageRect(Image image, Rect src, Rect dst, Paint paint) {
_beforePainting?.call(paint); _onSpritePainted?.call(paint);
super.drawImageRect(image, src, dst, paint); super.drawImageRect(image, src, dst, paint);
} }
} }

@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui'; import 'dart:ui';

@ -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<CanvasComponent>(),
);
});
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<Image>();
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<int> _image = <int>[
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,
];

@ -286,10 +286,11 @@ void main() {
}); });
test("getSaveCount calls Canvas's getSaveCount", () { test("getSaveCount calls Canvas's getSaveCount", () {
final canvasWrapper = CanvasWrapper().canvas = canvas; final canvasWrapper = CanvasWrapper()..canvas = canvas;
when(() => canvas.getSaveCount()).thenReturn(1); when(() => canvas.getSaveCount()).thenReturn(1);
canvasWrapper.getSaveCount(); canvasWrapper.getSaveCount();
verify(() => canvas.getSaveCount()).called(1); verify(() => canvas.getSaveCount()).called(1);
expect(canvasWrapper.getSaveCount(), 1);
}); });
test("restore calls Canvas's restore", () { test("restore calls Canvas's restore", () {

@ -17,10 +17,18 @@ class _TestCircleComponent extends CircleComponent with ZIndex {
void main() { void main() {
TestWidgetsFlutterBinding.ensureInitialized(); TestWidgetsFlutterBinding.ensureInitialized();
final flameTester = FlameTester(FlameGame.new);
const goldenPrefix = 'golden/rendering/';
group('ZCanvasComponent', () { group('ZCanvasComponent', () {
final flameTester = FlameTester(FlameGame.new);
const goldensFilePath = '../goldens/rendering/';
test('can be instantiated', () {
expect(
ZCanvasComponent(),
isA<ZCanvasComponent>(),
);
});
flameTester.test('loads correctly', (game) async { flameTester.test('loads correctly', (game) async {
final component = ZCanvasComponent(); final component = ZCanvasComponent();
await game.ensureAdd(component); await game.ensureAdd(component);
@ -43,7 +51,7 @@ void main() {
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(
find.byGame<FlameGame>(), find.byGame<FlameGame>(),
matchesGoldenFile('${goldenPrefix}red_blue.png'), matchesGoldenFile('${goldensFilePath}red_blue.png'),
); );
}, },
); );
@ -64,7 +72,7 @@ void main() {
verify: (game, tester) async { verify: (game, tester) async {
await expectLater( await expectLater(
find.byGame<FlameGame>(), find.byGame<FlameGame>(),
matchesGoldenFile('${goldenPrefix}blue_red.png'), matchesGoldenFile('${goldensFilePath}blue_red.png'),
); );
}, },
); );

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@ -1,6 +1,9 @@
// ignore_for_file: cascade_invocations // ignore_for_file: cascade_invocations
import 'dart:ui';
import 'package:bloc_test/bloc_test.dart'; import 'package:bloc_test/bloc_test.dart';
import 'package:flame/components.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame_test/flame_test.dart'; import 'package:flame_test/flame_test.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
@ -281,26 +284,34 @@ void main() {
); );
}); });
group( group('onNewState', () {
'onNewState', flameTester.test(
() { 'spawns a ball',
flameTester.test( (game) async {
'spawns a ball', final previousBalls =
(game) async { game.descendants().whereType<ControlledBall>().toList();
final previousBalls =
game.descendants().whereType<ControlledBall>().toList(); game.controller.onNewState(_MockGameState());
await game.ready();
game.controller.onNewState(_MockGameState()); final currentBalls =
await game.ready(); game.descendants().whereType<ControlledBall>().toList();
final currentBalls =
game.descendants().whereType<ControlledBall>().toList(); expect(
currentBalls.length,
expect( equals(previousBalls.length + 1),
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<HasPaint>().forEach((component) {
expect(component.paint.filterQuality, equals(FilterQuality.high));
});
}, },
); );
}); });

Loading…
Cancel
Save