flutter3d
Showcase Changelog 63 packages API reference

Migrating from 0.8 to 1.0

1.0 is the release the engine promises to keep: from it on, a break comes only in a major version. To get there, 0.8's API changed in 2095 places, 308 of them left to you; 380 are backend internals no application called. The rest a program carries out for you, and three tools share the work. All three read one table, flutter3d_build/lib/migrations/0.8_to_1.0.yaml, so they agree about what changed.

  • Your dependencies on 1.0.0-rc.1, your imports where the names now live
  • Renames, moved names and deprecated calls rewritten by tools
  • A short list of what needs a decision from you, each with a TODO in the code

Run the migration #

Commit or stash first: the migration edits your files in place, and a clean git diff is the easiest way to read what it did.

dart pub global activate flutter3d_build 1.0.0-rc.1
dart pub global activate flutter3d_lints 1.0.0-rc.1
dart pub global run flutter3d_build:migrate --dry-run .   # see what it would do
dart pub global run flutter3d_build:migrate .

Activate the tools globally rather than adding them to your project. Your pubspec still asks for 0.8, and a 1.0 tool cannot resolve beside it until the migration has moved the constraints.

migrate works through the project in this order:

  1. Dependencies. Every dependency on a flutter3d package moves to ^1.0.0-rc.1, or to the package's own number for the few that are versioned apart (pad_input, flutter3d_pointer_lock). A renamed package is renamed in the pubspec (pointer_lock is flutter3d_pointer_lock). Path and git dependencies are left alone and listed.
  2. Imports of libraries that moved to another package are rewritten.
  3. flutter pub get (or dart pub get for a plain Dart package).
  4. dart fix --apply. Each flutter3d package ships a lib/fix_data.yaml generated from the table. It renames what was renamed and imports moved names from where they live now. These are the same fixes your editor offers one at a time.
  5. Rewrites that need the resolved code, which dart fix cannot express: the deprecated capability getters (device.supportsWireframe becomes device.features.has(DeviceFeature.wireframe)), and implements X for the types 1.0 made base mixin classes. This step is dart pub global run flutter3d_lints:migrate, and migrate runs it for you.
  6. Dependencies again, for the packages the new imports need, such as flutter3d_core when a name left the flutter3d facade.
  7. The report. It lists the files changed and what is left for you. Every item on the list also has a // TODO(flutter3d-1.0): above it in the code, with a link to its line below.

migrate reads the release your project is on from its pubspec.lock and applies every migration table after it, so a project on 0.8 goes through 0.8_to_1.0.yaml and, once there is a later candidate, the tables after it in the same run. --from <version> says the release when there is no lock. --dry-run makes the same changes in a copy next to your project, prints the report and removes the copy.

Afterwards, run flutter analyze. What is left is either on the report or is a change in behaviour the analyzer cannot see. The table below says which entries change behaviour.

In the editor #

flutter3d_lints is also an analysis server plugin. Listed in analysis_options.yaml, it reports each use of something 1.0 changed: a flutter3d_migrate warning with a quick fix where the change can be written, and flutter3d_migrate_by_hand with the guide's link where it cannot.

plugins:
  flutter3d_lints: ^1.0.0-rc.1

The plugin also turns on the fixed-step rules described in its README. A plugin's quick fixes are applied one at a time; dart fix does not apply them in bulk, which is why step 5 above exists.

GameLoop to EngineLoop #

GameLoop is gone in 1.0, and EngineLoop takes its place: the fixed step runs as named systems in ordered phases, and plugins add theirs to the same loop. The change is a shape rather than a rename, so no tool makes it for you:

// 0.8
final loop = GameLoop(input: input, onStep: (dt) => world.step(dt));
// every frame:
loop.advance(dt);
// 1.0
final loop = EngineLoop(input: input)
  ..addSystem('world', LoopPhase.physics, (ctx) => world.step(ctx.dt));
// every frame:
loop.frame(dt);

