Aplicacions amb Flutter, Dart i Flame

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

Afegim els totxos

Ara afegirem els totxos. Posarem menys totxos que al joc original, però en una disposició semblant. En concret, hi haurà cinc fileres de deu totxos i els totxos de cada filera seran de color diferent als de les altres. Comencem per definir els cinc colors dels totxos.

colors.dart
import 'dart:ui';
class ColorsApp {
  static const Color fons = Color(0xFFFFEECC);
  static const Color bola = Color(0xFFFF6699);
  static const Color pala = Color(0xFF116699);
  static const colorsTotxos = [
    Color(0xFF2277AA),
    Color(0xFF99BB66),
    Color(0xFFFFCC44),
    Color(0xFFFF9911),
    Color(0xFFFF4444),
  ];
}

Necessitarem alguns paràmetres nous; en concret le nombre de totxos que tindrà cada filera, l'amplada de la separació entre totxos i l'alçada i l'amplada d'aquests. L'amplada dels totxos la calcularem per tal que ocupin tota l'amplada de l'àrea de joc.

config.dart
const gameWidth = 820.0;
const gameHeight = 1600.0;
const ballRadius = gameWidth * 0.02;
const batWidth = gameWidth * 0.2;
const batHeight = ballRadius * 2;
const batStep = gameWidth * 0.05;
const numTotxosFilera = 10;
const juntaTotxos = gameWidth * 0.015;
final brickWidth = (gameWidth - (juntaTotxos * (numTotxosFilera + 1))) / numTotxosFilera;
const brickHeight = gameHeight * 0.03;

Ara anem a crear els totxos. A la carpeta components creem el fitxer totxo.dart on posarem un RectangleComponent que representarà cada totxo. A més del HasGameReference, que ja havíem afegit a l'àrea de joc, a la pala i a la bola, ara afegim CollisionCallbacks, per tal que sigui sensible a les col·lisions.

El mètode onCollisionStart s'encarrega de detectar les col·lisions. Cada cop que la pilota toqui un totxo, aquest desapareixerà. Cal estar atents, però, per al cas que sigui el darrer totxo; ja que llavors farem desaparèixer la pilota i la pala.

Com veurem més endavant, els totxos els afegim tots de cop, en uns bucles, i, per tant, constituiran una mena de llista de la qual en podrem saber la seva llargada en qualsevol moment. Quan la llista només tingui un element voldrà dir que aquell totxo és el darrer. Fixem-nos que la llargada de la llista de totxos serà 1 i no 0, perquè aquell totxo no desapareixerà fins que s'acabi de processar el fotograma actual.

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();
    // Mira si era el darrer totxo
    if (game.world.children.query<Totxo>().length == 1) {
      game.world.removeAll(game.world.children.query<Bola>());
      game.world.removeAll(game.world.children.query<Pala>());
    }
  }
}

Un cop definit el totxo, l'haurem d'afegir en el fitxer de components.

components.dart
export 'bola.dart';
export 'pala.dart';
export 'totxo.dart';
export 'play_area.dart';

Per tal que es vegin els totxos, els haurem d'incorporar al joc. Farem servir uns bucles que trigaran un temps a executar-se, per això farem servir await. Per afegir un conjunt d'elements, emprarem la funció addAll en lloc d'usar add (com havíem fet amb l'àrea de joc, la pilota i la pala). Calcularem la posició de cada totxo tenint present que correspon al seu centre i respectant la junta de separació entre ells.

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';
class Breakout extends FlameGame 
    with HasCollisionDetection, KeyboardEvents  {
  Breakout()
    : super(
        camera: CameraComponent.withFixedResolution(
          width: gameWidth,
          height: gameHeight,
        ),
      );

  final rand = math.Random();  // Generador de valors aleatoris
  double get width => size.x;
  double get height => size.y;

  @override
  FutureOr<void> onLoad() async {
    super.onLoad();
    camera.viewfinder.anchor = Anchor.topLeft;
    world.add(PlayArea());
    world.add(Bola(
      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
    await 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],
          ),
    ]);
    debugMode = true;  // Línia provisional
  }

  @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);
    }
    return KeyEventResult.handled;
  }
}

Hem de modificar el fitxer de la bola, per tal de tenir en compte la interacció amb els totxos. L'eliminació dels totxos la gestionem en la definició d'aquests però el rebot de la bola quan els toca la gestionarem en aquesta.

bola.dart
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/material.dart';
import 'package:joc_breakout/breakout.dart';
import 'package:joc_breakout/components/pala.dart';
import 'package:joc_breakout/components/totxo.dart';
import 'package:joc_breakout/components/play_area.dart';
import 'package:joc_breakout/core/colors.dart';
class Bola extends CircleComponent 
    with CollisionCallbacks, HasGameReference<Breakout> {
  Bola({
    required this.velocity,
    required super.position,
    required double radius,
  }) : super(
        radius: radius,
        anchor: Anchor.center,
        paint: Paint()
          ..color = ColorsApp.bola
          ..style = PaintingStyle.fill,
        children: [CircleHitbox()],
      );

  final Vector2 velocity;

  @override
  void update(double dt) {
    super.update(dt);
    position += velocity * dt;
  }

  @override
  void onCollisionStart(
    Set<Vector2> intersectionPoints,
    PositionComponent other,
  ) {
    super.onCollisionStart(intersectionPoints, other);
    if (other is PlayArea) {
      if (intersectionPoints.first.y <= 0) {
        velocity.y = -velocity.y;
      } else if (intersectionPoints.first.x <= 0) {
        velocity.x = -velocity.x;
      } else if (intersectionPoints.first.x >= game.width) {
        velocity.x = -velocity.x;
      } else if (intersectionPoints.first.y >= game.height) {
        add(RemoveEffect(delay: 0.35));
      }
    } else if (other is Pala) {
      velocity.y = -velocity.y;
      velocity.x =
          velocity.x +
          (position.x - other.position.x) / other.size.x * game.width * 0.3;
    } else if (other is Totxo) {
      if (position.y < other.position.y - other.size.y / 2) {
        velocity.y = -velocity.y;
      } else if (position.y > other.position.y + other.size.y / 2) {
        velocity.y = -velocity.y;
      } else if (position.x < other.position.x) {
        velocity.x = -velocity.x;
      } else if (position.x > other.position.x) {
        velocity.x = -velocity.x;
      }
    }
  }
}

Ara el joc ja es comença a assemblar a la versió final.

 

 

 

 

 

 

 

 

 

 

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