You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/pt/6-space-game/4-collision-detection/README.md

12 KiB

Construir um Jogo Espacial Parte 4: Adicionar um Laser e Detetar Colisões

Questionário Pré-Aula

Questionário pré-aula

Nesta lição vais aprender a disparar lasers com JavaScript! Vamos adicionar duas coisas ao nosso jogo:

  • Um laser: este laser é disparado da nave do herói e segue verticalmente para cima.
  • Deteção de colisões, como parte da implementação da capacidade de disparar, também vamos adicionar algumas regras interessantes ao jogo:
    • Laser atinge inimigo: O inimigo morre se for atingido por um laser.
    • Laser atinge o topo do ecrã: Um laser é destruído se atingir a parte superior do ecrã.
    • Colisão entre inimigo e herói: Um inimigo e o herói são destruídos se colidirem um com o outro.
    • Inimigo atinge o fundo do ecrã: Um inimigo e o herói são destruídos se o inimigo atingir o fundo do ecrã.

Resumindo, tu -- o herói -- precisas de atingir todos os inimigos com um laser antes que eles consigam chegar ao fundo do ecrã.

Faz uma pequena pesquisa sobre o primeiro jogo de computador alguma vez criado. Qual era a sua funcionalidade?

Vamos ser heróicos juntos!

Deteção de colisões

Como fazemos a deteção de colisões? Precisamos de pensar nos objetos do jogo como retângulos em movimento. Porquê, perguntas tu? Bem, a imagem usada para desenhar um objeto do jogo é um retângulo: tem x, y, largura e altura.

Se dois retângulos, ou seja, um herói e um inimigo intersetarem, tens uma colisão. O que deve acontecer a seguir depende das regras do jogo. Para implementar a deteção de colisões, precisas do seguinte:

  1. Uma forma de obter uma representação em retângulo de um objeto do jogo, algo como isto:

    rectFromGameObject() {
      return {
        top: this.y,
        left: this.x,
        bottom: this.y + this.height,
        right: this.x + this.width
      }
    }
    
  2. Uma função de comparação, que pode ser assim:

    function intersectRect(r1, r2) {
      return !(r2.left > r1.right ||
        r2.right < r1.left ||
        r2.top > r1.bottom ||
        r2.bottom < r1.top);
    }
    

Como destruímos coisas

Para destruir coisas num jogo, precisas de informar o jogo que não deve mais pintar esse item no ciclo de jogo que é acionado em intervalos específicos. Uma forma de fazer isso é marcar um objeto do jogo como morto quando algo acontece, assim:

// collision happened
enemy.dead = true

Depois podes proceder para filtrar os objetos mortos antes de repintar o ecrã, assim:

gameObjects = gameObject.filter(go => !go.dead);

Como disparamos um laser

Disparar um laser traduz-se em responder a um evento de tecla e criar um objeto que se move numa direção específica. Por isso, precisamos de realizar os seguintes passos:

  1. Criar um objeto laser: a partir do topo da nave do herói, que ao ser criado começa a mover-se para cima em direção ao topo do ecrã.
  2. Associar código a um evento de tecla: precisamos de escolher uma tecla no teclado que represente o jogador a disparar o laser.
  3. Criar um objeto do jogo que se pareça com um laser quando a tecla é pressionada.

Intervalo de disparo do laser

O laser precisa de ser disparado sempre que pressionas uma tecla, como espaço, por exemplo. Para evitar que o jogo produza demasiados lasers num curto espaço de tempo, precisamos de corrigir isso. A solução é implementar um chamado intervalo de disparo, um temporizador, que garante que um laser só pode ser disparado de tempos em tempos. Podes implementar isso da seguinte forma:

class Cooldown {
  constructor(time) {
    this.cool = false;
    setTimeout(() => {
      this.cool = true;
    }, time)
  }
}

class Weapon {
  constructor {
  }
  fire() {
    if (!this.cooldown || this.cooldown.cool) {
      // produce a laser
      this.cooldown = new Cooldown(500);
    } else {
      // do nothing - it hasn't cooled down yet.
    }
  }
}

Consulta a lição 1 da série de jogos espaciais para te lembrares sobre intervalos de disparo.

O que construir

Vais pegar no código existente (que deves ter limpo e refatorado) da lição anterior e expandi-lo. Podes começar com o código da parte II ou usar o código em Parte III - inicial.

dica: o laser com que vais trabalhar já está na tua pasta de recursos e referenciado pelo teu código.

  • Adicionar deteção de colisões, quando um laser colide com algo, as seguintes regras devem ser aplicadas:
    1. Laser atinge inimigo: o inimigo morre se for atingido por um laser.
    2. Laser atinge o topo do ecrã: Um laser é destruído se atingir a parte superior do ecrã.
    3. Colisão entre inimigo e herói: um inimigo e o herói são destruídos se colidirem um com o outro.
    4. Inimigo atinge o fundo do ecrã: Um inimigo e o herói são destruídos se o inimigo atingir o fundo do ecrã.

Passos recomendados

Localiza os ficheiros que foram criados para ti na subpasta your-work. Deve conter o seguinte:

