flutter3d
Showcase Changelog 63 packages API reference

Your first project

Quickstart puts a lit mesh on screen inside this repository. This makes a project that is yours: its own directory, its own level, its own models, and an application that walks around in them.

  • A project written from a template, and what each of its files is
  • Getting it to run, including the one step nobody does for you
  • Editing its level, and what the editor refuses to save over
  • Changing how the body feels, and where to test that without a window
  • What to add next, depending on what you are making

Two things before starting. The editor is run from the checkout, so the workspace has to be resolved and its shader bundle built, both from Quickstart. That is about running the editor, not about the project it writes. The project it writes depends on hosted versions from pub.dev and never on a path into this checkout, and the compiled bundle rides inside flutter3d_impeller, so a scaffolded project has nothing to build. And this is macOS. The browser is the other supported target, but the editor runs on the desktop.

Write the project #

The level editor scaffolds it. Point it at a level path that does not exist yet and it offers a template instead of an error:

cd apps/flutter3d_editor
flutter run -d macos --dart-define=level=/Users/me/games/deep_mine/assets/levels/first.json

The editor pointed at a path that does not exist yet, offering to create a project from a template

There are four templates, one per genre package: platformer, shooter, racing and strategy. The screenshot above predates the last two and shows only the first pair.

Picking one writes the whole project at /Users/me/games/deep_mine and opens the new level in the editor. Note the shape of the path: the editor reads …/<project>/assets/levels/<name>.json and takes the part before assets/ as the project root.

The template is data, not a code path in the editor. It is copied into the new project and read back from there by the same parser that reads any level's vocabulary, which is how an editor that contains no genre word can still start a genre game. The genre packages are in the editor's dev_dependencies, so the words are checked against the packages that own them and never linked into the program.

Run it #

cd /Users/me/games/deep_mine
flutter create --platforms=macos .   # adds the platform folders, leaves the rest
flutter pub get
flutter run -d macos

flutter create adds macos/ beside what is already there and leaves pubspec.yaml, lib/ and test/ alone.

Then add two keys to macos/Runner/Info.plist: FLTEnableFlutterGPU and FLTEnableImpeller, both <true/>, inside the top-level <dict>. Flutter GPU is enabled per application rather than per channel, and Impeller is not yet the default renderer on macOS. A scaffolded project has no macos/ at all until flutter create makes one, so this is the step nobody can do for you. The README the scaffold writes says the same thing beside the same commands.

Skipping them does not fail, which is why this gets a box of its own. The game opens and draws, through the Dart software rasteriser, because that is what flutter3d_app falls back to when Impeller will not start. The only sign is a console line beginning flutter3d_app: Impeller would not start and a frame rate that is the fallback's rather than the engine's. If you see that line, these keys are why. The exact wording of the failures that do stop the app is in Pitfalls.

One that bites once. The pubspec names hosted versions from pub.dev. pubspecFor in the editor's scaffold_templates.dart writes them, and it pointed into the checkout by path: only while the packages were unpublished, so a project made in that window travels only with its lines fixed. Use a Flutter at least as new as the engine's: an older one writes a macOS project targeting an older system than the packages support, and the build then fails with a deployment-target error that mentions none of this.

Read what you got #

lib/main.dart The application. One Flutter3dView, which opens the device, makes the renderer and runs the loop, and a small genre of the seed's own that walks a body through the level
assets/levels/first.json The level: brushes, materials, lights, entities. Data, not code
assets/editor.json What this game's words look like to the editor. Not in the asset list, so no player downloads it
assets/models/*.glb One model per kind of thing the level names
test/widget_test.dart It loads the level through a software device, with no window

The imports at the top of main.dart say where the halves are: flutter3d_game brings the view, the input and the run, with flutter3d and what of flutter3d_app a game opens a window with, and flutter3d_sim brings the loop, the genre's shape and the event bus. Assembling an application is that seam in detail.

main.dart is a seed, not a game. It reads the level, builds it, and installs a genre of its own that walks a body through it, looking and jumping, steered by an ActionMap and saying on the event bus when it lands. It leaves out, on purpose, everything a genre is: weapons, monsters, coins, doors that open, a score, a menu and saving. Those live in flutter3d_game_shooter and flutter3d_game_platformer, and adding one is the last step below.

Change the level #

Run the editor again with the same command. The path exists now, so it opens the level instead of offering to create one. W A S D walks, Q/E go down and up, drag looks, scroll moves forward.

Click a palette row on the left, then click a surface: that puts one down where you clicked. Click a brush to select it; arrows move it on the grid, R/F raise and lower it, 1 2 3 pick an axis and − = resize along it. G cycles the grid between 0.25 m, 1 m and off. ⌘Z steps back.

⌘S saves.

⌘S refuses to save over a document whose JSON carries a generatedBy key, and every level the games ship carries one: they are written by Python generators, so a hand-edit survives only until somebody reruns the generator. ⇧⌘S writes a copy and takes ownership of that. A scaffolded level has no such key, so it is yours from the first save.

Change how it feels #

This is the part a level cannot express. The body in the template is a CharacterController built with its defaults:

CharacterController(world: world, position: at)

Every number that decides how it feels is in the tuning beside it, in metres and seconds:

CharacterController(
  world: world,
  position: at,
  tuning: const MovementSettings(
    jumpSpeed: 9.5,     // 8.0 by default: meters per second, straight up
    gravity: 22.0,      // 24.0: lower is floatier
    coyoteTime: 0.15,   // 0.1: how long after a ledge a jump still counts
  ),
)

The rest of them, and the ones each genre adds on top, are in the knobs.

tuning is not final, which is deliberate and is what ice, mud, a low-gravity room and a slow-effect are all made of: assign a different constant while the game is running and the body changes on the next step.

What makes those safe to play with is that none of it needs a window. flutter3d_game, flutter3d_physics and the genre packages import no renderer at all, so a jump height, a monster's patience or a lap counter is reachable from a plain test() that runs in milliseconds. Put your second change there: a test says what happened, where a run shows you a frame of it and leaves you to judge.

Add a genre #

The seed walks; a game does something. Each of the three games in this checkout keeps that wiring in one file, lib/src/staging.dart: what an entity type spawns, what a weapon is, what a coin does when you touch it. The one to copy from is apps/flutter3d_demo_dungeon/lib/src/staging.dart for a shooter, apps/flutter3d_demo_platformer/lib/src/staging.dart for a platformer, and apps/flutter3d_demo_racing/lib/src/staging.dart for a racer.

The genre pages say what each package brings: what a shooter adds, what a platformer adds, what a racing game adds.

Where to go next #

Pick by what you want to do.

I want to… Read
Know what a word here means Glossary
Change how the game feels The knobs
Understand what draws a frame The frame
Put my own models in Assets & animation
Author levels rather than write code The level editor, then Simulation layer for the format
Build a game from an empty directory instead Tutorial: build an FPS · a platformer · a racer
Find out why something looks wrong Pitfalls
Ship it in a browser The playable demos