Ya es un juego. Los ladrillos desaparecen cuando les das.
Un ladrillo que sabe morirse
En bricks.ts han aparecido tres cosas nuevas:
const self = useSelf();
const brick: BrickHandle = {
x,
y,
hit: () => {
bricks.splice(bricks.indexOf(brick), 1);
destroy(self);
},
};
bricks.push(brick);useSelf() le da al componente su propia identidad: el objeto que useSpawn creó
para él. Hace falta porque los objetos del motor son datos planos, sin métodos: no existe
un ladrillo.destruirse(). Existe destroy(algo), una función suelta, y useSelf es cómo
un componente consigue ese algo para pasárselo.
Y hit() hace las dos cosas en el orden correcto: primero se quita de la lista de
ladrillos vivos, después se borra del mundo.
El motor no lo borra en ese momento
Esto es lo importante del capítulo, y es lo que separa un juego que va bien de uno con fallos raros que solo pasan a veces.
destroy(self) no borra el ladrillo ahí mismo. Lo marca, y el motor lo saca del árbol
al terminar el fotograma, antes de dibujar nada. En Godot funciona igual, y por los mismos
dos motivos:
- No hay fotograma fantasma. El ladrillo no llega a dibujarse una última vez ya «muerto».
- No hay doble impacto. El resto del fotograma sigue ejecutándose con normalidad, pero
el ladrillo ya está fuera de la lista
bricks, así que nada puede volver a golpearlo.
Si el borrado fuera inmediato, cualquier código que estuviera recorriendo la lista de ladrillos en ese instante se encontraría el suelo moviéndose bajo los pies. Diferirlo lo hace aburrido, y aburrido es exactamente lo que quieres.
La lista la lleva el juego
const bricks: BrickHandle[] = [];
const spawnBrick = useSpawn(Brick);
// ...
spawnBrick({ row, col, bricks });Un array normal con los ladrillos que siguen vivos. Se crea dentro de la escena y cada ladrillo lo recibe al nacer, junto a su fila y su columna. Así cada partida tiene la suya: si la escena vuelve a empezar, la lista vuelve a empezar vacía. Si la sacaras del cuerpo de la escena, a lo alto del fichero, sobreviviría a la escena, y una partida nueva heredaría los ladrillos de la anterior.
El motor no tiene todavía una consulta del tipo «dame todo lo que se pueda golpear», así que el juego lleva su propia lista. Para un muro, es lo bastante honesto.
Y buscar el impacto es recorrerla:
export const brickAt = (bricks, x, y, size) =>
bricks.find((brick) =>
x + size > brick.x && x < brick.x + BRICK_W &&
y + size > brick.y && y < brick.y + BRICK_H);Es el mismo AABB del capítulo anterior, con find, que devuelve el primer elemento que
cumple la condición, o undefined si no hay ninguno.
En el bucle principal:
const brick = brickAt(bricks, ball.transform.x, ball.transform.y, BALL_SIZE);
if (brick) {
brick.hit();
const above = ball.transform.y + BALL_SIZE / 2 < brick.y + BRICK_H / 2;
velocityY = above ? -Math.abs(velocityY) : Math.abs(velocityY);
}El rebote manda la bola lejos del ladrillo: hacia arriba si su centro está por encima del
centro del ladrillo, hacia abajo si está por debajo. Es el Math.abs del capítulo de la bola
otra vez, y por lo mismo. Una bola que entra justo por la junta entre dos ladrillos los toca
a los dos, uno en cada fotograma. Con velocityY = -velocityY, el segundo le daría la vuelta
otra vez y la bola seguiría hacia arriba, atravesando el muro. Diciendo hacia dónde tiene que
ir, repetirlo no hace daño.
Un fallo que hay ahí, y lo sabes
El rebote es siempre vertical, le des por arriba o por el lateral. Si le das a un ladrillo por el costado, la bola debería salir hacia el lado, no hacia abajo.
Está así porque arreglarlo bien cuesta bastante más código del que cabe en un tutorial de introducción, y porque en un rompeladrillos casi todos los impactos son por arriba o por abajo. Es una decisión, no un descuido, y en tu juego puedes decidir otra cosa.
¿Qué es `undefined` y por qué `if (brick)` funciona?
brickAt devuelve un ladrillo... o undefined, que es como JavaScript dice «aquí no hay
nada».
En un if, ciertos valores cuentan como falsos aunque no sean false: undefined,
null, 0, la cadena vacía. Al resto se les llama verdaderos.
Así que if (brick) se lee como si ha encontrado alguno. Es la forma corta y habitual de
escribir if (brick !== undefined).
Ahora tú
Rompe todos los ladrillos. No pasa nada: no hay puntuación, no hay final, no hay premio. Eso es lo siguiente.
Mientras tanto: prueba a comentar la línea bricks.splice(...) dejando solo el
destroy(self). El ladrillo desaparece de la pantalla pero sigue en la lista, así que la
bola rebota contra un ladrillo invisible para siempre. Es un fallo estupendo de ver una
vez.