feat!: Migrate flame_forge2d to the Box2D v3 based forge2d - #3952
Open
spydon wants to merge 22 commits into
Open
feat!: Migrate flame_forge2d to the Box2D v3 based forge2d#3952spydon wants to merge 22 commits into
spydon wants to merge 22 commits into
Conversation
Every widget of the default flutter create counter app rests on a Forge2D body and drops to the floor, keeping its normal function. Pressing the button counts and launches it, and pressing any widget sends it flying. The bodies follow the shape Material draws: capsules for the text, a rounded box for the button. Every widget is given the same mass so the wide app bar does not crush the small counter, and the walls are solid boxes so nothing squeezes out through a corner.
Add a heroTag to the counter button, drop a redundant trailing zero in hue_decorator, teach cspell the word subclassing and use the en_US spelling of neighboring.
spydon
marked this pull request as ready for review
July 21, 2026 12:03
Contributor
There was a problem hiding this comment.
Pull request overview
This PR migrates flame_forge2d (and the repo’s usages/examples/docs) from the legacy pure-Dart Box2D 2.x API to the new Box2D v3-based forge2d ^0.15.0, introducing the new polled contact/sensor event model, updated shape/joint/query APIs, and the required Forge2D initialization flow (especially for web/WASM).
Changes:
- Reworks
flame_forge2dcore APIs: lazy physics-world creation + stepping/substepping, new contact event dispatching, newContactwrapper, andBodyComponentshape rendering viaShape.geometry. - Introduces meters-to-pixels scaling via
Forge2DViewfinder/Forge2DGame.metersToPixels(decoupled from camera zoom). - Migrates/updates tests, examples, and documentation (including new migration guides) to the Forge2D 0.15 / Box2D v3 API surface.
Reviewed changes
Copilot reviewed 71 out of 77 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| scripts/customer_testing.dart | Excludes flame_forge2d from customer testing with rationale (native toolchain requirement). |
| packages/flame/lib/src/rendering/hue_decorator.dart | Minor constant formatting change. |
| packages/flame/lib/src/events/dispatchers/multi_tap_dispatcher.dart | Marks handleTapDown as @visibleForTesting for tests. |
| packages/flame_forge2d/test/world_contact_listener_test.dart | Removes tests for deleted legacy listener API. |
| packages/flame_forge2d/test/helpers/mocks.dart | Removes mocks tied to removed legacy Forge2D types. |
| packages/flame_forge2d/test/helpers/helpers.dart | Removes obsolete export. |
| packages/flame_forge2d/test/forge2d_world_test.dart | Updates/expands world behavior tests for new stepping/bodies/query/callback APIs. |
| packages/flame_forge2d/test/forge2d_viewfinder_test.dart | Adds tests for new meters-to-pixels viewfinder behavior. |
| packages/flame_forge2d/test/forge2d_game_test.dart | Updates screen/world conversion tests for meters-to-pixels scaling. |
| packages/flame_forge2d/test/contact_test.dart | Adds tests for new flame-side Contact wrapper. |
| packages/flame_forge2d/test/contact_events_dispatcher_test.dart | Adds tests for new contact/sensor event dispatcher + integration with Forge2DGame. |
| packages/flame_forge2d/test/contact_callbacks_test.dart | Updates tests for new contact model and auto-enabling event flags. |
| packages/flame_forge2d/test/body_component_test.dart | Migrates rendering/shape tests and adds coverage for new behaviors. |
| packages/flame_forge2d/README.md | Updates Forge2D description + adds 0.19→0.20 migration section. |
| packages/flame_forge2d/pubspec.yaml | Bumps dependency to forge2d: ^0.15.0. |
| packages/flame_forge2d/lib/world_contact_listener.dart | Removes legacy listener-based API. |
| packages/flame_forge2d/lib/forge2d_world.dart | Implements lazy world creation, stepping/substeps, bodies tracking, queries, callbacks, and event dispatching. |
| packages/flame_forge2d/lib/forge2d_viewfinder.dart | Adds Forge2DViewfinder to separate meters-to-pixels scaling from camera zoom. |
| packages/flame_forge2d/lib/forge2d_game.dart | Awaits Forge2D initialization on load; wires Forge2DViewfinder and metersToPixels. |
| packages/flame_forge2d/lib/flame_forge2d.dart | Updates exports (adds new types, removes old listener). |
| packages/flame_forge2d/lib/contact.dart | Adds flame-side Contact wrapper spanning contact + sensor events. |
| packages/flame_forge2d/lib/contact_events_dispatcher.dart | Adds polled-events dispatcher routing to ContactCallbacks via userData. |
| packages/flame_forge2d/lib/contact_callbacks.dart | Updates docs/API to shape-based contacts and new preSolve/hit guidance. |
| packages/flame_forge2d/lib/body_component.dart | Replaces fixtures with ShapeSpec/shapes; updates rendering and hit-testing for new geometry model. |
| packages/flame_forge2d/example/lib/main.dart | Migrates package example to new shape/material APIs. |
| examples/lib/stories/bridge_libraries/flame_forge2d/widget_example.dart | Rebuilds widget overlay example on new API; bodies fitted to measured widget sizes. |
| examples/lib/stories/bridge_libraries/flame_forge2d/utils/style.dart | Adds shared example palette + Forge2DExampleGame + glowing rendering mixin. |
| examples/lib/stories/bridge_libraries/flame_forge2d/utils/joint_renderer.dart | Adds joint rendering helpers compatible with new joint API. |
| examples/lib/stories/bridge_libraries/flame_forge2d/utils/boxes.dart | Migrates box components + adds mouse-joint rendering. |
| examples/lib/stories/bridge_libraries/flame_forge2d/utils/boundaries.dart | Migrates boundary walls; updates defaults and styling. |
| examples/lib/stories/bridge_libraries/flame_forge2d/utils/balls.dart | Migrates balls to new shapes/materials; integrates shared styling. |
| examples/lib/stories/bridge_libraries/flame_forge2d/tap_callbacks_example.dart | Migrates example to Forge2DExampleGame + async onLoad. |
| examples/lib/stories/bridge_libraries/flame_forge2d/sprite_body_example.dart | Migrates sprite-body example to new API and async onLoad. |
| examples/lib/stories/bridge_libraries/flame_forge2d/revolute_joint_with_motor_example.dart | Migrates revolute/motor example; updates shapes/joints/materials. |
| examples/lib/stories/bridge_libraries/flame_forge2d/raycast_example.dart | Migrates raycast example to castRayClosest/castRayAll. |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/wheel_joint.dart | Adds new wheel joint example for Box2D v3 API. |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/weld_joint.dart | Migrates weld joint example and adds joint rendering. |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/rope_joint.dart | Removes rope joint example (not in Box2D v3). |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/revolute_joint.dart | Migrates revolute joint example and adds joint rendering. |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/pulley_joint.dart | Removes pulley joint example (not in Box2D v3). |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/prismatic_joint.dart | Migrates prismatic joint example; replaces custom renderer with shared renderer. |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/mouse_joint.dart | Migrates mouse joint example; adds joint rendering. |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/motor_joint.dart | Migrates motor joint example; uses new joint API + renderer. |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/gear_joint.dart | Removes gear joint example (not in Box2D v3). |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/friction_joint.dart | Removes friction joint example (not in Box2D v3). |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/filter_joint.dart | Adds filter joint example for Box2D v3 API. |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/distance_joint.dart | Migrates distance joint example; uses new spring parameters + renderer. |
| examples/lib/stories/bridge_libraries/flame_forge2d/joints/constant_volume_joint.dart | Removes constant-volume joint example (not in Box2D v3). |
| examples/lib/stories/bridge_libraries/flame_forge2d/flame_forge2d.dart | Updates dashbook story set to match new/removed joint examples. |
| examples/lib/stories/bridge_libraries/flame_forge2d/drag_callbacks_example.dart | Migrates drag callbacks example and async onLoad. |
| examples/lib/stories/bridge_libraries/flame_forge2d/domino_example.dart | Reworks domino example for new API and shared styling. |
| examples/lib/stories/bridge_libraries/flame_forge2d/contact_callbacks_example.dart | Migrates contact callbacks example and async onLoad. |
| examples/lib/stories/bridge_libraries/flame_forge2d/composition_example.dart | Migrates composition example to new base game + async onLoad. |
| examples/lib/stories/bridge_libraries/flame_forge2d/camera_example.dart | Migrates camera example to Forge2DExampleGame and new scaling model. |
| examples/lib/stories/bridge_libraries/flame_forge2d/blob_example.dart | Removes blob example (depended on removed joint/system). |
| examples/lib/stories/bridge_libraries/flame_forge2d/animated_body_example.dart | Migrates animated body example and async onLoad. |
| examples/lib/main.dart | Updates direct-route game registry to match available joint examples. |
| examples/games/padracing/lib/wall.dart | Migrates PadRacing wall shape/material usage. |
| examples/games/padracing/lib/tire.dart | Migrates tire body + revolute joint creation; updates renamed APIs. |
| examples/games/padracing/lib/padracing_game.dart | Updates base game scaling init (meters-to-pixels vs zoom). |
| examples/games/padracing/lib/lap_line.dart | Migrates lap sensor to shape-based sensor events. |
| examples/games/padracing/lib/car.dart | Migrates car body shapes/materials and enables sensor events for lap detection. |
| examples/games/padracing/lib/ball.dart | Migrates ball component to shape/material/contact-event flags. |
| doc/other_modules/other_modules.md | Adds Forge2D module + navigation entries. |
| doc/other_modules/forge2d/migration.md | Adds Forge2D 0.14→0.15 migration guide. |
| doc/other_modules/forge2d/forge2d.md | Adds Forge2D module overview and getting-started docs. |
| doc/bridge_packages/flame_forge2d/migration.md | Adds flame_forge2d 0.19→0.20 migration guide. |
| doc/bridge_packages/flame_forge2d/forge2d.md | Updates Forge2D bridge docs for new initialization and scaling model. |
| doc/bridge_packages/flame_forge2d/flame_forge2d.md | Adds migration page to docs toctree. |
| .github/.cspell/words_dictionary.txt | Adds “subclassing” to spelling dictionary. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Asserts are stripped in release builds, so a Box2D world that fails to allocate (for example when the world limit is hit) would be cached and used by every later step, crashing far from the cause. Check it at runtime and throw a StateError.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Migrates
flame_forge2d(and everything in the monorepo that uses it) to the new forge2d, thenative Box2D v3.1.1 bindings that are currently in review in the stacked chain
flame-engine/forge2d#115 (the native rewrite) and flame-engine/forge2d#116 (web support through a
WebAssembly build). This both prepares the migration and serves as real-world validation of the new
forge2d API while that chain is in review.
flame_forge2dnow depends on the publishedforge2d: ^0.15.0.flame_forge2dstays in thecustomer_testing.dartexclusions, because forge2d compiles Box2D from source through the Dartbuild hooks and so needs a C toolchain on the flutter/flutter presubmit runner; the reasoning is
documented next to the exclusion.
Core package
Forge2DWorldsteps the world withphysicsWorld.step(dt, subStepCount: subStepCount)and thendispatches the polled contact and sensor events through the new overridable
ContactEventsDispatcher(replacingWorldContactListener, since listener interfaces no longerexist). It keeps a Dart-side
bodiesset (upstream no longer exposes one) which the gravitysetter uses to wake bodies, and exposes the new query API (
castRayClosest,castRay,castRayAll,overlapAabb) plus forwarding setters forpreSolveCallbackandcustomFilterCallback.ContactCallbackskeeps its familiarbeginContact(Object other, Contact contact)shape througha new lightweight flame-side
Contactclass that wraps both contact and sensor events.BodyComponentrenders from the newShape.geometryread-back (Circle,Capsule,Segment,Polygon; chain segments arrive asSegments), withrenderShape/renderSegmentand a newrenderCapsule.fixtureDefsis replaced byshapeSpecs(a list ofShapeSpec, pairing aShapeGeometrywith an optionalShapeDef). The defaultcreateBody()auto-enablescontact/sensor events on shapes whose body or shape userData is a
ContactCallbacks, since thenew engine only generates events for shapes that opted in.
>=3.12.0/ Flutter>=3.44.0(root workspace, melos bootstrap,package, and
FLUTTER_MIN_VERSIONin CI).Forge2DGameawaitsinitializeForge2D()in itsonLoad, andForge2DWorldcreates itsphysics world lazily so that this can happen first. Without it every
Forge2DGamethrows on theweb, since that call is what loads the Box2D WebAssembly module. Code that creates a world
outside of a
Forge2DGamehas to await it itself.Fixture/Contact/Manifoldare gone) andall goldens regenerated.
flame'sMultiTapDispatcher.handleTapDownannotation changed from@internalto@visibleForTestingso the tests can use it without ignores.Examples and docs
padracing, and the package example are migrated. The examples for jointsthat no longer exist in Box2D v3 (gear, pulley, rope, friction, constant-volume) and the blob
example are removed.
doc/bridge_packages/flame_forge2d/forge2d.mdandjoints.mdare rewritten for the new API(including the new filter and wheel joints).
Verification
flutter testinpackages/flame_forge2d(45 tests, compiles native Box2D through build hooks),goldens visually inspected.
dart analyzeclean across the whole workspace.flutter build webof the examples app confirms the Box2D wasm module is bundled automaticallyat the package asset path.
Notes for the forge2d review (found during this migration)
Shape.geometryread-back was added upstream during this work and is what makesBodyComponentrendering possible without a Dart-side geometry registry.Forge2DWorld.BodyComponents no longer receive a finalendContactfor contacts that end due to thedestruction (the old engine fired those synchronously inside destroy).
Checklist
docsand added dartdoc comments with///.examplesordocs.Breaking Change?
Migration instructions
FixtureandFixtureDefare gone: create shapes withbody.createShape(geometry, ShapeDef(...)), where the geometry is aCircle,Capsule,Segment, orPolygon. Friction and restitution now live inShapeDef.material(a
SurfaceMaterial).body.fixturesbecomesbody.shapes.BodyComponent.fixtureDefsbecomesshapeSpecs, a list ofShapeSpec(geometry, [shapeDef]).renderFixturebecomesrenderShape,renderEdgebecomesrenderSegment, andrenderChainis gone (chain segments render as segments).
CircleShape()..radius = rbecomesCircle(radius: r, center: c),EdgeShape()..set(a, b)becomesSegment(point1: a, point2: b),PolygonShape()..setAsBoxXY(w, h)becomesPolygon.box(w, h), andChainShape()..createChain/createLoopbecomesbody.createChain(ChainDef(points: ..., isLoop: ...))(chains now require at least four points; the first and last points of an open chain areghost anchors).
BodyDef.anglebecomesBodyDef(rotation: Rot.fromAngle(angle)).ShapeDef.enableContactEvents(andenableSensorEventsfor sensors and their visitors). The defaultBodyComponent.createBody()enables them automatically when a
ContactCallbacksis present in the body's or shape'suserData.
ContactCallbacks.beginContact/endContactkeep their signatures but receive the new flame-sideContact(withshapeA,shapeB,bodyA,bodyB, begin-onlynormal/points, andisSensorEvent).preSolve/postSolveare removed: useForge2DWorld.preSolveCallbackwithShapeDef.enablePreSolveEvents, and hit events (ShapeDef.enableHitEvents+world.physicsWorld.contactEvents.hit) respectively.WorldContactListeneris replaced byContactEventsDispatcher, and thecontactListenerparameter of
Forge2DGamebycontactEventsDispatcher.world.physicsWorld.createRevoluteJoint(RevoluteJointDef(bodyA: ..., bodyB: ...))andjoint.destroy(). The available joints are distance, filter, motor, mouse, prismatic, revolute,weld, and wheel; gear, pulley, rope, friction, and constant-volume joints no longer exist. The
def
initializehelpers are gone; anchors are passed as local points(
body.localPoint(worldAnchor)).world.raycast(callback, p1, p2)becomescastRayClosest/castRay/castRayAll(taking an origin and a translation),
queryAABBbecomesoverlapAabb, andclearForcesandthe particle system are removed.
worldCenterbecomesworldCenterOfMass,setAwake(x)becomesisAwake = x,getInertia()becomesrotationalInertia, andworldVector(v)becomesrotation.rotate(v).platforms, and nothing extra on the web (the Box2D wasm module is bundled automatically).
Related Issues
Depends on flame-engine/forge2d#115 and flame-engine/forge2d#116.