Aplicacions amb Flutter, Dart i Flame

Tutorial Flutter Flame Projectes   Recursos CITCEA
Exemples Dart Dades pràctiques     Inici

Afegim un efecte de partícules

L'esperit del joc és que la bola va destruïnt els totxos; per tant, és poc realista que els totxos símplement desapareguin. A Flame hi ha la possibilitat de fer aparèixer un efecte d'emissió de partícules al voltant d'un punt. L'aplicació més usual és simular una explosió i es fa que les partícules projectades siguin d'un color adient, com blanc o groc. En el nostre cas volem simular el trencament dels totxos i, per tant, donarem a les partícules el color bàsic del totxo que s'ha destruït en aquell moment. Això ens obligarà a assignar un color a cada totxo. Fixem-nos, però, que si els totxos encara fossin els rectangles (en lloc d'sprites) aquest color ja el tindríem.

Al fitxer de colors hi hem de posar la llista dels colors que corresponen als totxos. De fet, aquesta llista ja l'havíem tingut en versions anteriors.

colors.dart
import 'dart:ui';
class ColorsApp {
  static const Color fons = Color(0xFFFFEECC);
  static const Color tema = Color(0xFF114477);
  static const gradient = [  // Gradient del marc
    Color(0xFFAADDEE), 
    Color(0xFFFFEECC)
  ];
  static const colorsTotxos = [
    Color(0xFF2277AA),
    Color(0xFF99BB66),
    Color(0xFFFFCC44),
    Color(0xFFFF9911),
    Color(0xFFFF4444),
  ];
}

En el fitxer dels totxos hem d'afegir la variable del color i la funció que crea l'efecte. La funció que genera l'efecte només es crida si qui ha xocat amb el totxo és la bola. En aquest cas només la bola pot xocar amb el totxo i, per tant, la comprovació no caldria; però cal tenir-ho present en un cas general.

totxo.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/particles.dart';
import 'package:joc_breakout/breakout.dart';
import 'package:joc_breakout/config.dart';
import 'bola.dart';
import 'pala.dart';
class Totxo extends SpriteComponent
    with CollisionCallbacks, HasGameReference<Breakout> {
  // Ara demanem la posició en la imatge
  Totxo({
    required super.position, 
    required this.posicio,
    required this.colorParticules,
    })
    : super(
        size: Vector2(brickWidth, brickHeight),
        anchor: Anchor.center,
      );

  final Vector2 posicio;  // Posició de cada totxo al fitxer
  final Color colorParticules;

  // En carregar l'sprite, definim el fitxer i el detector de col·lisions
  @override
  Future<void> onLoad() async {
    super.onLoad();
    sprite = Sprite(
      game.images.fromCache('sheet.png'),
      srcPosition: posicio, // Utilitzem la posició que ens passen
      srcSize: midaTotxoImg,  // Mida del dibuix del totxo
    );
    // Afegim la caixa de col·lisions
    add(RectangleHitbox());
  }

  @override
  void onCollisionStart(
    Set<Vector2> intersectionPoints,
    PositionComponent other,
  ) {
    super.onCollisionStart(intersectionPoints, other);
    // Només generem l'efecte si el xoc és amb la bola
    if (other is Bola) {
      _creaExplosioParticules();
    }
    removeFromParent();
    game.punts.value++;
    // Mira si era el darrer totxo
    if (game.world.children.query<Totxo>().length == 1) {
      game.playState = PlayState.guanya;
      game.world.removeAll(game.world.children.query<Bola>());
      game.world.removeAll(game.world.children.query<Pala>());
    }
  }

  // Efecte de partícules en explosió
  void _creaExplosioParticules() {
    final random = Random();
    // Afegim el sistema de partícules directament al món del joc
    game.world.add(
      ParticleSystemComponent(
        position: position.clone(), // Sortiran des del centre del totxo actual
        particle: Particle.generate(
          count: 15, // Nombre de partícules de l'explosió
          lifespan: 0.4, // Durada de l'efecte en segons
          generator: (i) {
            // Calculem una direcció i velocitat aleatòria (360 graus)
            final angle = random.nextDouble() * 2 * pi;
            final velocitat = random.nextDouble() * 120 + 60;

            return AcceleratedParticle(
              acceleration: Vector2(0, 250), // Gravetat cap avall perquè caiguin
              speed: Vector2(cos(angle), sin(angle)) * velocitat,
              child: CircleParticle(
                radius: 2.0 + random.nextDouble() * 3.0, // Mida aleatòria de la partícula
                paint: Paint()..color = colorParticules, 
              ),
            );
          },
        ),
      ),
    );
  }
}

També hem de modificar el fitxer principal del joc per tal que enviï el color que correspon a cada totxo.

breakout.dart
import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:joc_breakout/core/colors.dart';
import 'components/components.dart';
import 'config.dart';
enum PlayState {inici, jugant, fi, guanya}
class Breakout extends FlameGame 
    with HasCollisionDetection, KeyboardEvents, TapCallbacks {
  Breakout()
    : super(
        camera: CameraComponent.withFixedResolution(
          width: gameWidth,
          height: gameHeight,
        ),
      );

  final ValueNotifier<int> punts = ValueNotifier(0);
  final rand = math.Random();  // Generador de valors aleatoris
  double get width => size.x;
  double get height => size.y;
  late PlayState _playState;
  PlayState get playState => _playState;
  set playState(PlayState playState) {
    _playState = playState;
    switch (playState) {
      case PlayState.inici:
      case PlayState.fi:
      case PlayState.guanya:
        overlays.add(playState.name);
      case PlayState.jugant:
        overlays.remove(PlayState.inici.name);
        overlays.remove(PlayState.fi.name);
        overlays.remove(PlayState.guanya.name);
    }
  }

  @override
  FutureOr<void> onLoad() async {
    super.onLoad();
    await images.load('sheet.png');
    camera.viewfinder.anchor = Anchor.topLeft;
    world.add(PlayArea());
    playState = PlayState.inici;
  }

  void startGame() {
    if (playState == PlayState.jugant) return;
    world.removeAll(world.children.query<Bola>());
    world.removeAll(world.children.query<Pala>());
    world.removeAll(world.children.query<Totxo>());
    playState = PlayState.jugant;
    punts.value = 0;
    world.add(Bola(
      factorDificultat: factorDif,
      radius: ballRadius,
      position: size / 2,  // Centre de l'àrea de joc
      velocity: Vector2(
        (rand.nextDouble() - 0.5) * width,
        height * 0.2,
      ).normalized()..scale(height / 4),
    ));
    world.add(
      Pala(
        size: Vector2(batWidth, batHeight),
        position: Vector2(width / 2, height * 0.95),
      ),
    );
    // Afegim els totxos amb dos bucles
    world.addAll([
      for (var i = 0; i < numTotxosFilera; i++)
        //for (var j = 0; j < llistaImatgesTotxos.length; j++)
        for (var j = 0; j < llistaCoordenades.length; j++) 
          Totxo(
            position: Vector2(
              (i + 0.5) * brickWidth + (i + 1) * juntaTotxos,
              (j + 1 + 2.0) * brickHeight + (j + 1) * juntaTotxos,
            ),
            posicio: llistaCoordenades[j],
            colorParticules: ColorsApp.colorsTotxos[j],
          ),
        
    ]);
  }

  @override
  void onTapDown(TapDownEvent event) {
    super.onTapDown(event);
    //if(playState == PlayState.inici){
      startGame();
    //}
  }

  @override
  KeyEventResult onKeyEvent(
    KeyEvent event,
    Set<LogicalKeyboardKey> keysPressed,
  ) {
    super.onKeyEvent(event, keysPressed);
    switch (event.logicalKey) {
      case LogicalKeyboardKey.arrowLeft:
        world.children.query<Pala>().first.moveBy(-batStep);
      case LogicalKeyboardKey.arrowRight:
        world.children.query<Pala>().first.moveBy(batStep);
      case LogicalKeyboardKey.space:
      case LogicalKeyboardKey.enter:
        startGame();
    }
    return KeyEventResult.handled;
  }

  @override
  Color backgroundColor() => ColorsApp.fons;
}

 

 

 

 

 

 

 

 

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.