Todos los capítulos · Capítulo 7/9

Romperlos

Arrancando

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:

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.