A starter template for 2D casual games built with Flutter and Flame. It is meant for teams who want to ship gameplay fast: routing between main menu, gameplay, pause, and game over is already wired, with shared UI primitives and hooks for audio, persistence, and optional BLoC state.
Use this repo as a GitHub template or clone it, rename the app, then customize the four screens and plug in your own GameplayDelegate.
- Flame
RouterComponentwith named routes:menu,play,pause,game_over. - Sample game (
ColorMatchDelegate) showing how gameplay lives behind a single delegate interface. - Reusable in-game UI: gradient backgrounds, styled buttons, pause dim overlay (
lib/game/ui/). - Services:
get_itDI,LocalStorage(high scores per game id),AudioController(BGM/SFX respecting settings). - Flutter shell:
GameWidget, Material app,flutter_blocproviders for settings and score (usable from Flutter overlays if you add them later). - Localization scaffold via
gen_l10n(lib/l10n/).
- Clone or use this repository as a template for a new project.
- Run
flutter pub get. - Run the app:
flutter run(or your IDE run action).
The game boots into MainMenuRoute (initialRoute: 'menu' in MyCasualGame).
| Path | Role |
|---|---|
lib/main.dart |
App entry, GameWidget.controlled(gameFactory: MyCasualGame.new), BLoC providers. |
lib/game/my_casual_game.dart |
FlameGame subclass, GameType enum, router table, lastScore / lastGameWon for game-over screen. |
lib/game/routes/ |
One component per screen: menu, gameplay, pause, game over. |
lib/game/ui/ |
MenuButton, BackgroundGradient, DimOverlay. |
lib/game/config/game_type_config.dart |
Per–game-type display name and menu gradient (menuGradientColors). |
lib/features/gameplay/ |
GameplayDelegate + concrete games (e.g. color_match/color_match_delegate.dart). |
lib/core/ |
DI (locator.dart), LocalStorage. |
lib/features/ |
Audio, settings, score cubits. |
flowchart LR
menu[menu]
play[play]
pause[pause]
over[game_over]
menu -->|Play| play
play -->|Pause button| pause
pause -->|Resume pop| play
pause -->|Restart| play
pause -->|Main menu| menu
play -->|onGameOver| over
over -->|Replay| play
over -->|Main menu| menu
- Pause is pushed on top of gameplay (
pushNamed('pause')), sopop()returns to the same run. - Game over uses
pushReplacementNamed('game_over')so the previous gameplay route is not kept (game_overroute usesmaintainState: false).
All of these are Flame components under lib/game/routes/. Adjust layout in onGameResize, copy styling from existing TextComponent / TextPaint blocks, or swap components.
- Background:
BackgroundGradientwithcolorsResolverreadingGameType.colorMatch.menuGradientColors(change when you add modes ingame_type_config.dart). - Title / high score:
onMountsets text fromGameType...displayNameandLocalStorage.getHighScore(gameName)wheregameNameis the enum’s.name(e.g.colorMatch). - Play:
PlayButtonextendsMenuButtonand callsgame.router.pushReplacementNamed('play').
- Dim layer:
DimOverlay(frombackground_gradient.dart) covers the full viewport; gameplay stays underneath but does not update while this route is active. - Buttons: Resume (
pop()), Restart (popthenpushReplacementNamed('play')), Main menu (popthenpushReplacementNamed('menu')).
- Reads
game.lastScoreandgame.lastGameWonset byGameplayRoutebefore navigating. - Persists high score via
LocalStorage.setHighScore(gameName, score)when the run beats the previous best. - Important: use the same
gameNamekey as the main menu (GameType.yourMode.name) so high scores stay consistent.
MenuButton(menu_button.dart): primary/secondary gradient styles; subclass and implementonTap()like the existing*Buttonclasses.MenuButtonColors: edit static colors to retheme all menu-style buttons at once.
Gameplay for a mode is a GameplayDelegate (lib/features/gameplay/gameplay_delegate.dart): a PositionComponent with:
onGameOver(bool isWin, int score)— call when the round ends; the route handler setsgame.lastScore/game.lastGameWonand opensgame_over.onScoreUpdated(int score)— optional HUD updates.reset()— called fromGameplayRoute.onMountwhen a new run starts.- Optional hooks:
onGameStart,onGamePause,onGameResume(override if you need them).
The sample ColorMatchDelegate shows a full implementation: layout in onGameResize, logic in update, and calling onGameOver / onScoreUpdated at the right times.
- Instantiate your delegate in
onLoad(replace or switch onGameTypeif you add multiple modes). - Keep the existing
onGameOvercallback pattern so audio and router behavior stay consistent. - Preload audio in
onLoad/FlameAudioas needed; start/stop BGM inonMount/onRemove. PauseButtonin this file opens the pause route; reposition it inonGameResizeif you change safe areas.
- Add enum values in
my_casual_game.dart. - Extend
displayNameandmenuGradientColorsingame_type_config.dartfor menu title and gradient. - Point main menu and game over at the active
GameTypeforgameName/ display strings (today they referenceGameType.colorMatchdirectly; with multiple modes you would pass the selected type from the game instance).
For a longer checklist (multiple games on a selection screen, assets, etc.), see the project skill Add New Game at .cursor/skills/add-new-game/SKILL.md and align it with your current router if the skill mentions routes your fork does not use.
AudioController is registered in get_it:
getIt<AudioController>().playBgm('background.mp3');
getIt<AudioController>().playSfx('ting.mp3');
getIt<AudioController>().stopBgm();Place files under assets/audio/ and list them in pubspec.yaml if you add new paths.
From Flutter widgets (not from Flame components unless you pass callbacks/context):
context.read<SettingsCubit>().toggleSound();
context.read<ScoreCubit>().addScore(10);Gameplay in this template typically updates score through the delegate callbacks or local state; use cubits when you surface score in Flutter overlays.
Add strings to lib/l10n/app_en.arb, then use AppLocalizations.of(context)! in Flutter. Flame TextComponent uses TextPaint / TextStyle directly; wire localized strings by passing them from Flutter into the game or by duplicating keys in a small shared map if you need l10n on Flame text.
- Rename
MyCasualGame,flame_boilerplate, and Android/iOS bundle identifiers to match your product. - Replace sample assets under
assets/with your art and audio. - Add a dedicated loading route if you need a heavy preload step before
menu; register it inMyCasualGame’sroutesmap and setinitialRouteaccordingly.
Use and modify this boilerplate per your team’s policy; refer to repository license if one is added.