paused, alpha, playback, recorders, drainLook and longestFrame keep their names. EngineLoop adds timing: (a WorldTiming, the step rate and time scale) and catchUp: (what to do when a frame falls behind). Their defaults are what GameLoop did. In a Flame game, HasFixedStep runs an EngineLoop already: read engineLoop, and register your step work in installStepSystems.

Data files #

Your levels, effects, data plugins and recordings do not need a migration to keep working: 1.0 reads every version of each format, lifting an old file through its format's chain each time it opens it. What stays behind is the file on disk. A tool that does not carry the chain, a diff, or an older checkout of the editor still sees the 0.8 shape.

dart pub global run flutter3d_build:migrate --data --dry-run .   # list them
dart pub global run flutter3d_build:migrate --data --backup .

--data walks the project by the suffixes of every format below. Each JSON file below its format's version is written again in the shape 1.0 writes, with the format envelope (format, version, requires, generator) first. Every key it had is kept, including the ones 1.0 does not know. The report has a line per file, assets/levels/first.level.json: f3d.level v1 → v4, and a list of what it did not carry over. That covers a version newer than the tool, a document of another format under the suffix, and the formats it leaves as they are. --backup keeps each file as it was next to it, as <file>.v<N>.bak. flutter3d doctor, run in a project, lists the files that are behind.

A level is lifted by the level reader itself, because a level from before version 3 addressed a prefab override by name, and only the reader that has read the prefabs can turn that into an id path. The level's digest is computed from what the reader reads, so a run recorded against the old file still matches the new one.

Runs are left at their version on purpose. .f3drun is at version 5 in 1.0. A run with change stamps on its starting world (the step each component was last set at, which query().changed<T>() replays from) is written at 5. So is a run that carries externalInputs, state fed in from outside the simulation at a given step, which may say sourceTime, receivedTime, quality, tag and unit. Version 5 also reserves probes, what probes read while the run was recorded, and parent, the run a branch starts from. A build from before 5 plays neither of the first two correctly, so it must refuse them. Every other run is still written at the lowest version that describes it, and an older build keeps playing it. Lifting a version-1 run to 5 would only make those builds refuse it, so --data does not. Input tapes follow the same rule. A version-1 tape with button pairs where the game now has an axis is upgraded by the game's ActionSet.upgradeTape when it is replayed, since only the game knows its axes.

The formats owned by a package that needs the Flutter SDK (settings, action maps, saves, racing tracks and ghosts, strategy matches) cannot be loaded by a command-line tool. --data names those files and leaves them; the game reads every version of them as before.

Generated from the FormatSpecs migrate --data loads — 24 formats, and 10 it leaves to the package that reads them.

