Box2DPhysicsEngine class

Box2D v3.2 physics engine adapter exposing the same duck-typed API as the pure-Dart PhysicsEngine.

Drop-in replacement on native platforms — PhysicsEngineFactory.create returns this class. The existing ECS (PhysicsSystem, PhysicsBridgeSystem) requires no changes because both engines share the same API surface and communicate through PhysicsBody objects.

Architecture:

  • Box2DWorld owns the native world + fixed-step accumulator.
  • Box2DBody wraps each native body handle with prev/current state.
  • After each step, _syncTransformsFromNative extracts all transforms in a single FFI call into pre-allocated native buffers (zero-copy).
  • pollContactBeginEvents lets just_game_engine's Box2DCollisionSystem fire CollisionEvents without coupling this package to the ECS.
  • registerImpactCallback routes high-velocity collisions from native worker threads to the Dart main isolate via NativeCallable.listener.
Inheritance

Constructors

Box2DPhysicsEngine({double gravityX = 0.0, double gravityY = 981.0, int subSteps = 4, int numThreads = 0})

Properties

alpha → double
Render interpolation alpha from the current frame (for ECS bridge use).
no setteroverride
bodies → List<PhysicsBody>
Get all bodies
no setteroverride
bodyCount → int
How many bodies the world holds, without copying them.
no setteroverride
debugRender ↔ bool
Whether debug rendering is enabled
getter/setter pairinherited
fixedTimestep → bool
Whether update sub-steps at a fixed 1/60 s. See PhysicsEngine.pureDart.
finalinherited
gravity → Vector2
Global gravity vector. Default: 981 units/s² (9.81 m/s² at 1 unit = 1 cm), matching the Box2D backend so both simulate identically.
finalinherited
hashCode → int
The hash code for this object.
no setterinherited
joints → List<JointConstraint>
All active joints (read-only view).
no setterinherited
numThreads → int
Number of native worker threads (0 → cores − 1, at most 4).
final
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited
stats → Map<String, dynamic>
Lightweight physics diagnostics from the last simulation step.
no setteroverride
subSteps → int
Number of Box2D sub-steps per fixed tick (4 recommended).
final

Methods

