Esta página ha sido traducida del inglés por la comunidad. Aprende más y únete a la comunidad de MDN Web Docs.

View in English Always switch to English

Construir el muro de ladrillos

Este es el paso 6 de los 11 del tutorial para crear un juego Breakout con JavaScript puro. Veamos cómo crear un grupo de ladrillos, dibujarlos en la pantalla con un bucle y eliminarlos cuando la pelota los golpea. Construir el muro de ladrillos es un poco más complicado que añadir un solo objeto a la pantalla.

Dibujar los ladrillos

Todos los ladrillos usan la misma imagen, así que podemos crearla y decodificarla una sola vez y compartirla entre ellos. Añade un mapa estático assets y un campo url a GameObject, y sustituye su constructor y su método preload():

js
class GameObject {
  static assets = new Map();
  url;
  // …
  constructor(url, ctx) {
    this.url = url;
    this.ctx = ctx;
  }
  async preload() {
    if (!GameObject.assets.has(this.url)) {
      const asset = new Image();
      asset.src = this.url;
      GameObject.assets.set(
        this.url,
        asset.decode().then(() => asset),
      );
    }
    this.asset = await GameObject.assets.get(this.url);
    if (this.size.w === undefined) {
      this.size.w = this.asset.width;
      this.size.h = this.asset.height;
    }
  }
  // …
}

La caché asocia cada URL con una promesa que se resuelve con la imagen decodificada. La primera llamada a preload() para una URL crea la imagen y empieza a decodificarla; las llamadas posteriores esperan a la misma promesa y reciben la misma imagen. Cada objeto sigue teniendo su propia posición y su propio tamaño.

Al igual que Ball y Paddle, Brick también se basa en la clase GameObject. Un ladrillo no tiene posición ni tamaño predeterminados, y hay que indicarlos explícitamente en el constructor. Como los ladrillos tienen dimensiones explícitas, podemos usar los parámetros adicionales dWidth y dHeight de ctx.drawImage(), que escala automáticamente la imagen si todavía no tiene las dimensiones deseadas.

js
class Brick extends GameObject {
  constructor(url, ctx, x, y, w, h) {
    super(url, ctx);
    this.pos = { x, y };
    this.size = { w, h };
  }
  draw() {
    const { left, top } = this.hitbox;
    this.ctx.drawImage(this.asset, left, top, this.size.w, this.size.h);
  }
}

También tienes que descargar la imagen del ladrillo y guardarla en tu directorio /img.

Pondremos todo el código para dibujar los ladrillos dentro de una función initBricks, para mantenerlo separado del resto del código. Añade una llamada a initBricks debajo de colliders.push(paddle);:

js
// …
const paddle = new Paddle("img/paddle.png", ctx);
colliders.push(paddle);
const bricks = initBricks();
// …

Y asegúrate de que el juego espere a que los ladrillos se precarguen antes de empezar, añadiendo , ...bricks al array dentro de Promise.all(). Estas llamadas comparten la promesa de decodificación almacenada en caché, así que la imagen del ladrillo solo se decodifica una vez.

Ahora vamos con la función en sí. Añade la función initBricks al final del archivo script.js. Para empezar, añadimos el objeto bricksLayout, que nos será útil muy pronto:

js
function initBricks() {
  const bricksLayout = {
    width: 50,
    height: 20,
    count: {
      row: 3,
      col: 7,
    },
    offset: {
      top: 50,
      left: 60,
    },
    padding: 10,
  };
  const bricks = [];
  // sigue añadiendo código aquí...
  return bricks;
}

Este bricksLayout contiene toda la información que necesitamos: el ancho y el alto de un solo ladrillo, el número de filas y columnas de ladrillos que veremos en la pantalla, el desplazamiento superior e izquierdo (la posición del canvas donde empezamos a dibujar los ladrillos) y el espacio de separación entre cada fila y columna de ladrillos.

Ahora, empecemos a crear los ladrillos en sí. Podemos recorrer las filas y las columnas para crear un ladrillo nuevo en cada iteración; añade el siguiente bucle anidado debajo de la línea de código anterior:

js
for (let c = 0; c < bricksLayout.count.col; c++) {
  for (let r = 0; r < bricksLayout.count.row; r++) {
    const brickX =
      c * (bricksLayout.width + bricksLayout.padding) +
      bricksLayout.offset.left;
    const brickY =
      r * (bricksLayout.height + bricksLayout.padding) +
      bricksLayout.offset.top;

    const newBrick = new Brick(
      "img/brick.png",
      ctx,
      brickX,
      brickY,
      bricksLayout.width,
      bricksLayout.height,
    );
    bricks.push(newBrick);
  }
}

Cada posición brickX se calcula como bricksLayout.width más bricksLayout.padding, multiplicado por el número de columna, c, más bricksLayout.offset.left; la lógica de brickY es idéntica, salvo que usa los valores del número de fila, r, bricksLayout.height y bricksLayout.offset.top. Ahora cada ladrillo puede colocarse en su sitio, con un espacio de separación entre ellos, y dibujarse con un desplazamiento respecto a los bordes izquierdo y superior del canvas.

Por último, podemos dibujar estos ladrillos en la pantalla dentro de la función update(). Añade lo siguiente debajo de la llamada a paddle.draw():

js
for (const brick of bricks) {
  brick.draw();
}

Si recargas index.html en este punto, deberías ver los ladrillos dibujados en la pantalla, a la misma distancia unos de otros.

Detección de colisiones entre ladrillos y pelota

Pasemos al siguiente reto: la detección de colisiones entre la pelota y los ladrillos. Por suerte, ya implementamos un sistema de colisiones muy genérico, así que basta con conectar nuestros ladrillos a él.

Primero, registra cada ladrillo como colisionador, justo debajo de la llamada a initBricks():

js
const bricks = initBricks();
for (const brick of bricks) {
  colliders.push(brick);
}

Añade un método onCollide() a cada ladrillo, que lo elimina de las colecciones bricks y colliders:

js
class Brick extends GameObject {
  // …
  onCollide() {
    bricks.splice(bricks.indexOf(this), 1);
    colliders.splice(colliders.indexOf(this), 1);
  }
}

El ladrillo tiene que desaparecer lo antes posible, para que la pelota no rebote en él.

¡Y eso es todo! Recarga tu código y deberías ver que la nueva detección de colisiones funciona tal como se esperaba.

Compara tu código

Esto es lo que deberías tener hasta ahora, funcionando en vivo. Para ver su código fuente, haz clic en el botón "Play".

Próximos pasos

Ya podemos golpear los ladrillos y eliminarlos, lo que de por sí es una buena mejora en la jugabilidad. Sería todavía mejor llevar la cuenta de la puntuación y ganar cuando se destruyan todos los ladrillos.