diff --git a/packages/flame_forge2d/lib/body_component.dart b/packages/flame_forge2d/lib/body_component.dart index 636aa564541..1797a7c1725 100644 --- a/packages/flame_forge2d/lib/body_component.dart +++ b/packages/flame_forge2d/lib/body_component.dart @@ -27,7 +27,7 @@ abstract class BodyComponent extends Component /// Specifies if the body's fixtures should be rendered. /// /// [renderBody] is true by default for [BodyComponent], if set to false - /// the body wont be rendered. + /// the body's fixtures wont be rendered. /// /// If you render something on top of the [BodyComponent], or doesn't want it /// to be seen, you probably want to set it to false. @@ -73,32 +73,45 @@ abstract class BodyComponent extends Component @override void render(Canvas canvas) { if (renderBody) { - _renderFixtures(canvas); + body.fixtures.forEach( + (fixture) => renderFixture(canvas, fixture), + ); } } @override void renderDebugMode(Canvas canvas) { - _renderFixtures(canvas); + body.fixtures.forEach( + (fixture) => renderFixture(canvas, fixture), + ); } - void _renderFixtures(Canvas canvas) { + /// Renders a [Fixture] in a [Canvas]. + /// + /// Called for each fixture in [body] when [render]ing. Override this method + /// to customize how fixtures are rendered. For example, you can filter out + /// fixtures that you don't want to render. + /// + /// **NOTE**: If [renderBody] is false, no fixtures will be rendered. Hence, + /// [renderFixture] is not called when [render]ing. + void renderFixture( + Canvas canvas, + Fixture fixture, + ) { canvas.save(); - for (final fixture in body.fixtures) { - switch (fixture.type) { - case ShapeType.chain: - _renderChain(canvas, fixture); - break; - case ShapeType.circle: - _renderCircle(canvas, fixture); - break; - case ShapeType.edge: - _renderEdge(canvas, fixture); - break; - case ShapeType.polygon: - _renderPolygon(canvas, fixture); - break; - } + switch (fixture.type) { + case ShapeType.chain: + _renderChain(canvas, fixture); + break; + case ShapeType.circle: + _renderCircle(canvas, fixture); + break; + case ShapeType.edge: + _renderEdge(canvas, fixture); + break; + case ShapeType.polygon: + _renderPolygon(canvas, fixture); + break; } canvas.restore(); } diff --git a/packages/flame_forge2d/lib/forge2d_camera.dart b/packages/flame_forge2d/lib/forge2d_camera.dart index de22c5ccfb7..a9efcc846a0 100644 --- a/packages/flame_forge2d/lib/forge2d_camera.dart +++ b/packages/flame_forge2d/lib/forge2d_camera.dart @@ -1,8 +1,7 @@ import 'package:flame/components.dart'; import 'package:flame/extensions.dart'; import 'package:flame/game.dart'; - -import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_forge2d/body_component.dart'; extension Forge2DCameraExtension on Camera { /// Immediately snaps the camera to start following the [BodyComponent]. diff --git a/packages/flame_forge2d/pubspec.yaml b/packages/flame_forge2d/pubspec.yaml index 96e1769a9fb..88392cb4aff 100644 --- a/packages/flame_forge2d/pubspec.yaml +++ b/packages/flame_forge2d/pubspec.yaml @@ -9,12 +9,15 @@ environment: dependencies: flame: ^1.1.1 - forge2d: ">=0.11.0 <0.12.0" flutter: sdk: flutter + forge2d: ">=0.11.0 <0.12.0" dev_dependencies: dartdoc: ^4.1.0 flame_lint: ^0.0.1 + flutter_test: + sdk: flutter + flame_test: ^1.4.0 mocktail: ^0.3.0 test: any diff --git a/packages/flame_forge2d/test/body_component_test.dart b/packages/flame_forge2d/test/body_component_test.dart index 3ed874e97fe..6c961f3b037 100644 --- a/packages/flame_forge2d/test/body_component_test.dart +++ b/packages/flame_forge2d/test/body_component_test.dart @@ -1,23 +1,232 @@ -import 'package:flame_forge2d/body_component.dart'; -import 'package:test/expect.dart'; -import 'package:test/scaffolding.dart'; +import 'dart:ui'; + +import 'package:flame_forge2d/flame_forge2d.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class _TestBodyComponent extends BodyComponent { + @override + Body createBody() => body; -class TestBodyComponent extends BodyComponent { @override void noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } +class _MockCanvas extends Mock implements Canvas {} + void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + group('BodyComponent', () { group('renderBody', () { test('is true by default', () { - final body = TestBodyComponent(); - expect(body.renderBody, isTrue); + final component = _TestBodyComponent(); + expect(component.renderBody, isTrue); }); test('sets and gets', () { - final body = TestBodyComponent()..renderBody = false; - expect(body.renderBody, isFalse); + final component = _TestBodyComponent()..renderBody = false; + expect(component.renderBody, isFalse); + }); + }); + + group('render', () { + group('draws correctly', () { + String goldenPath(String name) => 'goldens/body_component/$name.png'; + + final flameTester = FlameTester(Forge2DGame.new); + final testPaint = Paint()..color = const Color(0xffff0000); + + flameTester.testGameWidget( + 'a CircleShape', + setUp: (game, tester) async { + final body = game.world.createBody(BodyDef()); + final shape = CircleShape()..radius = 5; + body.createFixture(FixtureDef(shape)); + + final component = _TestBodyComponent() + ..body = body + ..paint = testPaint; + await game.add(component); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile(goldenPath('circle_shape')), + ); + }, + ); + + flameTester.testGameWidget( + 'an EdgeShape', + setUp: (game, tester) async { + final body = game.world.createBody(BodyDef()); + final shape = EdgeShape() + ..set( + Vector2.zero(), + Vector2.all(10), + ); + body.createFixture(FixtureDef(shape)); + + final component = _TestBodyComponent() + ..body = body + ..paint = testPaint; + await game.add(component); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile(goldenPath('edge_shape')), + ); + }, + ); + + flameTester.testGameWidget( + 'a PolygonShape', + setUp: (game, tester) async { + final body = game.world.createBody(BodyDef()); + final shape = PolygonShape() + ..set( + [ + Vector2.zero(), + Vector2.all(10), + Vector2(0, 10), + ], + ); + body.createFixture(FixtureDef(shape)); + + final component = _TestBodyComponent() + ..body = body + ..paint = testPaint; + await game.add(component); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile(goldenPath('polygon_shape')), + ); + }, + ); + + flameTester.testGameWidget( + 'a ChainShape', + setUp: (game, tester) async { + final body = game.world.createBody(BodyDef()); + final shape = ChainShape() + ..createChain( + [ + Vector2.zero(), + Vector2.all(10), + Vector2(10, 0), + ], + ); + body.createFixture(FixtureDef(shape)); + + final component = _TestBodyComponent() + ..body = body + ..paint = testPaint; + await game.add(component); + + game.camera.followVector2(Vector2.zero()); + }, + verify: (game, tester) async { + await expectLater( + find.byGame(), + matchesGoldenFile(goldenPath('chain_shape')), + ); + }, + ); + }); + }); + + group('renderFixture', () { + group('returs normally', () { + late Canvas canvas; + late Body body; + + setUp(() { + canvas = _MockCanvas(); + final world = World(); + body = world.createBody(BodyDef()); + }); + + test('when rendering a CircleShape', () { + final component = _TestBodyComponent(); + final shape = CircleShape()..radius = 5; + final fixture = body.createFixture( + FixtureDef(shape), + ); + + expect( + () => component.renderFixture(canvas, fixture), + returnsNormally, + ); + }); + + test('when rendering an EdgeShape', () { + final component = _TestBodyComponent(); + final shape = EdgeShape() + ..set( + Vector2.zero(), + Vector2.all(10), + ); + final fixture = body.createFixture( + FixtureDef(shape), + ); + + expect( + () => component.renderFixture(canvas, fixture), + returnsNormally, + ); + }); + + test('when rendering a PolygonShape', () { + final component = _TestBodyComponent(); + final shape = PolygonShape() + ..set( + [ + Vector2.zero(), + Vector2.all(10), + Vector2(0, 10), + ], + ); + final fixture = body.createFixture( + FixtureDef(shape), + ); + + expect( + () => component.renderFixture(canvas, fixture), + returnsNormally, + ); + }); + + test('when rendering a ChainShape', () { + final component = _TestBodyComponent(); + final shape = ChainShape() + ..createChain( + [ + Vector2.zero(), + Vector2.all(10), + Vector2(10, 0), + ], + ); + final fixture = body.createFixture( + FixtureDef(shape), + ); + + expect( + () => component.renderFixture(canvas, fixture), + returnsNormally, + ); + }); }); }); }); diff --git a/packages/flame_forge2d/test/goldens/body_component/chain_shape.png b/packages/flame_forge2d/test/goldens/body_component/chain_shape.png new file mode 100644 index 00000000000..ea1420b6372 Binary files /dev/null and b/packages/flame_forge2d/test/goldens/body_component/chain_shape.png differ diff --git a/packages/flame_forge2d/test/goldens/body_component/circle_shape.png b/packages/flame_forge2d/test/goldens/body_component/circle_shape.png new file mode 100644 index 00000000000..573a3083daa Binary files /dev/null and b/packages/flame_forge2d/test/goldens/body_component/circle_shape.png differ diff --git a/packages/flame_forge2d/test/goldens/body_component/edge_shape.png b/packages/flame_forge2d/test/goldens/body_component/edge_shape.png new file mode 100644 index 00000000000..048ecd1d8cf Binary files /dev/null and b/packages/flame_forge2d/test/goldens/body_component/edge_shape.png differ diff --git a/packages/flame_forge2d/test/goldens/body_component/polygon_shape.png b/packages/flame_forge2d/test/goldens/body_component/polygon_shape.png new file mode 100644 index 00000000000..3535f0246f2 Binary files /dev/null and b/packages/flame_forge2d/test/goldens/body_component/polygon_shape.png differ