good¶
GOOD — Game Overdrive On Dart — is an ECS game engine for Flutter.
Your game is data in columns and systems that walk them. The simulation runs on its own isolate at a fixed timestep, keeps every component in shared native memory, and allocates nothing on the per-frame path, so neither a Flutter rebuild nor the garbage collector can stall it.
good is the kernel. The ecosystem around it includes goo2d for 2D games and
goo3d for 3D games, both built on it.
-
good— the kernelECS, scenes, the fixed-tick loop, the memory pool, input, assets, coroutines and the cross-isolate plumbing. The base both engines are built on.
-
goo2d— the 2D engineTransforms, cameras, colliders, sprite rendering. Re-exports the kernel, so a 2D game has one dependency and one import.
-
goo3d— the 3D engineTransforms, cameras, meshes, materials and lighting. Re-exports the kernel, so a 3D game has one dependency and one import.
class Player extends EntityStruct with Transform2D, WorldTransform2D, Renderable2D {
late final Sprite sprite;
@override
void describeSprites(SpriteDescriptor descriptor) {
super.describeSprites(descriptor);
sprite = descriptor.has(width: 64, height: 64, color: 0xFF4FC3F7);
}
}
Start here¶
-
Flutter SDK, per-platform toolchains, and the
goodcommand-line tool. -
good create, and what a good project is made of. -
A sprite you can drive with the keyboard, built from an empty project.
-
Compaction, packing, encryption, and
good build <platform>.
The packages¶
A 2D game needs one dependency. Everything else is opt-in, because each carries weight not every game wants.
| Package | What it is |
|---|---|
goo2d |
The 2D engine. Re-exports the kernel — one dependency, one import |
good |
The kernel. You do not normally depend on it directly |
good_cli |
The good build tool: scaffolding, codegen, asset pipeline, packaging |
goo2d_physics_box2d |
Box2D v3 physics: RigidBody2D, colliders, joints, effectors |
goo2d_ffi_box2d |
Vendored Box2D plus a primitives-only C shim. Not used directly |
good_net |
Declared network messages, sessions, and the transport contract |
good_net_p2p |
Serverless P2P backend — nothing to host |
The packages → explain how they divide up, and Implementation status tracks what is landed in the repository right now.
What the split buys¶
The kernel holds the ECS, the scheduler, scenes, hierarchy, input, the asset
registry, the isolate bootstrap, networking, and the CLI. That is what lets
goo3d sit beside goo2d without duplicating any of it — and what lets a
headless dedicated server depend on the kernel and physics without pulling in a
renderer at all.
The practical consequence for you: most of what you learn is not 2D-specific. Every guide page is marked with which layer it belongs to.
The shape of a good game¶
Three ideas carry most of the engine, and they are worth meeting before the tutorial.
Two isolates, two copies of your Game. Declarations live on the Flutter
isolate; the simulation runs on its own. The same Game object is deep-copied
across the boundary, so both sides agree on every id without negotiating.
See Architecture.
Everything is declared once, and hands back a typed handle. There are no
string keys anywhere in the API. A describe* pass returns an object you keep
in a late final field, and the analyzer catches a misspelling that a map
lookup would not.
sprite = descriptor.has(width: 64, height: 64); // keep the handle
sprite.color[entity] = 0xFFFF0000; // use it per entity
Components are storage, not objects. An EntityStruct subclass is a
description of a row layout, shared by every entity of that kind. A field is
a DataPointer you index by Entity, not a value on an instance. See
Entities and components.
The rules that shaped it¶
good is written against an explicit set of hot-path constraints — no heap
allocation per frame, no closures in a tick, no Canvas.save/restore. They
are worth reading before writing systems, because the engine's API shapes are
consequences of them: Hot-path rules.