[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-sviluppare-giochi-2d-in-flutter-con-flame-dal-game-loop-alle-collisioni":3,"comments-article-sviluppare-giochi-2d-in-flutter-con-flame-dal-game-loop-alle-collisioni":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},144,"Sviluppare giochi 2D in Flutter con Flame: dal game loop alle collisioni","sviluppare-giochi-2d-in-flutter-con-flame-dal-game-loop-alle-collisioni","Flame è il motore di gioco 2D costruito su Flutter: game loop, Component System, sprite, collisioni, input e HUD. Guida pratica per costruire il tuo primo gioco e integrarlo con i widget Flutter.","Flutter non serve solo per app gestionali ed e-commerce: il suo motore di rendering è perfettamente in grado di disegnare centinaia di sprite a 60 fps. **Flame** è il motore di gioco 2D open source costruito sopra Flutter che aggiunge ciò che manca: un game loop, un sistema a componenti, gestione di sprite e animazioni, rilevamento delle collisioni, camera, effetti e audio.\n\nIn questa guida costruiamo le fondamenta di uno shooter verticale, vedendo i concetti chiave che servono in qualsiasi progetto Flame.\n\n## Perché Flame e quando (non) usarlo\n\nFlame è la scelta giusta per:\n\n- giochi 2D casual, puzzle, platform, endless runner;\n- mini-giochi integrati in un'app esistente (gamification, onboarding animato);\n- simulazioni e visualizzazioni interattive con molti elementi in movimento.\n\nNon è la scelta giusta per il 3D (meglio Unity\u002FGodot o `flutter_scene` sperimentale) né per UI classiche: per quelle i widget Flutter restano imbattibili. La buona notizia è che le due cose convivono: un `GameWidget` è un widget come gli altri e può stare in uno `Stack` con menu, pulsanti e dialog costruiti in Flutter.\n\n## Setup\n\n```yaml\ndependencies:\n  flutter:\n    sdk: flutter\n  flame: ^1.23.0\n  flame_audio: ^2.10.0\n\nflutter:\n  assets:\n    - assets\u002Fimages\u002F\n    - assets\u002Faudio\u002F\n```\n\nFlame cerca di default le immagini in `assets\u002Fimages\u002F` e gli audio in `assets\u002Faudio\u002F`: rispettare questa convenzione evita configurazioni extra.\n\nIl punto di ingresso è il `GameWidget`:\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Flame.device.fullScreen();\n  await Flame.device.setPortrait();\n  runApp(GameWidget(game: SpaceGame()));\n}\n```\n\n## Il Flame Component System\n\nIl cuore di Flame è il **FCS (Flame Component System)**: tutto ciò che vive nel gioco è un `Component` organizzato in un albero, esattamente come i widget. Ogni componente ha due metodi fondamentali chiamati dal game loop:\n\n- `update(double dt)`: aggiorna lo stato logico, dove `dt` è il tempo in secondi trascorso dall'ultimo frame;\n- `render(Canvas canvas)`: disegna sul canvas (spesso non serve sovrascriverlo, lo fanno già i componenti pronti).\n\nUsare sempre `dt` per i movimenti è la regola d'oro: `position.y += 200 * dt` significa \"200 pixel al secondo\" indipendentemente dal frame rate, mentre `position.y += 3` produce velocità diverse su dispositivi diversi.\n\nLa classe base del gioco è `FlameGame`, che espone due oggetti chiave: `world` (il mondo di gioco, soggetto a camera) e `camera` (un `CameraComponent` con viewport e viewfinder).\n\n```dart\nclass SpaceGame extends FlameGame\n    with HasCollisionDetection, HasKeyboardHandlerComponents {\n  late final Player player;\n  int score = 0;\n  final _rng = Random();\n\n  @override\n  Future\u003Cvoid> onLoad() async {\n    await images.loadAll(['player.png', 'enemy.png']);\n\n    \u002F\u002F Coordinate del mondo allineate ai pixel dello schermo,\n    \u002F\u002F origine in alto a sinistra.\n    camera.viewfinder.anchor = Anchor.topLeft;\n\n    player = Player()..position = Vector2(size.x \u002F 2, size.y - 100);\n    world.add(player);\n\n    world.add(\n      TimerComponent(period: 0.8, repeat: true, onTick: _spawnEnemy),\n    );\n\n    \u002F\u002F L'HUD va nel viewport: non si muove con la camera.\n    camera.viewport.add(ScoreText());\n  }\n\n  void _spawnEnemy() {\n    final x = 24 + _rng.nextDouble() * (size.x - 48);\n    world.add(Enemy()..position = Vector2(x, -40));\n  }\n\n  void gameOver() {\n    pauseEngine();\n    overlays.add('gameOver');\n  }\n}\n```\n\n`onLoad()` è asincrono ed è il posto giusto per caricare asset e costruire la scena. Gli altri hook del ciclo di vita sono `onMount`, `onRemove` e `onGameResize`.\n\n## Sprite, input e movimento\n\nUn componente che disegna un'immagine è un `SpriteComponent`. Il mixin `HasGameReference\u003CT>` dà accesso tipizzato all'istanza di gioco, `KeyboardHandler` riceve gli eventi da tastiera (utile su web e desktop), `DragCallbacks` quelli di trascinamento su mobile.\n\n```dart\nclass Player extends SpriteComponent\n    with HasGameReference\u003CSpaceGame>, CollisionCallbacks, KeyboardHandler, DragCallbacks {\n  Player() : super(size: Vector2.all(56), anchor: Anchor.center);\n\n  static const double speed = 320;\n  int _direction = 0;\n\n  @override\n  Future\u003Cvoid> onLoad() async {\n    sprite = Sprite(game.images.fromCache('player.png'));\n    add(RectangleHitbox(collisionType: CollisionType.active));\n  }\n\n  @override\n  void update(double dt) {\n    position.x += _direction * speed * dt;\n    position.x = position.x.clamp(size.x \u002F 2, game.size.x - size.x \u002F 2);\n  }\n\n  @override\n  bool onKeyEvent(KeyEvent event, Set\u003CLogicalKeyboardKey> keysPressed) {\n    _direction = 0;\n    if (keysPressed.contains(LogicalKeyboardKey.arrowLeft)) _direction -= 1;\n    if (keysPressed.contains(LogicalKeyboardKey.arrowRight)) _direction += 1;\n    return false; \u002F\u002F lascia propagare l'evento\n  }\n\n  @override\n  void onDragUpdate(DragUpdateEvent event) {\n    position.x += event.localDelta.x;\n  }\n}\n```\n\nPer il tocco su un singolo componente esiste il mixin `TapCallbacks` con `onTapDown`\u002F`onTapUp`: Flame calcola automaticamente quale componente è stato colpito in base a `position`, `size` e `anchor`.\n\n## Collisioni\n\nIl sistema di collisioni si attiva con il mixin `HasCollisionDetection` sul gioco e con una **hitbox** su ogni componente coinvolto (`RectangleHitbox`, `CircleHitbox`, `PolygonHitbox`). I callback arrivano tramite `CollisionCallbacks`.\n\n```dart\nclass Enemy extends SpriteComponent\n    with HasGameReference\u003CSpaceGame>, CollisionCallbacks {\n  Enemy() : super(size: Vector2.all(48), anchor: Anchor.center);\n\n  static const double speed = 140;\n\n  @override\n  Future\u003Cvoid> onLoad() async {\n    sprite = Sprite(game.images.fromCache('enemy.png'));\n    add(CircleHitbox());\n  }\n\n  @override\n  void update(double dt) {\n    position.y += speed * dt;\n    if (position.y - size.y > game.size.y) {\n      removeFromParent();\n      game.score += 1;\n    }\n  }\n\n  @override\n  void onCollisionStart(Set\u003CVector2> points, PositionComponent other) {\n    super.onCollisionStart(points, other);\n    if (other is Player) {\n      removeFromParent();\n      game.gameOver();\n    }\n  }\n}\n```\n\nConsigli pratici:\n\n- usa `CollisionType.passive` per gli oggetti che non devono essere testati fra loro (es. decine di nemici): dimezza i controlli;\n- rimuovi sempre dallo schermo i componenti usciti dai bordi con `removeFromParent()`, altrimenti l'albero cresce all'infinito;\n- attiva `debugMode = true` sul gioco per vedere hitbox e coordinate durante lo sviluppo.\n\n## Animazioni ed effetti\n\nPer le animazioni a sprite sheet si usa `SpriteAnimationComponent`:\n\n```dart\nfinal explosion = await game.loadSpriteAnimation(\n  'explosion.png',\n  SpriteAnimationData.sequenced(\n    amount: 8,\n    stepTime: 0.05,\n    textureSize: Vector2.all(64),\n    loop: false,\n  ),\n);\n\ngame.world.add(\n  SpriteAnimationComponent(\n    animation: explosion,\n    size: Vector2.all(64),\n    anchor: Anchor.center,\n    position: position.clone(),\n    removeOnFinish: true,\n  ),\n);\n```\n\nPer movimenti, scalature e dissolvenze c'è il sistema di **effects**, analogo alle animazioni implicite di Flutter:\n\n```dart\nplayer.add(\n  ScaleEffect.to(\n    Vector2.all(1.2),\n    EffectController(duration: 0.15, alternate: true, curve: Curves.easeOut),\n  ),\n);\n\nenemy.add(\n  MoveEffect.by(\n    Vector2(0, 40),\n    EffectController(duration: 0.4, infinite: true, alternate: true),\n  ),\n);\n```\n\nGli effetti si rimuovono da soli al termine e si possono comporre in sequenza con `SequenceEffect`.\n\n## Camera, viewport e HUD\n\nIl `CameraComponent` separa il **mondo** dalla **vista**. Per seguire il giocatore in un platform:\n\n```dart\ncamera.follow(player);\ncamera.setBounds(Rectangle.fromLTWH(0, 0, levelWidth, levelHeight));\n```\n\nPer avere la stessa composizione su tutti i dispositivi conviene una risoluzione fissa, con Flame che aggiunge le bande o scala automaticamente:\n\n```dart\ncamera = CameraComponent.withFixedResolution(\n  world: world,\n  width: 480,\n  height: 960,\n);\n```\n\nTutto ciò che aggiungi a `camera.viewport` (punteggio, vite, pulsanti) resta ancorato allo schermo:\n\n```dart\nclass ScoreText extends TextComponent with HasGameReference\u003CSpaceGame> {\n  ScoreText()\n      : super(\n          position: Vector2(16, 32),\n          textRenderer: TextPaint(\n            style: const TextStyle(fontSize: 24, color: Colors.white),\n          ),\n        );\n\n  @override\n  void update(double dt) => text = 'Punti: ${game.score}';\n}\n```\n\n## Integrare i widget Flutter con gli overlay\n\nMenu, pause screen e dialog si scrivono meglio con i widget. Flame offre il sistema degli **overlay**: widget Flutter disegnati sopra il gioco e attivabili da codice.\n\n```dart\nGameWidget\u003CSpaceGame>(\n  game: SpaceGame(),\n  loadingBuilder: (_) => const Center(child: CircularProgressIndicator()),\n  overlayBuilderMap: {\n    'gameOver': (context, game) => Center(\n          child: Card(\n            child: Padding(\n              padding: const EdgeInsets.all(24),\n              child: Column(\n                mainAxisSize: MainAxisSize.min,\n                children: [\n                  Text('Game over — ${game.score} punti'),\n                  const SizedBox(height: 16),\n                  FilledButton(\n                    onPressed: () {\n                      game.overlays.remove('gameOver');\n                      game.resumeEngine();\n                    },\n                    child: const Text('Riprova'),\n                  ),\n                ],\n              ),\n            ),\n          ),\n        ),\n  },\n)\n```\n\n`pauseEngine()` e `resumeEngine()` fermano il game loop senza distruggere lo stato: perfetti per il menu di pausa o quando l'app va in background.\n\n## Audio\n\nCon `flame_audio` (basato su `audioplayers`) il suono è immediato:\n\n```dart\n\u002F\u002F precarica in onLoad per evitare latenza al primo colpo\nawait FlameAudio.audioCache.loadAll(['shoot.wav', 'explosion.wav']);\n\nFlameAudio.play('shoot.wav', volume: 0.6);\nFlameAudio.bgm.initialize();\nFlameAudio.bgm.play('theme.mp3', volume: 0.3);\n```\n\nRicorda di chiamare `FlameAudio.bgm.stop()` quando l'app passa in background per non consumare batteria.\n\n## Performance: le regole che contano davvero\n\n- **Precarica gli asset** in `onLoad` con `images.loadAll`: caricare un'immagine durante il gameplay provoca uno scatto.\n- **Niente allocazioni nel loop**: evita `Vector2(...)`, `Paint()` o closure create a ogni `update`. Riusa istanze come campi del componente.\n- **Object pooling** per proiettili e particelle: invece di creare e distruggere centinaia di componenti, riattiva quelli già esistenti.\n- **Usa `priority`** per controllare l'ordine di disegno invece di riordinare manualmente la lista dei figli.\n- **Disattiva `debugMode`** nelle build di release e profila con DevTools come faresti con una normale app Flutter.\n- Su Android\u002FiOS, Impeller riduce lo shader jank: verifica il frame rate in modalità profile, mai in debug.\n\n## L'ecosistema\n\nAttorno al core esistono pacchetti ufficiali che coprono gli scenari più comuni:\n\n- `flame_tiled`: carica mappe create con l'editor Tiled;\n- `flame_forge2d`: fisica rigida basata su Box2D;\n- `flame_audio`: audio ed effetti sonori;\n- `flame_bloc` \u002F `flame_riverpod`: ponte verso i tuoi state manager preferiti;\n- `flame_test`: utility come `testWithGame` per testare la logica di gioco senza UI.\n\n```dart\ntestWithGame\u003CSpaceGame>('il nemico esce dallo schermo e viene rimosso',\n    SpaceGame.new, (game) async {\n  final enemy = Enemy()..position = Vector2(100, 0);\n  await game.world.add(enemy);\n  await game.ready();\n\n  game.update(10); \u002F\u002F simula 10 secondi\n  expect(enemy.isMounted, isFalse);\n});\n```\n\n## Conclusione\n\nFlame non reinventa Flutter: lo estende con le astrazioni che servono per il tempo reale. Il modello mentale è familiare — un albero di componenti con un ciclo di vita — e la possibilità di mescolare widget e canvas rende semplice costruire menu, impostazioni e salvataggi con gli stessi strumenti che già usi. Parti da un prototipo piccolo (un componente, una collisione, un HUD), tieni la logica fuori dal `render` e usa sempre `dt`: il resto è iterazione e asset grafici.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F4f4e0ff8-11bc-4845-a269-a92fb9ccf3ae.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1623743993875-03d6a5c7c709?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTM0MTkzMTN8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Onur Binay","https:\u002F\u002Funsplash.com\u002F@onurbinay","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fperson-holding-black-samsung-android-smartphone-2M6qt9fzRBw",null,"published","2026-10-31T04:01:54+00:00","Giochi 2D in Flutter con Flame: guida pratica","Come creare giochi 2D in Flutter con Flame: game loop, component system, sprite, collisioni, camera, HUD e overlay widget. Guida con esempi di codice.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1793444671111]