Aplicacions amb Flutter, Dart i Flame

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

Calculem i mostrem la puntuació

Normalment els jocs tenen una puntuació, el nostre no serà menys. Es sumarà un punt cada cop que l'usuari elimini un totxo i els punts s'aniran mostrant a la part superior de la pantalla.

A la classe del joc (Breakout) posarem una variable per comptar els punts i un notificador, que estigui al cas de quan el valor canvia.

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();
    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),
        cornerRadius: const Radius.circular(ballRadius / 2),
        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 < ColorsApp.colorsTotxos.length; j++)
          Totxo(
            position: Vector2(
              (i + 0.5) * brickWidth + (i + 1) * juntaTotxos,
              (j + 1 + 2.0) * brickHeight + (j + 1) * juntaTotxos,
            ),
            color: ColorsApp.colorsTotxos[j],
          ),
    ]);
  }

  @override
  void onTapDown(TapDownEvent event) {
    super.onTapDown(event);
    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;
}

Cal sumar un punt cada cop que s'elimini un totxo.

totxo.dart
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import '../breakout.dart';
import '../config.dart';
import 'bola.dart';
import 'pala.dart';
class Totxo extends RectangleComponent
    with CollisionCallbacks, HasGameReference<Breakout> {
  Totxo({required super.position, required Color color})
    : super(
        size: Vector2(brickWidth, brickHeight),
        anchor: Anchor.center,
        paint: Paint()
          ..color = color
          ..style = PaintingStyle.fill,
        children: [RectangleHitbox()],
      );

  @override
  void onCollisionStart(
    Set<Vector2> intersectionPoints,
    PositionComponent other,
  ) {
    super.onCollisionStart(intersectionPoints, other);
    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>());
    }
  }
}

Necessitarem un giny que mostri la puntuació. Fem servir un element que vagi rebent els canvis en la puntuació que detecta el notificador.

puntuacio.dart
import 'package:flutter/material.dart';
class Puntuacio extends StatelessWidget {
  const Puntuacio({super.key, required this.punts});

  final ValueNotifier<int> punts;

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<int>(
      valueListenable: punts,
      builder: (context, punts, child) {
        return Padding(
          padding: const EdgeInsets.fromLTRB(12, 6, 12, 18),
          child: Text(
            'Punts: $punts'.toUpperCase(),
            style: Theme.of(context).textTheme.titleLarge!,
          ),
        );
      },
    );
  }
}

I, per acabar, afegim el giny de la puntuació en l'àrea del joc.

game_app.dart
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:joc_breakout/core/colors.dart';
import '../breakout.dart';
import '../config.dart';
import 'sobreposat.dart';
import 'puntuacio.dart';
class GameApp extends StatefulWidget {
  const GameApp({super.key});

  @override
  State<GameApp> createState() => _GameAppState();
}

class _GameAppState extends State<GameApp> {
  late final Breakout game;

  @override
  void initState() {
    super.initState();
    game = Breakout();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        textTheme: GoogleFonts.pressStart2pTextTheme().apply(
          bodyColor: ColorsApp.tema,
          displayColor: ColorsApp.tema,  // Color del text
        ),
      ),
      home: Scaffold(
        body: Container(
          decoration: const BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: ColorsApp.gradient,  // Gradient del marc
            ),
          ),
          child: SafeArea(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Center(
                child: Column(
                  children: [
                    Puntuacio(punts: game.punts),
                    Expanded(
                      child: FittedBox(
                        child: SizedBox(
                          width: gameWidth,
                          height: gameHeight,
                          child: GameWidget(
                            game: game,
                            overlayBuilderMap: {
                              PlayState.inici.name: (context, game) =>
                                const Sobreposat(
                                  title: 'PICA PER COMENÇAR',
                                  subtitle: 'Empra les fletxes o fes lliscar',
                                ),
                              PlayState.fi.name: (context, game) =>
                                const Sobreposat(
                                  title: 'F I   D E L   J O C',
                                  subtitle: 'Pica per tornar a jugar',
                                ),
                              PlayState.guanya.name: (context, game) =>
                                const Sobreposat(
                                  title: 'H A S   G U A N Y A T !',
                                  subtitle: 'Pica per tornar a jugar',
                                ),
                            }
                          )
                        )
                      )
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Arribats a aquest punt, l'aplicació, vista amb l'emulador, té aquest aspecte:

Pantalla

 

 

 

 

 

 

 

 

 

 

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