Forge2D

Blue Fire maintains Forge2D, Dart bindings for the Box2D physics engine (native on mobile and desktop, WebAssembly on the web).

If you want to use Forge2D specifically for Flame you should use our bridge library flame_forge2d and if you just want to use it in a Dart project you can use the forge2d library directly.

To use it in your game you just need to add flame_forge2d to your pubspec.yaml, as can be seen in the Forge2D example and the pub.dev installation instructions.

Since Forge2D runs Box2D as native code, a C toolchain is required when building for native platforms (Xcode on iOS/macOS, the NDK on Android, Visual Studio Build Tools on Windows and clang or gcc on Linux). On the web a bundled WebAssembly build of Box2D is used instead.

Forge2D has to be initialized with await initializeForge2D() before any physics world is created, which on the web is what loads that WebAssembly module. Forge2DGame awaits this in its onLoad, so games don’t have to do anything, but that also means that a Forge2DGame subclass which overrides onLoad has to await super.onLoad() before it creates any bodies:

class MyGame extends Forge2DGame {
  @override
  Future<void> onLoad() async {
    await super.onLoad();  // Not awaiting this breaks the game on the web.
    world.add(MyBody());
  }
}

If you create a Forge2DWorld or a raw Forge2D World outside of a Forge2DGame, await initializeForge2D() yourself first, or the world creation will throw on the web.

If you are upgrading an existing game from flame_forge2d 0.19, see the migration guide.

Forge2DGame

If you are going to use Forge2D in your project it can be a good idea to use the Forge2D-specific FlameGame class, Forge2DGame.

It is called Forge2DGame and supports both the special Forge2D components called BodyComponents as well as normal Flame components.

Forge2DGame has a built-in CameraComponent that uses a Forge2DViewfinder. The physics world is measured in meters, and the viewfinder renders one meter as metersToPixels pixels, which is 100 by default. Lay your world out in meters at a realistic scale and let metersToPixels decide how big that is on screen; see Units and scale for why the two are kept apart.

You can change the scale either by calling super(metersToPixels: yourScale) in your constructor or by doing game.metersToPixels = yourScale; at a later stage.

The zoom of the viewfinder is applied on top of metersToPixels and defaults to 1, so it is free for what it is normally used for: zooming the camera in and out. Everything except the rendering stays in meters, so body positions, camera.viewfinder.position, camera.visibleWorldRect and the local positions that events report are all still expressed in meters.

If you are previously familiar with Box2D it can be good to know that the whole concept of the Box2d world is mapped to world in the Forge2DGame component and every Body that you want to use as a component should be wrapped in a BodyComponent, and added to the world in your Forge2DGame.

You can have have non-physics-related components in your Forge2DGame world’s component list along with your physical entities. When the update is called, it will use the Forge2D physics engine to properly update every BodyComponent and other components in the game will be updated according to the normal FlameGame way.

In Forge2DGame the gravity is flipped compared to Forge2D to keep the same coordinate system as in Flame, so a positive y-axis in the gravity like Vector2(0, 10) would be pulling bodies downwards, meanwhile, a negative y-axis would pull them upwards. The gravity can be set directly in the constructor of the Forge2DGame.

A simple Forge2DGame implementation example can be seen in the examples folder.

Units and scale

Forge2D is Box2D, and Box2D is tuned for meters, kilograms and seconds. Lay your world out in meters at a realistic scale, aiming to keep moving bodies roughly between 0.1 and 10 of them, with 1 meter being the sweet spot. How large that is on screen is a separate decision, and it is what metersToPixels is for.

Note

If you used flame_forge2d before the Box2D v3 migration, this is a change of advice. The old version had a hard maxTranslation of 2 meters per step, so about 120 m/s, and the docs told you to lay the world out much smaller than a meter to stay under it. That limit is now WorldDef.maximumLinearSpeed, which defaults to 400 m/s and is settable per world through Forge2DWorld(definition: WorldDef(...)). When you pass a definition, also pass the gravity argument (or set WorldDef.gravity explicitly), because the definition’s default is Box2D’s y-up (0, -10) rather than Flame’s y-down (0, 10). There is no longer a reason to shrink the world, and there are good reasons not to.

Why a shrunken world misbehaves

A handful of Box2D’s tolerances are absolute lengths rather than fractions of the shapes they apply to, so in a world laid out at a much smaller scale than a meter they stop being negligible and start dominating:

Tolerance

Default

What it does in a world only a meter across

Tolerances.speculativeDistance

0.02 m

contacts are reported across 2% of the world

WorldDef.restitutionThreshold

1 m/s

nothing ever bounces

WorldDef.hitEventThreshold

1 m/s

no hit events are ever generated

BodyDef.sleepThreshold

0.05 m/s

bodies fall asleep while still moving

WorldDef.maxContactPushSpeed

3 m/s

overlapping bodies are pushed apart violently

Tolerances.aabbMargin

0.05 m

broadphase bounds dwarf the shapes

The first one is the one that gets reported as a bug. Box2D creates contact points for shapes that are approaching but have not touched yet, which is what stops fast bodies from passing through things and removes most collision jitter. It also means beginContact fires while there is still a visible gap of up to Tolerances.speculativeDistance. A body that is not comfortably larger than that is permanently in contact with its neighbors. flame_forge2d prints a debug-mode warning once when it notices a moving body that small.

Scaling a world up

If your world is currently too small, scale it up and scale gravity with it. That last part is the one that is easy to miss: scaling lengths alone makes everything look like it is moving in treacle, while scaling lengths and gravity by the same factor leaves the timing of the simulation completely unchanged. For a length scale factor of S:

Quantity

Scale by

