Skip to content

Commit

Permalink
feat: allow controlling when a fixture is rendered (#1648)
Browse files Browse the repository at this point in the history
Changes _renderFixtures to renderFixture. This keeps the consistency with the other public render methods in BodyComponent.

Introduces new functionalities:

Allow listening when a given fixture is rendered

Allows modifying the rendering logic for a given (one or more) fixtures.

For example, avoiding a fixture to be rendered:

class MyBodyComponent extends BodyComponent { ... void renderFixture(Canvas canvas, Fixture fixture) { final avoidRendering = condition; // Any condition here. if (avoidRendering) return; super.renderFixture(canvas, fixture); } ... }
  • Loading branch information
alestiago authored Jun 6, 2022
1 parent aee77f1 commit 1b59d80
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 30 deletions.
51 changes: 32 additions & 19 deletions packages/flame_forge2d/lib/body_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ abstract class BodyComponent<T extends Forge2DGame> 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.
Expand Down Expand Up @@ -73,32 +73,45 @@ abstract class BodyComponent<T extends Forge2DGame> 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();
}
Expand Down
3 changes: 1 addition & 2 deletions packages/flame_forge2d/lib/forge2d_camera.dart
Original file line number Diff line number Diff line change
@@ -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].
Expand Down
5 changes: 4 additions & 1 deletion packages/flame_forge2d/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
225 changes: 217 additions & 8 deletions packages/flame_forge2d/test/body_component_test.dart
Original file line number Diff line number Diff line change
@@ -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<Forge2DGame>(),
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<Forge2DGame>(),
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<Forge2DGame>(),
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<Forge2DGame>(),
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,
);
});
});
});
});
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 1b59d80

Please sign in to comment.