Forge2D - Dart bindings for the Box2D physics engine
Forge2D provides an idiomatic Dart API for the native
Box2D v3 physics engine. The C library is bundled,
compiled by the Dart/Flutter build through
native assets, and called over dart:ffi,
so simulations run at native speed with the real, actively maintained
Box2D.
You can use it independently in Dart or in your Flame project with the help of flame_forge2d.
- Dart 3.12+ (Flutter 3.38+), which builds the bundled C library automatically through build hooks.
- A C toolchain on native platforms: Xcode on macOS/iOS, Visual Studio Build Tools on Windows, clang or gcc on Linux, and the NDK on Android.
- Supported platforms: Android, iOS, macOS, Windows, Linux, and the web.
On the web the same API runs against a bundled WebAssembly build of Box2D
(about 220 KB). No setup is needed: initializeForge2D() finds the module
at the package asset path in Dart web apps, and at the bundled package
asset in Flutter web apps. For custom hosting setups the location can be
overridden with initializeForge2D(wasmUri: ...), and
dart run forge2d:setup_web copies the module into a web/ directory.
import 'package:forge2d/forge2d.dart';
Future<void> main() async {
await initializeForge2D();
final world = World();
// Static ground: a wide box whose top surface is at y = 0.
world
.createBody(BodyDef(position: Vector2(0, -1)))
.createShape(Polygon.box(50, 1));
// A dynamic box dropped from above.
final box = world.createBody(
BodyDef(type: BodyType.dynamic, position: Vector2(0, 10)),
)..createShape(Polygon.square(0.5));
for (var i = 0; i < 90; i++) {
world.step(1 / 60);
}
print(box.position); // The box has landed on the ground.
world.destroy();
}Highlights of the API:
World,Body,Shape,Chain, and the joints are cheap value-like handles over native ids; destroy things explicitly withdestroy().- Contact, sensor, and body-move events are polled from the world after
each step (
world.contactEvents,world.sensorEvents,world.bodyMoveEvents). - Ray casts (
castRayClosest,castRayAll,castRay), AABB overlap queries, and explosions are available onWorld. DebugDrawcan be implemented to render the physics world for debugging.
The standard bench2d benchmark (a 40-high pyramid of boxes, 256 frames), on the same machine:
| Engine | ms/frame (mean) |
|---|---|
| forge2d 0.14 (pure Dart port) | 9.37 |
| forge2d with native Box2D v3 | 0.51 |
Forge2D 0.15 is a ground-up rewrite on the Box2D v3 API. For the full walkthrough, see the Forge2D migration guide. The high-level concepts map as follows:
- Dart 3.12+ and a C toolchain are required, see Requirements. This is usually the biggest practical hurdle of the upgrade.
- On the web,
await initializeForge2D()has to run before the firstWorldis created. On native it is a no-op (the backend is created lazily), but cross-platform code should always await it. World,Body,Shape,Chain, and joints are value-like handles over native ids rather than Dart objects owning their state. Nothing is garbage collected: destroy things explicitly, and checkisValidif a handle may have outlived what it points at.Fixtureis gone: bodies carryShapes directly, created withbody.createShape(geometry, ShapeDef(...))where the geometry is aCircle,Capsule,Segment, orPolygon. Chains have their ownbody.createChain(ChainDef(points: ...)).EdgeShapeis replaced bySegment(standalone) and one-sided chain shapes for level geometry.- Friction, restitution, and rolling resistance live on
ShapeDef(material: SurfaceMaterial(...))instead of being fields on the oldFixtureDef. ContactListenercallbacks are replaced by events polled after each step:world.contactEvents,world.sensorEvents, andworld.bodyMoveEvents. Contact and sensor events are opted in per shape withShapeDef(enableContactEvents: true)andShapeDef(isSensor: true, enableSensorEvents: true); hit events needenableHitEvents. Custom filtering is aworld.customFilterCallback.- Query and ray-cast callback classes are gone:
world.castRayClosest,world.castRayAll, andworld.overlapAabbreturn their results directly, andworld.castRaytakes a plain closure.AABBis nowAabb. - Joints are created with type-specific methods such as
world.createRevoluteJoint(RevoluteJointDef(...)), and the set is now distance, filter, motor, mouse, prismatic, revolute, weld, and wheel. Gear, pulley, rope, friction, and constant-volume joints do not exist in Box2D v3. - Rotations are a
Rotrather than a raw angle:BodyDef(rotation: ...)andbody.setTransform(position, rotation)take one, built withRot.fromAngle(radians).body.anglestill reads back a double. body.applyForceandbody.applyLinearImpulsetake the point of application as a named argument,applyForce(force, point: ..., wake: true), and applying at the centre of mass is just leavingpointout.applyForceToCenteris gone.DebugDrawis an abstract class you implement and pass toworld.draw(debugDraw), with colors as0xRRGGBBints.- The particle system (LiquidFun) is not part of Box2D v3 and has been removed; stay on forge2d 0.14 if you depend on it.
- Worlds default to
subStepCount: 4instepinstead of velocity and position iterations. - A bare
World()now has the Box2D default gravity of(0, -10); the old API defaulted to zero gravity. Top-down games should passWorld(gravity: Vector2.zero()). - Destroying bodies, shapes, chains, or joints while the world is stepping
(from a collision callback) is deferred until the step ends; creating
them mid-step throws a
StateErrorinstead of the old silent queueing.world.destroy()itself is not deferred and throws mid-step.
Box2D was first written in C++ and released by Erin Catto in 2007, and it is still actively maintained.
It was ported to Java (jbox2d) by Daniel Murphy around 2015, then from that Java port it was ported to Dart by Dominic Hamon and Kevin Moore.
A few years after that Lukas Klingsbo refactored the code to better follow the Dart standard and the project was renamed to Forge2D.
Since Box2D v3 rewrote the engine in C with a first-class embedding API, Forge2D moved from being a port to being bindings: the same idiomatic Dart surface, powered by the real engine.
There have also been countless other contributors which we are very thankful to!
- The Flame engine team who is continuously working on maintaining and improving Forge2D.
- Erin Catto for Box2D itself, which this package embeds.
- Special thanks to Lukas Klingsbo, a Flame team member who took this project under his wing and greatly improved the project!
- The Dart port of Box2D that earlier versions of Forge2D were built on.