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
Future<void> onLoad() async {
gameRef.read<GameBloc>().add(Scored(points: _points.value));
await gameRef.firstChild<ZCanvasComponent>()!.add(
ScoreComponent(
points: _points,
position: _position,
effectController: _effectController,
),
);
final canvas = gameRef.descendants().whereType<ZCanvasComponent>().single;
await canvas.add(
ScoreComponent(
points: _points,
position: _position,
effectController: _effectController,
),
);
}
}

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

@ -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<PinballGame>
plunger.body.position.x,
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);
if (info.raw.kind == PointerDeviceKind.mouse) {
final canvas = descendants().whereType<ZCanvasComponent>().single;
final ball = ControlledBall.debug()
..initialPosition = info.eventPosition.game;
firstChild<ZCanvasComponent>()?.add(ball);
canvas.add(ball);
}
}
}

@ -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';

@ -0,0 +1,2 @@
export 'canvas_component.dart';
export 'z_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<Component>? 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);
}
}

@ -1,3 +1,5 @@
// ignore_for_file: public_member_api_docs
import 'dart:typed_data';
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", () {
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", () {

@ -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<ZCanvasComponent>(),
);
});
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<FlameGame>(),
matchesGoldenFile('${goldenPrefix}red_blue.png'),
matchesGoldenFile('${goldensFilePath}red_blue.png'),
);
},
);
@ -64,7 +72,7 @@ void main() {
verify: (game, tester) async {
await expectLater(
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
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<ControlledBall>().toList();
game.controller.onNewState(_MockGameState());
await game.ready();
final currentBalls =
game.descendants().whereType<ControlledBall>().toList();
expect(
currentBalls.length,
equals(previousBalls.length + 1),
);
},
);
group('onNewState', () {
flameTester.test(
'spawns a ball',
(game) async {
final previousBalls =
game.descendants().whereType<ControlledBall>().toList();
game.controller.onNewState(_MockGameState());
await game.ready();
final currentBalls =
game.descendants().whereType<ControlledBall>().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<HasPaint>().forEach((component) {
expect(component.paint.filterQuality, equals(FilterQuality.high));
});
},
);
});

Loading…
Cancel
Save