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.
311 lines
12 KiB
311 lines
12 KiB
<!--
|
|
CO_OP_TRANSLATOR_METADATA:
|
|
{
|
|
"original_hash": "a6ce295ff03bb49df7a3e17e6e7100a0",
|
|
"translation_date": "2025-08-28T23:52:39+00:00",
|
|
"source_file": "6-space-game/4-collision-detection/README.md",
|
|
"language_code": "br"
|
|
}
|
|
-->
|
|
# Construindo um Jogo Espacial Parte 4: Adicionando um Laser e Detectando Colisões
|
|
|
|
## Questionário Pré-Aula
|
|
|
|
[Questionário pré-aula](https://ff-quizzes.netlify.app/web/quiz/35)
|
|
|
|
Nesta lição, você aprenderá a disparar lasers com JavaScript! Vamos adicionar dois elementos ao nosso jogo:
|
|
|
|
- **Um laser**: este laser será disparado da nave do herói e seguirá verticalmente para cima.
|
|
- **Detecção de colisões**: como parte da implementação da habilidade de *atirar*, também adicionaremos algumas regras interessantes ao jogo:
|
|
- **Laser atinge inimigo**: o inimigo é destruído se for atingido por um laser.
|
|
- **Laser atinge o topo da tela**: o laser é destruído se atingir a parte superior da tela.
|
|
- **Colisão entre inimigo e herói**: o inimigo e o herói são destruídos se colidirem.
|
|
- **Inimigo atinge a parte inferior da tela**: o inimigo e o herói são destruídos se o inimigo atingir a parte inferior da tela.
|
|
|
|
Resumindo, você -- *o herói* -- precisa atingir todos os inimigos com um laser antes que eles consigam chegar à parte inferior da tela.
|
|
|
|
✅ Faça uma pequena pesquisa sobre o primeiro jogo de computador já criado. Qual era sua funcionalidade?
|
|
|
|
Vamos ser heróicos juntos!
|
|
|
|
## Detecção de colisões
|
|
|
|
Como fazemos a detecção de colisões? Precisamos pensar nos objetos do jogo como retângulos em movimento. Por que isso, você pode perguntar? Bem, a imagem usada para desenhar um objeto do jogo é um retângulo: ela possui `x`, `y`, `largura` e `altura`.
|
|
|
|
Se dois retângulos, ou seja, um herói e um inimigo, *se cruzarem*, temos uma colisão. O que deve acontecer a partir daí depende das regras do jogo. Para implementar a detecção de colisões, você precisará do seguinte:
|
|
|
|
1. Uma maneira de obter uma representação retangular de um objeto do jogo, algo assim:
|
|
|
|
```javascript
|
|
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:
|
|
|
|
```javascript
|
|
function intersectRect(r1, r2) {
|
|
return !(r2.left > r1.right ||
|
|
r2.right < r1.left ||
|
|
r2.top > r1.bottom ||
|
|
r2.bottom < r1.top);
|
|
}
|
|
```
|
|
|
|
## Como destruir coisas
|
|
|
|
Para destruir coisas em um jogo, você precisa informar ao jogo que ele não deve mais desenhar esse item no loop do jogo que é acionado em um determinado intervalo. Uma maneira de fazer isso é marcar um objeto do jogo como *morto* quando algo acontece, assim:
|
|
|
|
```javascript
|
|
// collision happened
|
|
enemy.dead = true
|
|
```
|
|
|
|
Depois, você pode filtrar os objetos *mortos* antes de redesenhar a tela, assim:
|
|
|
|
```javascript
|
|
gameObjects = gameObject.filter(go => !go.dead);
|
|
```
|
|
|
|
## Como disparar um laser
|
|
|
|
Disparar um laser significa responder a um evento de tecla e criar um objeto que se move em uma determinada direção. Precisamos, portanto, 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 se mover para cima em direção ao topo da tela.
|
|
2. **Vincular código a um evento de tecla**: precisamos escolher uma tecla no teclado que represente o jogador disparando o laser.
|
|
3. **Criar um objeto do jogo que se pareça com um laser** quando a tecla for pressionada.
|
|
|
|
## Tempo de recarga do laser
|
|
|
|
O laser precisa ser disparado toda vez que você pressionar uma tecla, como *espaço*, por exemplo. Para evitar que o jogo produza lasers em excesso em um curto período de tempo, precisamos corrigir isso. A solução é implementar um chamado *tempo de recarga*, um temporizador, que garante que um laser só possa ser disparado em intervalos específicos. Você pode implementar isso da seguinte forma:
|
|
|
|
```javascript
|
|
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.
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
✅ Consulte a lição 1 da série de jogos espaciais para relembrar sobre *tempos de recarga*.
|
|
|
|
## O que construir
|
|
|
|
Você usará o código existente (que você deve ter organizado e refatorado) da lição anterior e o estenderá. Comece com o código da parte II ou use o código em [Parte III - inicial](../../../../../../../../../your-work).
|
|
|
|
> dica: o laser com o qual você trabalhará já está na sua pasta de ativos e referenciado pelo seu código.
|
|
|
|
- **Adicione detecção de colisões**, quando um laser colidir com algo, as seguintes regras devem ser aplicadas:
|
|
1. **Laser atinge inimigo**: o inimigo é destruído se for atingido por um laser.
|
|
2. **Laser atinge o topo da tela**: o laser é destruído se atingir a parte superior da tela.
|
|
3. **Colisão entre inimigo e herói**: o inimigo e o herói são destruídos se colidirem.
|
|
4. **Inimigo atinge a parte inferior da tela**: o inimigo e o herói são destruídos se o inimigo atingir a parte inferior da tela.
|
|
|
|
## Passos recomendados
|
|
|
|
Localize os arquivos que foram criados para você na subpasta `your-work`. Ela deve conter o seguinte:
|
|
|
|
```bash
|
|
-| assets
|
|
-| enemyShip.png
|
|
-| player.png
|
|
-| laserRed.png
|
|
-| index.html
|
|
-| app.js
|
|
-| package.json
|
|
```
|
|
|
|
Inicie seu projeto na pasta `your_work` digitando:
|
|
|
|
```bash
|
|
cd your-work
|
|
npm start
|
|
```
|
|
|
|
O comando acima iniciará um servidor HTTP no endereço `http://localhost:5000`. Abra um navegador e insira esse endereço. Neste momento, ele deve renderizar o herói e todos os inimigos, mas nada estará se movendo - ainda :).
|
|
|
|
### Adicione código
|
|
|
|
1. **Configure uma representação retangular do seu objeto do jogo para lidar com colisões**. O código abaixo permite obter uma representação retangular de um `GameObject`. Edite sua classe GameObject para estendê-la:
|
|
|
|
```javascript
|
|
rectFromGameObject() {
|
|
return {
|
|
top: this.y,
|
|
left: this.x,
|
|
bottom: this.y + this.height,
|
|
right: this.x + this.width,
|
|
};
|
|
}
|
|
```
|
|
|
|
2. **Adicione código que verifica colisões**. Esta será uma nova função que testa se dois retângulos se cruzam:
|
|
|
|
```javascript
|
|
function intersectRect(r1, r2) {
|
|
return !(
|
|
r2.left > r1.right ||
|
|
r2.right < r1.left ||
|
|
r2.top > r1.bottom ||
|
|
r2.bottom < r1.top
|
|
);
|
|
}
|
|
```
|
|
|
|
3. **Adicione a capacidade de disparar lasers**
|
|
1. **Adicione uma mensagem de evento de tecla**. A tecla *espaço* deve criar um laser logo acima da nave do herói. Adicione três constantes no objeto Messages:
|
|
|
|
```javascript
|
|
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
|
|
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
|
|
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
|
|
```
|
|
|
|
1. **Manipule a tecla espaço**. Edite a função `window.addEventListener` keyup para lidar com espaços:
|
|
|
|
```javascript
|
|
} else if(evt.keyCode === 32) {
|
|
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
|
|
}
|
|
```
|
|
|
|
1. **Adicione ouvintes**. Edite a função `initGame()` para garantir que o herói possa disparar quando a barra de espaço for pressionada:
|
|
|
|
```javascript
|
|
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
|
|
if (hero.canFire()) {
|
|
hero.fire();
|
|
}
|
|
```
|
|
|
|
e adicione uma nova função `eventEmitter.on()` para garantir o comportamento quando um inimigo colidir com um laser:
|
|
|
|
```javascript
|
|
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
|
|
first.dead = true;
|
|
second.dead = true;
|
|
})
|
|
```
|
|
|
|
1. **Mova o objeto**, Certifique-se de que o laser se mova gradualmente para o topo da tela. Você criará uma nova classe Laser que estende `GameObject`, como fez antes:
|
|
|
|
```javascript
|
|
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)
|
|
}
|
|
}
|
|
```
|
|
|
|
1. **Manipule colisões**, Implemente as regras de colisão para o laser. Adicione uma função `updateGameObjects()` que testa objetos colidindo para detectar impactos:
|
|
|
|
```javascript
|
|
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);
|
|
}
|
|
```
|
|
|
|
Certifique-se de adicionar `updateGameObjects()` ao seu loop do jogo em `window.onload`.
|
|
|
|
4. **Implemente o tempo de recarga** no laser, para que ele só possa ser disparado em intervalos específicos.
|
|
|
|
Por fim, edite a classe Hero para que ela possa lidar com o tempo de recarga:
|
|
|
|
```javascript
|
|
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, seu jogo já terá alguma funcionalidade! Você pode navegar com as teclas de seta, disparar um laser com a barra de espaço, e os inimigos desaparecem quando você os atinge. Muito bem!
|
|
|
|
---
|
|
|
|
## 🚀 Desafio
|
|
|
|
Adicione uma explosão! Dê uma olhada nos ativos do jogo no [repositório Space Art](../../../../6-space-game/solution/spaceArt/readme.txt) e tente adicionar uma explosão quando o laser atingir um alienígena.
|
|
|
|
## Questionário Pós-Aula
|
|
|
|
[Questionário pós-aula](https://ff-quizzes.netlify.app/web/quiz/36)
|
|
|
|
## Revisão e Autoestudo
|
|
|
|
Experimente os intervalos no seu jogo até agora. O que acontece quando você os altera? Leia mais sobre [eventos de temporização em JavaScript](https://www.freecodecamp.org/news/javascript-timing-events-settimeout-and-setinterval/).
|
|
|
|
## Tarefa
|
|
|
|
[Explore colisões](assignment.md)
|
|
|
|
---
|
|
|
|
**Aviso Legal**:
|
|
Este documento foi traduzido utilizando o serviço de tradução por IA [Co-op Translator](https://github.com/Azure/co-op-translator). Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automatizadas podem conter erros ou imprecisões. O documento original em seu idioma nativo deve ser considerado a fonte autoritativa. 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 equivocadas decorrentes do uso desta tradução. |