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:


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