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

La bola

Arrancando

Ya hay partida. La bola rebota en las paredes, rebota en la pala, y si se te escapa por abajo vuelve a salir desde el centro.

Una bola es una posición y una velocidad

let velocityX = BALL_SPEED * 0.55;
let velocityY = -BALL_SPEED;

Dos números: cuánto avanza por segundo en horizontal y cuánto en vertical. velocityY empieza en negativo porque (capítulo 2) restar de la Y es subir.

Y moverla es el mismo dt de siempre, dos veces:

ball.transform.x += velocityX * dt;
ball.transform.y += velocityY * dt;

Rebotar es cambiar un signo

Contra la pared izquierda:

if (ball.transform.x <= 0) {
    ball.transform.x = 0;
    velocityX = Math.abs(velocityX);
}

Dos cosas, y las dos importan.

Recolocarla en el borde (x = 0). La bola se mueve a saltos, así que rara vez cae exactamente en 0: lo normal es que un fotograma la deje en −3. Si no la devuelves al borde, se queda un pelín fuera.

Forzar el signo con Math.abs, en vez de velocityX = -velocityX. Aquí está el fallo clásico de este juego, y merece un párrafo entero.

Si la bola se queda pegada al borde, la condición x <= 0 se cumple otra vez al fotograma siguiente. Con -velocityX, le das la vuelta otra vez, y otra, y otra. La bola tirita en el borde, atrapada. Con Math.abs, dices lo que de verdad quieres: a partir de ahora, hacia la derecha, esté como esté. Repetirlo no hace daño.

La pala: colisión de cajas

const onPaddle =
    velocityY > 0 &&
    ball.transform.y + BALL_SIZE >= PADDLE_Y &&
    ball.transform.y <= PADDLE_Y + PADDLE_H &&
    ball.transform.x + BALL_SIZE >= paddle.transform.x &&
    ball.transform.x <= paddle.transform.x + PADDLE_W;

Cuatro comparaciones que preguntan si dos rectángulos se solapan. Se llama AABB (axis-aligned bounding box, caja alineada con los ejes) y es la colisión más simple que existe. Con rectángulos que no rotan, sobra.

La quinta condición, la primera, es la que evita otro fallo desagradable: velocityY > 0 significa solo si la bola va bajando. Sin eso, una bola que roza la pala por el lateral mientras sube rebotaría hacia arriba estando ya por debajo, y se quedaría enganchada en el borde inferior de la pala.

Y cuando la toca, pasa lo mismo que con las paredes: se la recoloca justo encima (ball.transform.y = PADDLE_Y - BALL_SIZE) antes de mandarla hacia arriba, para que no se dibuje ni un fotograma metida dentro de la pala.

El golpe no es siempre igual

const offset = (ball.transform.x + BALL_SIZE / 2 - paddle.transform.x) / PADDLE_W - 0.5;
velocityX = offset * BALL_SPEED * 1.6;

Esto es lo que convierte el juego en un juego. offset mide por dónde le has dado, como un número entre −0,5 (el extremo izquierdo de la pala) y +0,5 (el derecho). El centro da 0.

Y esa medida se convierte en la velocidad horizontal de salida. Dale con la punta izquierda y la bola sale disparada hacia la izquierda; dale con el centro y sale casi recta.

Sin esta línea la bola rebotaría siempre con el mismo ángulo, y no tendrías forma de apuntar. Un rompeladrillos donde no puedes apuntar es un salvapantallas.

¿Qué significa `&&`?

&& es «y». Una condición con && solo es cierta si todas sus partes lo son:

if (tengoHambre && hayComida) comer();

Aquí hay cinco partes encadenadas, cada una en su línea para poder leerlas. La bola toca la pala solo si se cumplen las cinco a la vez: va bajando, y su borde de abajo llega a la pala, y no se ha pasado de largo, y su borde derecho llega al izquierdo de la pala, y su borde izquierdo no se ha pasado del derecho.

También existe ||, que es «o» y basta con que se cumpla una.

Ahora tú

Cambia el 1.6 del último trozo por 3 y fíjate en lo salvaje que se vuelve el ángulo. Ponlo a 0 y verás el salvapantallas del que te hablaba.

Y prueba a poner velocityX = -velocityX en lugar de Math.abs(velocityX) en los rebotes laterales. Puede que tarde un rato en pasar, pero acabará tiritando.