Skip to main content
lass BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { Offset _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); ate SpriteRenderer _renderer; late SpriteRenderer _renderer; late SpriteRenderer _renderer; late SpriteRenderer _renderer; erride @override @override @override onMounted() { void onMounted() { void onMounted() { void onMounted() { nderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); omponent(_renderer); addComponent(_renderer); addComponent(_renderer); addComponent(_renderer); } } } @override @override @override void onUpdate(double dt) { void onUpdate(double dt) { void onUpdate(double dt) { void onUpdate(double dt) { final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); transform.position += _velocity * dt; transform.position += _velocity * dt; transform.position += _velocity * dt; transform.position += _velocity * dt; } } } } } } MyGameWorld extends GameState { class MyGameWorld extends GameState { class MyGameWorld extends GameState { class MyGameWorld extends GameState { ride @override @override @override le<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { GameWidget( yield GameWidget( yield GameWidget( yield GameWidget( onents: () => [ components: () => [ components: () => [ components: () => [ ectTransform(), ObjectTransform(), ObjectTransform(), ObjectTransform(), Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, BouncingBehavior(), BouncingBehavior(), BouncingBehavior(), BouncingBehavior(), ], ], ], ], ); ); ); ); } } } } } } in() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); al Pipeline Logic // Internal Pipeline Logic // Internal Pipeline Logic // Internal Pipeline Logic hBatch() { void _flushBatch() { void _flushBatch() { void _flushBatch() { fer = _gl.createBuffer(); final buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); } } } s BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { set _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); SpriteRenderer _renderer; late SpriteRenderer _renderer; late SpriteRenderer _renderer; late SpriteRenderer _renderer; ide @override @override @override Mounted() { void onMounted() { void onMounted() { void onMounted() { rer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); onent(_renderer); addComponent(_renderer); addComponent(_renderer); addComponent(_renderer); } } } } @override @override @override @override oid onUpdate(double dt) { void onUpdate(double dt) { void onUpdate(double dt) { void onUpdate(double dt) { final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); ransform.position += _velocity * dt; transform.position += _velocity * dt; transform.position += _velocity * dt; transform.position += _velocity * dt; } } } } } } ameWorld extends GameState { class MyGameWorld extends GameState { class MyGameWorld extends GameState { class MyGameWorld extends GameState { e @override @override @override Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { yield GameWidget( yield GameWidget( yield GameWidget( yield GameWidget( components: () => [ components: () => [ components: () => [ components: () => [ ObjectTransform(), ObjectTransform(), ObjectTransform(), ObjectTransform(), Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, BouncingBehavior(), BouncingBehavior(), BouncingBehavior(), BouncingBehavior(), ], ], ], ], ); ); ); } } } } } } ) => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); // Internal Pipeline Logic // Internal Pipeline Logic // Internal Pipeline Logic // Internal Pipeline Logic oid _flushBatch() { void _flushBatch() { void _flushBatch() { void _flushBatch() { final buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); l.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); } } } ouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); riteRenderer _renderer; late SpriteRenderer _renderer; late SpriteRenderer _renderer; late SpriteRenderer _renderer; @override @override @override void onMounted() { void onMounted() { void onMounted() { void onMounted() { _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); addComponent(_renderer); addComponent(_renderer); addComponent(_renderer); addComponent(_renderer); } } } erride @override @override @override onUpdate(double dt) { void onUpdate(double dt) { void onUpdate(double dt) { void onUpdate(double dt) { al transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); sform.position += _velocity * dt; transform.position += _velocity * dt; transform.position += _velocity * dt; transform.position += _velocity * dt; } } } } } } class MyGameWorld extends GameState { class MyGameWorld extends GameState { class MyGameWorld extends GameState { class MyGameWorld extends GameState { @override @override @override @override Iterable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { yield GameWidget( yield GameWidget( yield GameWidget( yield GameWidget( components: () => [ components: () => [ components: () => [ components: () => [ ObjectTransform(), ObjectTransform(), ObjectTransform(), ObjectTransform(), Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, BouncingBehavior(), BouncingBehavior(), BouncingBehavior(), BouncingBehavior(), ], ], ], ); ); ); } } } } } } oid main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); Internal Pipeline Logic // Internal Pipeline Logic // Internal Pipeline Logic // Internal Pipeline Logic _flushBatch() { void _flushBatch() { void _flushBatch() { void _flushBatch() { al buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); ufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); } } } cingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { class BouncingBehavior extends Behavior with Tickable, Collidable { elocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); Offset _velocity = const Offset(2.0, 1.5); late SpriteRenderer _renderer; late SpriteRenderer _renderer; late SpriteRenderer _renderer; late SpriteRenderer _renderer; @override @override @override @override oid onMounted() { void onMounted() { void onMounted() { void onMounted() { _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); _renderer = SpriteRenderer()..sprite = GameSprite(texture: Asset.enemy); ddComponent(_renderer); addComponent(_renderer); addComponent(_renderer); addComponent(_renderer); } } } ide @override @override @override Update(double dt) { void onUpdate(double dt) { void onUpdate(double dt) { void onUpdate(double dt) { transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); final transform = getComponent<ObjectTransform>(); rm.position += _velocity * dt; transform.position += _velocity * dt; transform.position += _velocity * dt; transform.position += _velocity * dt; } } } } } } } ss MyGameWorld extends GameState { class MyGameWorld extends GameState { class MyGameWorld extends GameState { class MyGameWorld extends GameState { verride @override @override @override rable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { Iterable<Widget> build(BuildContext context) sync* { eld GameWidget( yield GameWidget( yield GameWidget( yield GameWidget( omponents: () => [ components: () => [ components: () => [ components: () => [ ObjectTransform(), ObjectTransform(), ObjectTransform(), ObjectTransform(), amera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, Camera()..orthographicSize = 5.0, uncingBehavior(), BouncingBehavior(), BouncingBehavior(), BouncingBehavior(), ], ], ], ); ); ); ); } } } } } } } main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); void main() => runApp(const Game(child: MyGameWorld())); ernal Pipeline Logic // Internal Pipeline Logic // Internal Pipeline Logic // Internal Pipeline Logic lushBatch() { void _flushBatch() { void _flushBatch() { void _flushBatch() { buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); final buffer = _gl.createBuffer(); dBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); _gl.bindBuffer(GL.ARRAY_BUFFER, buffer); erData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); _gl.bufferData(GL.ARRAY_BUFFER, _vertexData, GL.DYNAMIC_DRAW); } } }
FRAME_TIME (ms) [ACTIVE]
0ms16.6ms (60fps)
CIRCLE_COLLIDER
TRIGGER_ZONE_#04
OnOverlap: notify()
EVENT_TRGR_#12
AREA_TRGR_#07
TEXTURE_ATLAS_01
FPS: 60.0
Delta: 16.6ms
VRAM: 124MB
Draws: 12
viewport: 0,0,1920,1080
VertexData_Buffer
SpriteBatcher_01
Texture_Slot#02
GPU_Resource_HNDL
Shader_Program_Cache
DrawCall_Bucket_#14