-| assets
  -| enemyShip.png
  -| player.png
  -| laserRed.png
-| index.html
-| app.js
-| package.json

Inicia o teu projeto na pasta your_work digitando:

cd your-work
npm start

O comando acima iniciará um servidor HTTP no endereço http://localhost:5000. Abre um navegador e insere esse endereço, neste momento deve renderizar o herói e todos os inimigos, nada está a mover-se - ainda :).

Adicionar código

  1. Configurar uma representação em retângulo do teu objeto do jogo, para lidar com colisões O código abaixo permite-te obter uma representação em retângulo de um GameObject. Edita a tua classe GameObject para expandi-la:

    rectFromGameObject() {
        return {
          top: this.y,
          left: this.x,
          bottom: this.y + this.height,
          right: this.x + this.width,
        };
      }
    
  2. Adicionar código que verifica colisões Isto será uma nova função que testa se dois retângulos se intersetam:

    function intersectRect(r1, r2) {
      return !(
        r2.left > r1.right ||
        r2.right < r1.left ||
        r2.top > r1.bottom ||
        r2.bottom < r1.top
      );
    }
    
  3. Adicionar capacidade de disparar laser

    1. Adicionar mensagem de evento de tecla. A tecla espaço deve criar um laser logo acima da nave do herói. Adiciona três constantes no objeto Messages:

       KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
       COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
       COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
      
    2. Lidar com a tecla espaço. Edita a função window.addEventListener keyup para lidar com espaços:

        } else if(evt.keyCode === 32) {
          eventEmitter.emit(Messages.KEY_EVENT_SPACE);
        }
      
    3. Adicionar ouvintes. Edita a função initGame() para garantir que o herói pode disparar quando a barra de espaço é pressionada:

      eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
       if (hero.canFire()) {
         hero.fire();
       }
      

      e adiciona uma nova função eventEmitter.on() para garantir o comportamento quando um inimigo colide com um laser:

      eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
        first.dead = true;
        second.dead = true;
      })
      
    4. Mover objeto, Garante que o laser se move gradualmente para o topo do ecrã. Vais criar uma nova classe Laser que expande GameObject, como já fizeste antes:

        class Laser extends GameObject {
        constructor(x, y) {
          super(x,y);
          (this.width = 9), (this.height = 33);
          this.type = 'Laser';
          this.img = laserImg;
          let id = setInterval(() => {
            if (this.y > 0) {
              this.y -= 15;
            } else {
              this.dead = true;
              clearInterval(id);
            }
          }, 100)
        }
      }
      
    5. Lidar com colisões, Implementa regras de colisão para o laser. Adiciona uma função updateGameObjects() que testa objetos em colisão:

      function updateGameObjects() {
        const enemies = gameObjects.filter(go => go.type === 'Enemy');
        const lasers = gameObjects.filter((go) => go.type === "Laser");
      // laser hit something
        lasers.forEach((l) => {
          enemies.forEach((m) => {
            if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
            eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
              first: l,
              second: m,
            });
          }
         });
      });
      
        gameObjects = gameObjects.filter(go => !go.dead);
      }  
      

      Certifica-te de adicionar updateGameObjects() no teu ciclo de jogo em window.onload.

    6. Implementar intervalo de disparo no laser, para que só possa ser disparado de tempos em tempos.

      Finalmente, edita a classe Hero para que possa ter intervalo de disparo:

      class Hero extends GameObject {
       constructor(x, y) {
         super(x, y);
         (this.width = 99), (this.height = 75);
         this.type = "Hero";
         this.speed = { x: 0, y: 0 };
         this.cooldown = 0;
       }
       fire() {
         gameObjects.push(new Laser(this.x + 45, this.y - 10));
         this.cooldown = 500;
      
         let id = setInterval(() => {
           if (this.cooldown > 0) {
             this.cooldown -= 100;
           } else {
             clearInterval(id);
           }
         }, 200);
       }
       canFire() {
         return this.cooldown === 0;
       }
      }
      

Neste ponto, o teu jogo já tem alguma funcionalidade! Podes navegar com as teclas de seta, disparar um laser com a barra de espaço, e os inimigos desaparecem quando os atinges. Muito bem!


🚀 Desafio

Adiciona uma explosão! Dá uma olhada nos recursos do jogo no repositório Space Art e tenta adicionar uma explosão quando o laser atinge um alienígena.

Questionário Pós-Aula

Questionário pós-aula

Revisão e Estudo Individual

Experimenta com os intervalos no teu jogo até agora. O que acontece quando os alteras? Lê mais sobre eventos de temporização em JavaScript.

Tarefa

Explorar colisões


Aviso Legal:
Este documento foi traduzido utilizando o serviço de tradução por IA Co-op Translator. Embora nos esforcemos para garantir a precisão, é importante ter em conta que traduções automáticas podem conter erros ou imprecisões. O documento original na sua língua nativa deve ser considerado a fonte autoritária. Para informações críticas, recomenda-se a tradução profissional realizada por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações incorretas decorrentes da utilização desta tradução.