lengths, positions, radii, velocities, gravity, accelerations

S

densities, friction, restitution, damping, angular velocities

1, unchanged

masses

forces, linear impulses

torques, rotational inertia, angular impulses

S⁴

time

1, unchanged

So a world that was 1 meter tall with a 0.02 m ball and a gravity of 9.81 becomes a world 10 meters tall with a 0.2 m ball and a gravity of 98.1, behaving identically but comfortably inside the range Box2D is tuned for. Divide metersToPixels by the same factor to keep it the same size on screen.

When the layout cannot change

When scaling the world is not practical, tell Box2D how many of your length units make up a meter and every tolerance in the first table above moves with it:

class MyGame extends Forge2DGame {
  MyGame() : super(lengthUnitsPerMeter: 0.04);
}

A good rule of thumb is the height of your player character: if it is 0.04 units tall and you think of it as a person, pass 0.04. You are then responsible for gravity, densities and forces being sensible at that scale, using the same table.

This is a process-wide setting inside Box2D that cannot change once a physics world exists, so it can only be passed to the constructor, and several games running at the same time have to agree on it. A game that asks for a different value than one already in effect throws a StateError rather than quietly corrupting the simulation.

Forge2DWorld

The Forge2DWorld is a the world that all your [BodyComponent]s live in. In the Forge2DGame there is a Forge2DWorld instance called world by default, which is where you should add your BodyComponents.

If you want to swap between worlds you can create your own Forge2DWorld instance and assign it to the Forge2DGame instance’s world property, game.world = Forge2DWorld().

If you would like to re-use a world later and have it keep its physics state you have to make sure that the bodies aren’t destroyed when the world is removed from the game. You can do this by setting world.destroyBodiesOnRemove to false, like game.world.destroyBodiesOnRemove = false;.

The underlying Forge2D physics world is available as world.physicsWorld, which you can use to access the parts of the Forge2D API that Forge2DWorld doesn’t wrap, like creating joints or polling the raw event streams.

BodyComponent

The BodyComponent is a wrapper for the Forge2D body, which is the body that the physics engine is interacting with. A body carries one or more Shapes, which are created from a ShapeGeometry (Circle, Capsule, Segment or Polygon, plus chains via body.createChain) and an optional ShapeDef that holds the surface material (friction, restitution), density, filter, and event flags.

To create a BodyComponent you can either:

  • override createBody() and create and return your created body;

  • use the default createBody() implementation by passing a BodyDef instance (and optionally a list of ShapeSpec instances, which pair a ShapeGeometry with an optional ShapeDef) to the BodyComponent’s constructor;

  • use the default createBody() implementation and assign a BodyDef instance to this.bodyDef, and optionally a list of ShapeSpec instances to this.shapeSpecs.

final ball = BodyComponent(
  bodyDef: BodyDef(type: BodyType.dynamic),
  shapeSpecs: [
    ShapeSpec(
      Circle(radius: 0.5),
      ShapeDef(material: SurfaceMaterial(restitution: 0.8)),
    ),
  ],
);

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 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 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, since Forge2D doesn’t have a concept of nested bodies. So bodies should live on the top level in the physics world, Forge2DGame.world. So instead of add(Weapon())), world.add(Weapon()) should be used (as below), and the Player should also of course initially be added to the world.

class Weapon extends BodyComponent {
  @override
  Future<void> onLoad() async {
    await super.onLoad();
    // ...
  }
}

class Player extends BodyComponent {
  @override
  Future<void> onLoad() async {
    await super.onLoad();
    world.add(Weapon());
  }
}

Later you might want to add bullets coming from your weapon, these are added to the world in the same sense, but if they are going to be moving very fast, make sure that you set isBullet = true to avoid some tunneling problems.

Contact callbacks

Forge2DGame provides a simple out-of-the-box solution to propagate contact events.

Contact events occur whenever two Shapes meet each other. These events allow listening when these Shapes begin to come in contact (beginContact) and cease being in contact (endContact). Sensor overlaps are delivered through the same callbacks.

There are multiple ways to listen to these events. One common way is to use the ContactCallbacks class as a mixin in the BodyComponent where you are interested in these events.

class Ball extends BodyComponent with ContactCallbacks {
  ...
  void beginContact(Object other, Contact contact) {
    if (other is Wall) {
      // Do something here.
    }
  }
  ...
}

For the above to work, the Ball’s body.userData or contacting shape.userData must be set to a ContactCallbacks. And if Wall is a BodyComponent its body.userData or contacting shape.userData must be set to Wall.

If userData is null the contact events are ignored, it is null by default.

Forge2D only generates events for shapes that have opted in to them, so the involved shapes also need ShapeDef.enableContactEvents set to true (and ShapeDef.enableSensorEvents for sensors and their visitors). The default createBody() implementation of BodyComponent enables these flags automatically for shapes created through shapeSpecs when a ContactCallbacks is present in the body’s or shape’s userData, but if you override createBody() you need to set them yourself:

class Ball extends BodyComponent with ContactCallbacks {
  ...

  @override
  Body createBody() {
    ...
    final bodyDef = BodyDef(
      userData: this,
    );
    final shapeDef = ShapeDef(
      enableContactEvents: true,
    );
    ...
  }

}

Every time Ball and Wall begin to come in contact beginContact will be called, and once the shapes cease being in contact, endContact will be called.

The old preSolve and postSolve callbacks no longer exist. To disable a contact before it is solved (for example for one-sided platforms), use world.preSolveCallback together with ShapeDef.enablePreSolveEvents. To measure impact strength, enable ShapeDef.enableHitEvents and poll world.physicsWorld.contactEvents.hit.

An implementation example can be seen in the Flame Forge2D example.