Ara afegim la pala, que ha de poder ser moguda per l'usuari. Segons la plataforma on s'executi l'aplicació, l'usuari podrà fer servir el teclat, el ratolí o bé desplaçar el dit per la pantalla. Per començar, afegim les dues dimensions de la pala i el desplaçament que farà la pala cada cop que l'usuari premi la tecla corresponent.
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;
També hem de definir el color de la pala.
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);
}
A la carpeta components creem el fitxer pala.dart on posarem un RectangleComponent que representarà la pala. A més del HasGameReference, que ja havíem afegit a l'àrea de joc i a la bola, ara afegim DragCallbacks, per tal que sigui sensible a les accions del ratolí i la pantalla tàctil.
El mètode onDragUpdate s'encarrega de les accions amb el ratolí o movent el dit per la pantalla. Farem que només tingui en compte els moviments en l'eix X (horitzontal) i que el centre de la pala es mogui en una zona limitada (clamp) per les mides de l'àrea de joc; així la pala podrà sortir fins a la meitat per qualsevol dels dos costats.
El mètode moveBy s'encarrega del moviment amb el teclat. En aquest cas cal indicar quin serà el desplaçament per fotograma en cas que l'usuari mantingui premuda la tecla.
pala.dart
import 'package:flame/collisions.dart'; import 'package:flame/components.dart'; import 'package:flame/effects.dart'; import 'package:flame/events.dart'; import 'package:flutter/material.dart'; import 'package:joc_breakout/breakout.dart'; import 'package:joc_breakout/core/colors.dart';
class Pala extends RectangleComponent
with DragCallbacks, HasGameReference<Breakout> {
Pala({
required super.position,
required super.size,
}) : super(
anchor: Anchor.center,
children: [RectangleHitbox()],
paint: Paint()
..color = ColorsApp.pala
..style = PaintingStyle.fill,
);
@override
void onDragUpdate(DragUpdateEvent event) {
super.onDragUpdate(event);
position.x = (position.x + event.localDelta.x).clamp(0, game.width);
}
void moveBy(double dx) {
add(
MoveToEffect(
Vector2((position.x + dx).clamp(0, game.width), position.y),
EffectController(duration: 0.1),
),
);
}
}
Un cop definida la pala, l'haurem d'afegir en el fitxer de components.
components.dart
export 'bola.dart'; export 'pala.dart'; export 'play_area.dart';
Per tal que es vegi, l'haurem d'incorporar al joc. La posició inicial és al centre de l'àrea de joc, però molt propera a la part inferior.
A més, hem d'afegir KeyboardEvents a la classe i haurem de sobrescriure el mètode onKeyEvent, per tal que capturi les accions del teclat. El mètode captura les tecles premudes i, en cas que sigui una de les fletxes laterals, desplaça la pala en la direcció corresponent.
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 '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),
position: Vector2(width / 2, height * 0.95),
),
);
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 la pala. Aprofitem per afegir que, quan s'escapa per la part inferior, la bola desaparegui amb un efecte de retard. Això és important per evitar que la bola desaparegui en el mateix moment que toca la vora, quan encara és visible.
El rebot de la pilota amb la pala té en compte la posició relativa entre la pilota i la pala. En aquest cas, cal tenir present que position representa la pilota (l'element on es detecten les col·lisions) i other.position i other.size representen dades de la pala (atès que other correspon a l'element amb el qual es detecta la col·lisió).
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/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, // El paràmetre accedirà directament a l'original
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 {
debugPrint('collision with $other');
}
}
}
Ara el programa ja permet moure la pala i fer que la bola hi reboti.

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