En molts jocs hi ha un personatge que es desplaça per la pantalla. Normalment ens interessa que quan el personatge es desplaça cap a un costat o un altre estigui mirant cap a aquell costat. Per fer-ho, cal tenir les dues versions al mateix full i triar una o l'altra segons el sentit del moviment. Fins i tot, en alguns casos, pot interessar tenir quatre orientacions del personatge o l'element que es desplaça.
En aquest cas canviarem el color de la bola quan es desplaci a la dreta o a l'esquerra. És un canvi que no és gaire realista però ens serveix per mostrar com es faria.
El nostre fitxer d'imatges serà la que es mostra a continuació, on trobem la pala, els cinc totxos i les dues boles.

Per baixar-la, pots picar sobre la imatge amb el botó dret i guardar-la a l'ordinador.
A la taula següent tenim les mides i la posició de cada imatge. La posició correspon a la coordenada superior esquerra.
| Element | Mides | Posició | ||
| Amplada | Alçada | X | Y | |
| Pala | 512 | 64 | 0 | 0 |
| Totxo blau | 256 | 128 | 0 | 64 |
| Totxo verd | 256 | 128 | 256 | 64 |
| Totxo groc | 256 | 128 | 0 | 192 |
| Totxo taronja | 256 | 128 | 256 | 192 |
| Totxo roig | 256 | 128 | 0 | 320 |
| Bola1 | 64 | 64 | 256 | 320 |
| Bola2 | 64 | 64 | 320 | 320 |
En el fitxer de configuració hem d'afegir les coordenades de la nova bola.
config.dart
import 'package:vector_math/vector_math.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;
const factorDif = 1.03;
// Configuració per al full d'sprites
Vector2 posPalaImg = Vector2(0, 0);
Vector2 midaPalaImg = Vector2(512, 64);
// Definim les coordenades de cada totxo dins de la imatge
final List<Vector2> llistaCoordenades = [
Vector2(0.0, 64.0), // Totxo blau
Vector2(256.0, 64.0), // Totxo verd
Vector2(0.0, 192.0), // Totxo groc
Vector2(256.0, 192.0), // Totxo taronja
Vector2(0.0, 320.0), // Totxo roig
];
Vector2 midaTotxoImg = Vector2(256, 128);
Vector2 posBolaImg1 = Vector2(256, 320);
Vector2 posBolaImg2 = Vector2(320, 320);
Vector2 midaBolaImg = Vector2(64, 64);
En la definició de la bola creem dues variables per guardar les definicions dels sprites, així només cal passar de l'una a l'altra sense haver-ho de tornar a carregar. Després carreguem els dos sprites amb la imatge qie correspon a cada un.
Atès que la velocitat inicial és aleatòria, definim l'sprite inicial segons el signe de la seva coordenada X. També actualitzem l'sprite dins del mètode update, per assegurar que es fa a cada refresc.
bola.dart
import 'package:flame/collisions.dart'; import 'package:flame/components.dart'; import 'package:flame/effects.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/config.dart';
class Bola extends SpriteComponent //
with CollisionCallbacks, HasGameReference<Breakout> {
Bola({
required this.velocity,
required super.position,
required double radius,
required this.factorDificultat,
}) : super(
// Posem el diàmetre (doble del radi) en les dues direccions
size: Vector2.all(radius * 2), //
anchor: Anchor.center,
);
final Vector2 velocity;
final double factorDificultat;
// Declarem els dos sprites com a variables de la classe per no recrear-los constantment
late final Sprite spriteDreta;
late final Sprite spriteEsquerra;
// En carregar l'sprite, definim el fitxer i el detector de col·lisions
@override
Future<void> onLoad() async {
super.onLoad();
// Carreguem la imatge des del fitxer
spriteDreta = Sprite(
game.images.fromCache('sheet.png'),
srcPosition: posBolaImg1, // Coordenades inici bola
srcSize: midaBolaImg, // Mida de la bola
);
spriteEsquerra = Sprite(
game.images.fromCache('sheet.png'),
srcPosition: posBolaImg2, // Coordenades inici bola
srcSize: midaBolaImg, // Mida de la bola
);
// Definim l'sprite inicial segons la velocitat de partida
sprite = velocity.x >= 0 ? spriteDreta : spriteEsquerra;
// Detector de col·lisions circular ajustat a la mida de la imatge
add(CircleHitbox());
}
@override
void update(double dt) {
super.update(dt);
position += velocity * dt;
// Actualitzem l'sprite dinàmicament a cada frame segons la direcció actual
if (velocity.x >= 0) {
sprite = spriteDreta;
} else {
sprite = spriteEsquerra;
}
}
@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,
onComplete: () {
game.playState = PlayState.fi;
},
),
);
}
} 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;
}
velocity.setFrom(velocity * factorDificultat);
}
}
}

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