Format Files Package This build reads migrate --data
f3d.model .f3d flutter3d_core v1–v2 read as it is: its version is in its own header
f3d.fmat .fmat flutter3d_core v1 nothing to lift
f3d.materialLanguage .f3dmat flutter3d_core v1–v3 read as it is: its version is in its own header
f3d.splat .f3dsplat flutter3d_core v1 read as it is: its version is in its own header
f3d.frameCapture .capture.json flutter3d_core v1–v2 lifted to v2 through its chain
f3d.pipeline .pipeline.json flutter3d_core v1 nothing to lift
f3d.shaderBundle .f3dshaders flutter3d_hardware v1–v2 read as it is: its version is in its own header
f3d.trace .f3dtrace flutter3d_hardware v1–v2 read as it is: its version is in its own header
f3d.project .f3dproj flutter3d_model_core v1–v2 read as it is: its version is in its own header
f3d.level .level.json flutter3d_sim v1–v5 lifted to v5 by its reader
f3d.visibility .visibility.json flutter3d_sim v1 nothing to lift
f3d.lightmap .lightmap.bin flutter3d_sim v1 read as it is: its version is in its own header
f3d.run .f3drun flutter3d_sim v1–v5 kept below v5, at the lowest version that holds it
f3d.save .save.json flutter3d_sim v1 nothing to lift
f3d.share .share.json flutter3d_sim v1 nothing to lift
f3d.inputTape .tape.json flutter3d_sim v1–v2 kept below v2, at the lowest version that holds it
f3d.telemetry .upload.json flutter3d_sim v1 nothing to lift
f3d.effect .f3dfx flutter3d_particles v1–v2 lifted to v2 through its chain
f3d.plugin .f3dplugin flutter3d_plugin_runtime v1–v4 lifted to v4 through its chain
f3d.physicalMaterial .material.json flutter3d_matter v1–v2 lifted to v2 through its chain
f3d.voxelWorld .voxels.json flutter3d_voxel v1 nothing to lift
f3d.experiment .f3d.experiment flutter3d_education v1 nothing to lift
f3d.assets flutter3d_assets.yaml flutter3d_build v1 nothing to lift
f3d.convertReport .report.json flutter3d_build v1 nothing to lift
f3d.settings .settings.json flutter3d_game v1–v2 left as it is below v2: its package needs Flutter, and the game lifts it on every read
f3d.actions .actions.json flutter3d_game v1 nothing to lift: v1 is its only version
f3d.saveSlots — flutter3d_game v1 nothing to lift: v1 is its only version
f3d.cloudSaves — flutter3d_game v1 nothing to lift: v1 is its only version
f3d.telemetryConsent — flutter3d_game v1 nothing to lift: v1 is its only version
f3d.track .track.json flutter3d_game_racing v1 nothing to lift: v1 is its only version
f3d.ghost .ghost.json flutter3d_game_racing v1–v2 left as it is below v2: its package needs Flutter, and the game lifts it on every read
f3d.ghostTape .best.json flutter3d_game_kit v1 nothing to lift: v1 is its only version
f3d.match .match.f3drun flutter3d_game_strategy v1–v2 left as it is below v2: its package needs Flutter, and the game lifts it on every read
f3d.conformance .conformance.json flutter3d_conformance v1 nothing to lift: v1 is its only version

Every change, by package #

Each entry links here from its diagnostic and its TODO. How each one is carried out:

Generated from flutter3d_build/lib/migrations/ — 2095 entries from 0.8.5 to 1.0.0-rc.1, 308 by hand: 425 migrate, 308 by hand, 329 nothing to do, 528 dart fix, 380 gone internal, 125 migrate, with a TODO.

flame_flutter3d #

flame_flutter3d_audio #

flame_multiplayer #

flame_multiplayer_dashwire #

flutter3d #

flutter3d_addon_access #

flutter3d_addon_atmosphere #

flutter3d_addon_camera #

flutter3d_addon_capture #

flutter3d_addon_ghost #

flutter3d_addon_hud #

flutter3d_addon_light #

flutter3d_addon_motion #

flutter3d_addon_party #

flutter3d_addon_photo_mode #

flutter3d_addon_ragdoll #

flutter3d_addon_reactions #

flutter3d_addon_reflections #

flutter3d_addon_screens #

flutter3d_addon_seeded_levels #

flutter3d_addon_shading #

flutter3d_addon_soundtrack #

flutter3d_addon_style #

flutter3d_addon_touch #

flutter3d_addon_world #

flutter3d_addon_wrecks #

flutter3d_addons_standard #

flutter3d_app #

flutter3d_audio #

flutter3d_audio_core #

flutter3d_build #

flutter3d_conformance #

flutter3d_core #

flutter3d_cpu #

flutter3d_editor_core #

flutter3d_editor_mcp #

flutter3d_editor_widgets #

flutter3d_foundation #

flutter3d_game #

flutter3d_game_platformer #

flutter3d_game_racing #

flutter3d_game_shooter #

flutter3d_game_strategy #

flutter3d_hardware #

flutter3d_impeller #

flutter3d_lab #

flutter3d_lti #

flutter3d_mcp_kit #

flutter3d_mesh #

flutter3d_model_core #

flutter3d_model_mcp #

flutter3d_net #

flutter3d_net_webrtc #

flutter3d_particles #

flutter3d_physics #

flutter3d_plugin_api #

flutter3d_plugin_runtime #

flutter3d_samples #

flutter3d_shaders #

flutter3d_sim #

flutter3d_sim_mcp #

flutter3d_stereo #

flutter3d_testing #

flutter3d_webgl #

flutter3d_webgpu #

pad_input #

pointer_lock #