GOO2D

A Low Level Flutter 2D Game Engine

Scroll to explore
01

Stateful Scene Graph

Build your game tree using standard Flutter widgets and sync* generators. Manage complex entity lifecycles with the familiar StatefulGameWidget pattern.

02

Entity-Component-System (ECS)

A flexible Entity Component System. Decouple your game logic into reusable components that can be attached and queried at runtime with ease.

03

Action-Based Input

Decouple game logic from physical hardware. Bind multiple keys or touch controls to logical actions, with built-in support for composite vectors and deadzones.

Stateful Scene Graph

Build complex game worlds by stacking GameWidgets. Goo2D leverages standard Flutter build methods and sync* generators to manage hierarchical entity trees.


Iterable<Widget> build(BuildContext context) sync* {
yield GameWidget(
key: const GameTag('Player'),
components: () => [
ObjectTransform(),
SpriteRenderer()..sprite = playerSprite,
PlayerController(),
],
);
}
class PlayerController extends Behavior with Tickable {

void onUpdate(double dt) {
// Decouple logic from representation
final trans = getComponent<ObjectTransform>();
final input = getComponent<PlayerInput>();

trans.position += input.dir * 5.0 * dt;
}
}

ECS

A flexible Entity Component System. Decouple your game logic into reusable components that can be attached and queried at runtime with ease.

Asset Management

Type-safe asset management with Enums. Built-in caching and reactive loading progress out of the box, ensuring your game assets are always organized.

enum MySprites with AssetEnum, TextureAssetEnum {
ship, boss, explosion;

AssetSource get source =>
AssetSource.local("assets/sprites/$name.png");
}

// Reactive loading with streams
await for (final p in GameAsset.loadAll(MySprites.values)) {
updateProgress(p.assetLoaded / p.assetCount);
}
final sheet = SpriteSheet.grid(
texture: MySprites.explosion,
columns: 8, rows: 8,
ppu: 64.0,
);

// Instant frame access via coordinates
renderer.sprite = sheet[(0, 4)];

Sprite Sheets

Efficiently handle complex atlases and grids. Support for PPU (Pixels Per Unit) scaling and flexible pivot points for accurate rendering.

Collisions

Manage physical interactions without the widget tree overhead. Robust callbacks for hits, triggers, and screen-boundary events.

class Enemy extends Behavior with Collidable {

void onCollision(CollisionEvent event) {
// Precise filtering with GameTags
if (event.other.gameObject.tag == const GameTag('Player')) {
print("Hit player!");
gameObject.destroy();
}
}
}
moveAction = createInputAction(
name: 'move',
type: InputActionType.value,
bindings: [
InputBinding.composite(
up: game.input.keyboard.keyW,
down: game.input.keyboard.keyS,
left: game.input.keyboard.keyA,
right: game.input.keyboard.keyD,
),
],
);

// Read 2D vector anywhere
final dir = moveAction.readValue<Offset>();

Input System

Modern, action-based input system. Bind multiple physical controls to a single logical action for cross-platform support.