addBody(PhysicsBody body) → void
Add a PhysicsBody to the simulation.
override
addDistanceJoint(PhysicsBody a, PhysicsBody b, {double? length, double? minLength, double? maxLength, double stiffness = 0.0, double damping = 0.3}) → JointConstraint
override
addJoint(JointConstraint joint) → void
Add a joint constraint to the simulation.
inherited
addMouseJoint(PhysicsBody b, Vector2 target) → JointConstraint
inherited
addPrismaticJoint(PhysicsBody a, PhysicsBody b, Offset axis) → JointConstraint
override
addRevoluteJoint(PhysicsBody a, PhysicsBody b, Offset worldAnchor) → JointConstraint
override
addWeldJoint(PhysicsBody a, PhysicsBody b) → JointConstraint
override
addWheelJoint(PhysicsBody a, PhysicsBody b, Offset axis) → JointConstraint
override
applyLinearImpulse(PhysicsBody body, double ix, double iy) → void
Apply an instantaneous change in momentum to body.
override
cachePolygonShape(String cacheId, List<Offset> vertices) → void
inherited
castCircle(Offset origin, double radius, Offset motion, {Set<PhysicsBody>? exclude}) → ShapeCastResult?
Sweep a circle of radius from origin by motion and return the first body hit, or null if nothing is in the way.
inherited
castMover(MoverShape shape, Offset position, Offset translation, {MoverFilter filter = const MoverFilter()}) → double
How much of translation a mover of shape at position can move before it touches something: 1 for all of it (Box2D's b2World_CastMover). What it starts out touching is ignored, and a one-way platform stops it only when it comes from the solid side and is not partway through it.
override
castRay(Ray ray, {Set<PhysicsBody>? exclude}) → RayBodyHit?
Box2D's query filter cannot express "skip these specific bodies", only category/mask. So when exclude is non-empty we have to collect all hits and filter in Dart; otherwise the cheaper closest-hit call is enough. Either way a shape the ray starts inside is passed over.
override
castRayAll(Ray ray, {Set<PhysicsBody>? exclude}) → List<RayBodyHit>
Cast a ray and return all hits sorted nearest-first.
override
collideMover(MoverShape shape, Offset position, {MoverFilter filter = const MoverFilter()}) → List<MoverPlane>
The surfaces a mover of shape centred at position touches, as planes for MoverSolver.solvePlanes (Box2D's b2World_CollideMover, for a capsule or a box). Sensors, MoverFilter.ignore and bodies the filter leaves out are passed over; a one-way platform counts only from its solid side and only while the mover is not partway through it.
override
createDistanceJoint(PhysicsBody bodyA, PhysicsBody bodyB, {required double minLength, required double maxLength}) → Box2DJoint?
createPrismaticJoint(PhysicsBody bodyA, PhysicsBody bodyB, Offset worldAnchor, Offset axis) → Box2DJoint?
createRevoluteJoint(PhysicsBody bodyA, PhysicsBody bodyB, Offset worldAnchor) → Box2DJoint?
createWeldJoint(PhysicsBody bodyA, PhysicsBody bodyB, Offset worldAnchor) → Box2DJoint?
createWheelJoint(PhysicsBody bodyA, PhysicsBody bodyB, Offset worldAnchor, Offset axis) → Box2DJoint?
destroyJoint(Box2DJoint joint) → void
Destroy a Box2D joint and remove it from the engine's joint list.
dispose() → void
Clean up physics resources
override
getCachedPolygonShape(String cacheId) → List<Offset>?
inherited
initialize() → void
Initialize the physics engine
override
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
physicsBodyFromHandle(int handle) → PhysicsBody?
Resolve a packed body handle (from the impact callback) to a PhysicsBody.
pollBodyMoveEvents(void fn(PhysicsBody body, {required bool fellAsleep})) → void
Iterate body-move events from the last update.
override
pollContactBeginEvents(void fn(PhysicsBody a, PhysicsBody b, double nx, double ny)) → void
Iterate all begin-touch contact events from the last update.
override
pollContactEndEvents(void fn(PhysicsBody a, PhysicsBody b)) → void
Iterate all end-touch (separation) contact events from the last update.
override
pollSensorBeginEvents(void fn(PhysicsBody a, PhysicsBody b)) → void
Iterate sensor-begin events (sensor body first touched by visitor).
override
pollSensorEndEvents(void fn(PhysicsBody a, PhysicsBody b)) → void
Iterate sensor-end events (visitor left the sensor).
override
queryAABB(Rect rect) → List<PhysicsBody>
Return all active bodies whose bounding box — round all their shapes, turned with the body — overlaps rect. The native backend answers with Box2D's boxes, which it keeps a little larger than the shapes (by a tenth of a metre), so a body just outside rect can come too.
override
queryCircle(Offset center, double radius) → List<PhysicsBody>
Return all active bodies with a shape (turned with its body) overlapping a circle at center with radius.
inherited
queryPoint(Offset point) → List<PhysicsBody>
Return all active bodies containing point.
inherited
registerImpactCallback(void callback(int bodyA, int bodyB, double speed), {double speedThreshold = 100.0}) → void
Register a Dart callback for every contact whose bodies closed faster than speedThreshold (units per second) in a step: Box2D's hit events.
removeBody(PhysicsBody body) → void
Remove a physics body, and the joints that hold it. The contacts and overlaps it was in end without an event, as on the native backend.
override
removeJoint(JointConstraint joint) → void
Removes joint; a native one is destroyed, so its bodies come apart.
override
renderDebug(Canvas canvas, Size size) → void
Render debug visualization
override
setBodyAngularDamping(PhysicsBody body, double damping) → void
Set body's angular damping (spin decay per second).
override
setBodyBullet(PhysicsBody body, bool isBullet) → void
Turn continuous collision (PhysicsBody.isBullet) on or off for body. Only the native backend has it; here it is just the flag.
override
setBodyCanSleep(PhysicsBody body, bool canSleep) → void
Allow or forbid body to fall asleep. Forbidding it wakes the body.
override
setBodyDamping(PhysicsBody body, double damping) → void
Set body's linear damping (velocity decay per second).
override
setBodyFilter(PhysicsBody body, {int? categoryBits, int? maskBits, int? groupIndex}) → void
Change body's collision filter at runtime.
override
setBodyFriction(PhysicsBody body, double friction) → void
Set body's surface friction. Swap this to make a platform icy or sticky.
override
setBodyGravityScale(PhysicsBody body, double scale) → void
Set the per-body gravity multiplier. See PhysicsBody.gravityScale.
override
setBodyOneWay(PhysicsBody body, bool enabled, {OneWayDirection direction = OneWayDirection.fromAbove}) → void
Turn body into a one-way / pass-through platform, solid only when approached from direction.
override
setBodyRestitution(PhysicsBody body, double restitution) → void
Set body's bounciness.
override
setBodySensor(PhysicsBody body, bool isSensor) → void
Toggle sensor mode on body.
override
setBodyTransform(PhysicsBody body, double x, double y, {double? angle}) → void
Teleport body to (x, y), optionally also setting angle.
override
setBodyType(PhysicsBody body, BodyType type) → void
Change how body participates in the simulation.
override
setGravity(double gx, double gy) → void
Override world gravity at runtime — updates both the Dart gravity vector and the native Box2D world so they stay in sync.
override
toString() → String
A string representation of this object.
inherited
update(double deltaTime) → void
Advance physics by deltaTime seconds.
override

Operators

operator ==(Object other) → bool
The equality operator.
inherited