Flame Gallery

pub package CI License: MIT

Drop-in, brandable 2D mini games for any Flutter app, built on Flame. One call opens a full screen game; you get the score back.

final result = await FlameGallery.show(context, game: const FruitSlice());

Play it in your browser · Website · GitHub

Fruit Slice Ruins Runner Sweet Swap Note Rush
Swipe to slice, avoid bombs 3 lanes: swipe, jump, slide Match three or more Tap notes on the beat

Why

  • One dependency, one call. FlameGallery.show pushes a full screen route with start, pause and game over menus, and completes with a GameResult.
  • Brandable without forking. Colors (ColorScheme), font, logo, every text, sprites and rules are config. No colors given → the game follows your app theme.
  • Campaign ready. Timers, lives, move limits, target scores, seeded boards ("daily challenge") and a result with stats you can turn into coupons or points.
  • Light on your app. The libraries depend only on flutter and flame. No state management or codegen packages are forced on you.
  • Testable. Stable Semantics identifiers on every menu button and HUD value (GalleryKeys), so Maestro / Patrol / integration tests can drive the games.

Install

flutter pub add flame_gallery

Each game is also its own package (flame_gallery_slice, flame_gallery_runner, flame_gallery_match, flame_gallery_rhythm) if you only want one.

Usage

import 'package:flame_gallery/flame_gallery.dart';

final result = await FlameGallery.show(
  context,
  game: FruitSlice(
    config: const SliceConfig(
      timeLimit: Duration(seconds: 30),
      bomb: null, // no bombs in this campaign
    ),
    theme: GameTheme(
      colorScheme: ColorScheme.fromSeed(
        seedColor: const Color(0xFF00897B),
        brightness: Brightness.dark,
      ),
      logo: const AssetImage('assets/brand_logo.png'),
    ),
    strings: FruitSlice.stringsTr.copyWith(
      title: 'Summer Campaign',
      instructions: 'Slice as many fruits as you can in 30 seconds!',
    ),
  ),
  onEvent: (event) => analytics.log('$event'),
);

if (result case GameResult(isCompleted: true, :final score) when score >= 50) {
  grantCoupon();
}

The games

Game Class Config highlights
Fruit Slice FruitSlice timeLimit, lives, items (your own sprites, points, juice color), bomb, bombChance, difficulty, background
Ruins Runner EndlessRunner timeLimit, lives, coinValue, difficulty, sprites, background
Sweet Swap SweetSwap rows, columns, pieces, moveLimit, timeLimit, targetScore, seed
Note Rush NoteRush song (beat map), difficulty, missLimit, plus a pluggable RhythmAudio so you play music with your own audio stack

Every game ships English and Turkish texts (stringsEn, stringsTr); override any of them with copyWith.

Configs are immutable. copyWith takes a ValueGetter for nullable fields, so they can be cleared as well as changed:

const base = SliceConfig(lives: 3);
final campaign = base.copyWith(timeLimit: () => const Duration(seconds: 30), bomb: () => null);

Results and events

GameResult(
  gameId: 'fruit_slice',
  score: 42,
  reason: GameEndReason.completed, // or quit
  playTime: Duration(seconds: 30),
  stats: {'sliced': 40, 'maxCombo': 4, 'endedBy': 'timeUp'},
);

Read stats through the const keys instead of string literals:

final sliced = result.stats[SliceStatKeys.sliced];
final endedBy = result.stats[GameStatKeys.endedBy]; // 'timeUp', 'bomb', 'quit'...

Each game exports its own keys: SliceStatKeys, RunnerStatKeys, MatchStatKeys, RhythmStatKeys.

onEvent receives GameStarted, GameScoreChanged, GamePaused, GameResumed and GameFinished. GameResult.toJson() gives a flat map for analytics or a platform channel.

Your own assets

Use GameAsset('assets/my_fruit.png') for files in your app bundle:

FruitSlice(
  config: SliceConfig(
    items: const [
      SliceItem(sprite: GameAsset('assets/products/can.png'), juiceColor: Color(0xFFE53935), points: 2),
      SliceItem.orange,
    ],
  ),
);

Branding: background, dialogs, HUD

FlameGallery.show(
  context,
  game: FruitSlice(
    theme: GameTheme(
      // Color, gradient or image behind every game's playfield.
      background: const GameBackground.gradient(
        LinearGradient(colors: [Color(0xFF004D40), Color(0xFF00897B)]),
      ),
      // Full control over buttons, cards and text in the menus.
      themeData: myAppTheme,
    ),
  ),
  // Replace any built-in overlay. `ui` gives strings, score and actions.
  overlays: GameOverlayBuilders(
    start: (context, ui) => MyBrandDialog(title: ui.strings.title, onStart: ui.start),
    gameOver: (context, ui) => MyResultSheet(
      score: ui.result()?.score ?? 0,
      onAgain: ui.start,
      onClose: ui.close,
    ),
  ),
);

Animated sprites

Anything that was a single image can be a horizontal sprite strip:

RunnerSprites(
  runnerAnimation: GameAnimation(
    GameAsset('assets/hero_run.png'),
    frames: 6,
    stepTime: 0.08,
  ),
  coinAnimation: GameAnimation(GameAsset('assets/coin_spin.png'), frames: 8),
)

Sound effects

The library plays nothing itself. Pass a GameSounds and map cues to your audio package:

FlameGallery.show(
  context,
  game: EndlessRunner(),
  sounds: CallbackSounds((cue) => switch (cue) {
    GameSoundCue.coin => player.play('coin.mp3'),
    GameSoundCue.hit => player.play('crash.mp3'),
    _ => null,
  }),
);

Cues: start, scoreUp, penalty, gameOver (every game); slice, bomb (Fruit Slice); jump, coin, hit (Ruins Runner); swap, match (Sweet Swap); noteHit, noteMiss (Note Rush).

Difficulty

Presets stay (SliceDifficulty.hard), and you can tune any number:

SliceConfig(difficulty: SliceDifficulty.normal.copyWith(maxWaveSize: 6, speedFactor: 1.3))
RunnerConfig(difficulty: RunnerDifficulty.hard.copyWith(maxSpeed: 1.8))
RhythmConfig(difficulty: RhythmDifficulty(approachSeconds: 1.1))

System UI

Games run in immersive mode (system bars hidden) and restore SystemUiMode.edgeToEdge on close. Pass immersive: false to FlameGallery.show if your app manages system UI itself.

Testing

Menu buttons and HUD values carry Semantics(identifier:) values from GalleryKeys (gallery_start_button, gallery_pause_button, gallery_final_score, gallery_hud_score, ...) and per game keys (RunnerKeys, MatchKeys, RhythmKeys). The repository has a Maestro suite that plays every game on iOS and Android: apps/example/maestro.

Build your own game

Extend GalleryFlameGame (gameplay) and GalleryGame (public description) from flame_gallery_core; the host page, menus, pause handling and results come for free.

License

MIT

Libraries

Drop-in Flame mini games for any Flutter app.