From 3d0a08a7671f9835f706f75f230caff2d9194d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Lis?= Date: Sun, 24 Sep 2023 13:37:45 +0200 Subject: [PATCH] Allow for bodyDef and fixtureDefs to be prepared earlier --- doc/bridge_packages/flame_forge2d/forge2d.md | 11 ++-- packages/flame_forge2d/example/lib/main.dart | 41 ++++++--------- .../flame_forge2d/lib/body_component.dart | 32 +++++++++--- .../test/body_component_test.dart | 51 ++++++++++++++++++- 4 files changed, 99 insertions(+), 36 deletions(-) diff --git a/doc/bridge_packages/flame_forge2d/forge2d.md b/doc/bridge_packages/flame_forge2d/forge2d.md index 2d1ab98db80..f7948201d2e 100644 --- a/doc/bridge_packages/flame_forge2d/forge2d.md +++ b/doc/bridge_packages/flame_forge2d/forge2d.md @@ -51,8 +51,13 @@ A simple `Forge2DGame` implementation example can be seen in the ## BodyComponent The `BodyComponent` is a wrapper for the `Forge2D` body, which is the body that the physics engine -is interacting with. To create a `BodyComponent` you need to override `createBody()` and create and -return your created body. +is interacting with. To create a `BodyComponent` you can either: + +- override `createBody()` and create and return your created body; +- use the default `createBody()` implementation: pass a `BodyDef` instance (and optionally a list +of `FixtureDef` instances) to BodyComponent' constructor' `bodyDef` (and `fixtureDefs`) arguments; +- use the default `createBody()` implementation: assign a `BodyDef` instance to `this.bodyDef`, and +optionally a list of `FixtureDef` instances to `this.fixtureDefs`. The `BodyComponent` is by default having `renderBody = true`, since otherwise, it wouldn't show anything after you have created a `Body` and added the `BodyComponent` to the game. If you want to @@ -61,7 +66,7 @@ turn it off you can just set (or override) `renderBody` to false. Just like any other Flame component you can add children to the `BodyComponent`, which can be very useful if you want to add for example animations or other components on top of your body. -The body that you create in `createBody` should be defined according to Flame's coordinate system, +The body that you create should be defined according to Flame's coordinate system, not according to the coordinate system of Forge2D (where the Y-axis is flipped). :exclamation: In Forge2D you shouldn't add any bodies as children to other components, diff --git a/packages/flame_forge2d/example/lib/main.dart b/packages/flame_forge2d/example/lib/main.dart index db4b0d42d1e..d90e2d4cbee 100644 --- a/packages/flame_forge2d/example/lib/main.dart +++ b/packages/flame_forge2d/example/lib/main.dart @@ -36,32 +36,22 @@ class Forge2DExample extends Forge2DGame { } class Ball extends BodyComponent with TapCallbacks { - final Vector2 initialPosition; - Ball({Vector2? initialPosition}) - : initialPosition = initialPosition ?? Vector2.zero(); - - @override - Body createBody() { - final shape = CircleShape(); - shape.radius = 5; - - final fixtureDef = FixtureDef( - shape, - restitution: 0.8, - density: 1.0, - friction: 0.4, - ); - - final bodyDef = BodyDef( - userData: this, - angularDamping: 0.8, - position: initialPosition, - type: BodyType.dynamic, - ); - - return world.createBody(bodyDef)..createFixture(fixtureDef); - } + : super( + fixtureDefs: [ + FixtureDef( + CircleShape()..radius = 5, + restitution: 0.8, + density: 1.0, + friction: 0.4, + ), + ], + bodyDef: BodyDef( + angularDamping: 0.8, + position: initialPosition ?? Vector2.zero(), + type: BodyType.dynamic, + ), + ); @override void onTapDown(_) { @@ -80,7 +70,6 @@ class Wall extends BodyComponent { final shape = EdgeShape()..set(_start, _end); final fixtureDef = FixtureDef(shape, friction: 0.3); final bodyDef = BodyDef( - userData: this, position: Vector2.zero(), ); diff --git a/packages/flame_forge2d/lib/body_component.dart b/packages/flame_forge2d/lib/body_component.dart index ae9c1f425f2..58f91cbafcd 100644 --- a/packages/flame_forge2d/lib/body_component.dart +++ b/packages/flame_forge2d/lib/body_component.dart @@ -10,17 +10,20 @@ import 'package:flutter/foundation.dart'; /// Since a pure BodyComponent doesn't have anything drawn on top of it, /// it is a good idea to turn on [debugMode] for it so that the bodies can be /// seen -abstract class BodyComponent extends Component +/// +/// You can use the optional [bodyDef] and [fixtureDefs] arguments to create +/// the [BodyComponent]' body without having to create the definitions within +/// the component. +class BodyComponent extends Component with HasGameReference, HasPaint - implements - CoordinateTransform, - ReadOnlyPositionProvider, - ReadOnlyAngleProvider { + implements CoordinateTransform, ReadOnlyPositionProvider, ReadOnlyAngleProvider { BodyComponent({ Paint? paint, super.children, super.priority, this.renderBody = true, + this.bodyDef, + this.fixtureDefs, super.key, }) { this.paint = paint ?? (Paint()..color = defaultColor); @@ -29,6 +32,15 @@ abstract class BodyComponent extends Component static const defaultColor = Color.fromARGB(255, 255, 255, 255); late Body body; + /// Default implementation of [createBody] will use this value, if provided. + /// + /// If you do not provide a BodyDef here, you must override [createBody]. + BodyDef? bodyDef; + + /// Default implementation of [createBody] adds these fixtures to the body + /// that it creates from [bodyDef]. + List? fixtureDefs; + @override Vector2 get position => body.position; @@ -43,7 +55,15 @@ abstract class BodyComponent extends Component /// You should create the Forge2D [Body] in this method when you extend /// the BodyComponent. - Body createBody(); + Body createBody() { + assert( + bodyDef != null, + 'Ensure this.bodyDef is not null or override createBody', + ); + final body = world.createBody(bodyDef!); + fixtureDefs?.forEach(body.createFixture); + return body; + } @mustCallSuper @override diff --git a/packages/flame_forge2d/test/body_component_test.dart b/packages/flame_forge2d/test/body_component_test.dart index 9093a7ad2f2..afff0a25eeb 100644 --- a/packages/flame_forge2d/test/body_component_test.dart +++ b/packages/flame_forge2d/test/body_component_test.dart @@ -1,4 +1,4 @@ -import 'package:flame/components.dart' show PositionComponent; +import 'package:flame/components.dart' show ComponentKey, PositionComponent; import 'package:flame/extensions.dart'; import 'package:flame_forge2d/flame_forge2d.dart'; import 'package:flame_test/flame_test.dart'; @@ -350,5 +350,54 @@ void main() { }, ); }); + group('createBody', () { + test('should throw an error if bodyDef is null', () { + final bodyComponent = BodyComponent(); + expect(bodyComponent.createBody, throwsAssertionError); + }); + + group('should create body', () { + final flameTester = FlameTester(Forge2DGame.new); + + flameTester.testGameWidget( + 'with no fixtures', + setUp: (game, tester) async { + final bodyComponent = BodyComponent( + bodyDef: BodyDef(position: Vector2(33, 44)), + key: ComponentKey.named('tested'), + ); + game.world.add(bodyComponent); + }, + verify: (game, tester) async { + expect( + game.findByKeyName('tested')!.body.position, + Vector2(33, 44), + ); + }, + ); + + flameTester.testGameWidget( + 'with a set of fixtures', + setUp: (game, tester) async { + final bodyComponent = BodyComponent( + bodyDef: BodyDef(), + fixtureDefs: [ + FixtureDef(CircleShape()..radius = 10), + FixtureDef(CircleShape()..radius = 20), + FixtureDef(CircleShape()..radius = 30), + ], + key: ComponentKey.named('tested'), + ); + game.world.add(bodyComponent); + }, + verify: (game, tester) async { + final bodyComponent = game.findByKeyName('tested')!; + expect(bodyComponent.body.fixtures[0].shape.radius, 10); + expect(bodyComponent.body.fixtures[1].shape.radius, 20); + expect(bodyComponent.body.fixtures[2].shape.radius, 30); + }, + ); + }); + }); }); }