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;
}

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