feat!: Migrate flame_forge2d to the Box2D v3 based forge2d - #3952
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.
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.
|
I'm testing this with my existing game / paradigm, I'll let you know. If you want I can also do a full review of the code |
|
oops something happened with gravity :D I think I also need to re-do my contact callback / hit recognition (I might have forgot to toggle it on) because it should have detected the end of the level. I'll play around some more... The good news is, all the basics work and it really wasn't a huge effort to migrate the code so far Screencast_20260803_170348.webmWould I expect to see lower CPU use? (it's hard to tell with my app the majority of CPU comes from data streaming, logging network message polling) |
That'd be awesome! :)
You would mostly see a lower CPU usage when you have a lot of bodies. |
Hmm true, maybe we would have to spin up different Box2D backends for each game then. I think it's quite rare to have multiple Forge2DGame instances running at the same time, so maybe easiest would be to start with having it as an argument in the constructor like you suggest. |
|
Anyway, regarding the unit scaling, I guess the documentation will need to be updated, because I think the previous version which required a high zoom level for the camera (and therefore "small" physical objects), means that anyone who implemented a flame game with small objects to avoid the speed limit will likely have issues with objects triggering collisions. And yes, after testing, scaling, and changing the value of metersToPixels to something like 100 seems to work, but there is a side effect, because the physical distance increases, any forces will move an object relatively less. e.g. if gravity is 10, and i have a 10m high world, it will take 1s to get to the bottom. if I have to relatively scale everything up to avoid the 0.02m predictive collision, and I increase the world to 100m, it will now take the object 10 seconds to traverse the playing field...of course gravity + applied forces can also be scaled, but then it might end up hitting the speed limit again? I feel like I'm missing some fundamental piece of the puzzle here Screen.Recording.2026-08-11.at.14.14.00.mov |
No probs, didn't need it in the end, I just didn't see something and wondered where it was rendering but I found the typo.
I'm not sure how realistic that is with the dart VM anyway, I have tried to mess around with that for my native lib but ended up giving up (different circumstances but it didn't seem worth the trouble) I have now updated my game to have everything (including physics values) derived from initial constants * constant scaling factor and it seems to work just fine. In my game I have up to 4 Forge2DGame instances running, but they all share the exact same physics If it is a constructor arg, it might make sense having a wrapped static value that no-ops after the forge2d engine is initialised, otherwise the behaviour might become completely undefined if it's changed (globally) after init...that doesn't sound like a very nice solution, but i don't know how many people would even want to use this, it's just in the case where the current object sizes would bump into the speculative collision buffer AND where it would be a huge job to refactor the scale (luckily I had derived most of my object positions and sizes already from the start while I was messing around with layout) |
|
@zeyus I had a further look into how other engines that integrate box2d do it, I'll modify this PR a bit according to that. I also got this reply from the LLM:
|
Box2D v2 clamped every body to maxTranslation of 2 meters per step, so about 120 m/s, and flame_forge2d told people to lay their world out much smaller than a meter to stay under it. Box2D v3 replaced that clamp with the per-world WorldDef.maximumLinearSpeed, which defaults to 400 m/s, and added speculative contacts, which report a contact as soon as two shapes are within 0.02 m of each other. The old advice went from unnecessary to harmful: a sub-meter world now reports contacts across visible gaps, never bounces, and puts bodies to sleep while they are still moving. - Forge2DViewfinder and the docs no longer justify a shrunken world with the speed limit that no longer exists, and the docs gained a section on units and scale with the tolerances involved and how quantities scale. - defaultMetersToPixels goes from 10 to 100, so that a screenful of the default is a room rather than a city block. - Forge2DGame gains lengthUnitsPerMeter, forwarded to initializeForge2D, for worlds that cannot be laid out at a realistic scale. - BodyComponent prints a debug-mode warning once when it creates a moving body small enough for the speculative distance to dominate it, which is the failure that gets reported as a bug. The examples keep the scale they were written for by pinning metersToPixels, except the package example, which is rescaled to show a world at the default.
flame_forge2d now needs initializeForge2D(lengthUnitsPerMeter:) and Tolerances, which are in flame-engine/forge2d#120 and not in the published 0.15.0. The constraint is raised to the ^0.16.0 that melos will cut from that PR, with a git override so that the workspace resolves in the meantime. Remove the override once 0.16.0 is out.
# Conflicts: # examples/lib/stories/bridge_libraries/flame_forge2d/joints/mouse_joint.dart
Forge2DGame.onLoad now awaits initializeForge2D, which loads the Box2D WebAssembly module on the web, so an override that does not await it lets the world be created before the module is ready.
The conflict test relied on an earlier test having created a world at 100 units per meter, so it failed when run alone or under a randomized ordering. The body scale test sat exactly on the warning threshold and described its size wrongly, and the non-uniform scale assert hid under the non-positive values test name.
…ty note Flame's Circle and Polygon live in flame/experimental.dart rather than flame/geometry.dart, and the collision is on Shape rather than on Segment. The pubspec requires forge2d ^0.16.0, so the prose should not say 0.15. The WorldDef note now warns that the definition's gravity default is y-up.
# Description Box2D has a handful of tolerances that are absolute lengths rather than fractions of the shapes they apply to. The most visible one is the speculative distance: `manifold.c` stops generating contact points past `B2_SPECULATIVE_DISTANCE` (`4 * B2_LINEAR_SLOP`, so 0.02 m), and `contact.c` sets `touching = pointCount > 0`, which means `beginContact` fires while there is still a gap of up to 2 cm. A world laid out at a much smaller scale than a meter is dominated by this: shapes that are only a couple of centimeters across are permanently in contact with their neighbors. This came up while migrating flame_forge2d (flame-engine/flame#3952), where a reporter's ball had a radius of exactly 0.02, and it took a week to track down because the 0.02 is not discoverable from Dart. Box2D's answer is `b2SetLengthUnitsPerMeter`, which scales all of them. Its contract is `@warning This must be modified before any calls to Box2D`, which a free-standing setter cannot enforce, so it is exposed through the `initializeForge2D` gate that already has to run first: ```dart await initializeForge2D(lengthUnitsPerMeter: 100); ``` - Passing the value already in effect is a no-op, so several games that agree on a scale can each ask for it. The comparison round-trips through float32, since that is how Box2D stores it and values like `0.04` are not representable in either float width. - A value that conflicts with the one in effect throws a `StateError` once a `World` exists, rather than silently corrupting live simulations and the defaults Box2D hands out. - Non-positive and non-finite values throw an `ArgumentError`. `Tolerances` exposes the derived constants (`lengthUnitsPerMeter`, `linearSlop`, `speculativeDistance`, `aabbMargin`), so the 0.02 becomes a documented number that callers can reason about and assert against instead of a mystery. The web backend needs a keepalive wrapper: `b2SetLengthUnitsPerMeter` and `b2GetLengthUnitsPerMeter` are plain `B2_API` functions, so emcc drops them without one. The README gains a "Units" section covering the scale to lay a world out at, the absolute tolerances that bite when you do not, and how the other quantities scale when you rescale a world (lengths, velocities and gravity by `S`, masses by `S²`, forces and impulses by `S³`, torques by `S⁴`, with densities, friction, restitution and damping unchanged, which leaves the timing of the simulation unchanged). ## Testing `dart test` runs suites as isolates that share one process, and therefore one copy of the native library, so a suite that changes the length unit would be visible to whichever suites run alongside it. `dart_test.yaml` therefore sets `concurrency: 1`, so suites run one at a time as part of the normal test run, and the mutating suite puts the length unit back when it is done. The whole suite takes a couple of seconds either way. `melos test` passes and `melos analyze` is clean. ## Checklist - [x] The title of my PR starts with a [Conventional Commit] prefix (`fix:`, `feat:`, `docs:` etc). - [x] I have read the [Contributor Guide] and followed the process outlined for submitting PRs. - [x] I have updated/added tests for ALL new/updated/fixed functionality. - [x] I have updated/added relevant documentation in `docs` and added dartdoc comments with `///`. - [-] I have updated/added relevant examples in `examples`. ## Breaking Change - [ ] Yes, this is a breaking change. - [x] No, this is *not* a breaking change. Everything is additive: the new parameter is optional and defaults to leaving the length unit alone, and `Tolerances` is a new class. ## Related Issues Needed by flame-engine/flame#3952, which uses it for worlds that cannot be laid out at a realistic scale, and reports the underlying problem as a debug-mode warning. <!-- Links --> [issue database]: https://github.com/flame-engine/flame/issues [Contributor Guide]: https://github.com/flame-engine/flame/blob/main/CONTRIBUTING.md [Flame Style Guide]: https://github.com/flame-engine/flame/blob/main/STYLEGUIDE.md [Conventional Commit]: https://conventionalcommits.org
|
@spydon, def, that's what I was trying to get at with scaling the forces too (like I said in my game now all the physics and sizes are now scale-independent) but I think the debug warnings will help others who have used small worlds for the speed limits. This looks great now, can't wait to bring it into the fold :D nice work! |
zeyus
left a comment
There was a problem hiding this comment.
I can't officially review, but all good from my end those 2 comments are just minor
| // onLoad only runs once it has to be recreated here for the component | ||
| // to be usable again. Reading a destroyed body is not just stale, it | ||
| // reads freed native memory. | ||
| body = createBody(); |
There was a problem hiding this comment.
This is a great change, but it changes the behaviour slightly, might be worth a one line mention in one of the docs (e.g. previously removed BodyComponents can now be safely remounted)
All your reviews are appreciated! 😊 |
The length unit support in flame-engine/forge2d#120 was released as 0.15.1, so the git dependency override is no longer needed.
Remounting recreates the destroyed body, which the old version did not do. Also points the version reference back at Forge2D 0.15, since the length unit support shipped as 0.15.1.
|
@spydon just FYI, I tested today running my experiment with the new engine, it works great. I now pump the engine at 120Hz, and the raspberry pi I'm running the "headless" server on is sitting on a cool 30% CPU (including the isolates dedicated for logging and data streaming / polling)... |
That's awesome! Thanks for testing so thoroughly, all the feedback has been invaluable. |
# Conflicts: # examples/lib/stories/bridge_libraries/flame_forge2d/blob_example.dart # examples/lib/stories/bridge_libraries/flame_forge2d/joints/rope_joint.dart # packages/flame_forge2d/test/body_component_test.dart
Releases `flame_forge2d` 0.20.0, scoped to only this package so that the pending unreleased changes in `flame` and the other packages stay unreleased. New version: `flame_forge2d` 0.19.3+7 -> 0.20.0 Included changes: - **FIX**: Adapt to Flutter 3.47 (#3995) - **BREAKING** **FEAT**: Migrate flame_forge2d to the Box2D v3 based forge2d (#3952) The flame-side breaking changes that also touched this package's directory (#3968, #3961) only affected its tests and example, so they are intentionally left out of the changelog: they do not apply to flame_forge2d consumers until flame v2 is released. The package keeps its `flame: ^1.38.0` dependency, verified by resolving against the published flame 1.38.0 and forge2d 0.15.1 (`dart analyze` clean, all 91 tests pass, `flutter pub publish --dry-run` passes). Since melos refuses to version a package whose workspace dependency (`flame`) has pending changes outside the scope filter, the version bump, changelogs, and dependent constraint updates were applied manually in the same format melos generates. On merge, the `release-tag` workflow tags `flame_forge2d-v0.20.0` and triggers the publish workflow for it.

Description
Migrates
flame_forge2d(and everything in the monorepo that uses it) to the new forge2d, thenative Box2D v3.1.1 bindings that shipped in forge2d 0.15.0 through flame-engine/forge2d#115 (the
native rewrite) and flame-engine/forge2d#116 (web support through a WebAssembly build).
flame_forge2dnow depends on the publishedforge2d: ^0.15.1, which includesinitializeForge2D(lengthUnitsPerMeter:)andTolerancesfrom flame-engine/forge2d#120.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
Closes #2613