Quer aprender a criar um jogo em JavaScript do zero? Neste post, vamos guiá-lo na construção de um jogo de nave espacial utilizando HTML5 Canvas e JavaScript puro. Ao final, você terá uma nave que atira, enfrenta inimigos e reinicia o jogo com um simples toque na tecla “ENTER”.
Se preferir visualizar o jogo em ação antes de começar, confira nosso projeto no CodePen.
O que Você Vai Aprender
- Como usar a tag
<canvas>
para desenhar elementos do jogo. - Movimentar objetos na tela com eventos de teclado.
- Criar colisões entre objetos no jogo.
- Gerenciar estados como “Game Over” e reinício.
- Trabalhar com classes no JavaScript para organizar seu código.
Estrutura Inicial do Jogo
Vamos começar com o básico: criar o HTML e o CSS para exibir o jogo e configurar o espaço onde o jogo será renderizado.
HTML
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<title>Jogo em JavaScript</title>
<style>
body {
margin: 0;
overflow: hidden;
background-color: black;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, sans-serif;
}
canvas {
border: 2px solid white;
background-color: #111;
}
#pontuacao {
position: absolute;
top: 10px;
left: 10px;
color: white;
font-size: 20px;
}
</style>
</head>
<body>
<div id="pontuacao">Pontuação: 0</div>
<canvas id="tela-jogo" width="800" height="600"></canvas>
</body>
</html>
Neste código, criamos:
- A tag
<canvas>
para renderizar o jogo. - Um contador de pontuação para exibir os pontos acumulados.
- Um arquivo externo
jogo.js
onde a lógica do jogo será implementada.
CSS
Adicionamos estilos básicos para:
- Configurar um fundo escuro que simula o espaço.
- Centralizar o jogo na tela.
- Criar um visual limpo com bordas no canvas.
Implementando o Jogo com JavaScript
Agora, vamos criar a lógica do jogo no arquivo jogo.js
. Ele será responsável por movimentar a nave, criar inimigos e gerenciar os tiros.
JavaScript
A nave e os inimigos serão desenhados no canvas usando a API CanvasRenderingContext2D
.
// Obter elementos do DOM para uso no jogo
const tela = document.getElementById('tela-jogo');
const contexto = tela.getContext('2d');
const elementoPontuacao = document.getElementById('pontuacao');
// Dimensões e configurações iniciais do jogo
const LARGURA_JOGADOR = 40; // Largura do jogador
const ALTURA_JOGADOR = 40; // Altura do jogador
const LARGURA_INIMIGO = 40; // Largura dos inimigos
const ALTURA_INIMIGO = 40; // Altura dos inimigos
// Objeto principal do estado do jogo
const jogo = {
jogador: {
x: tela.width / 2 - LARGURA_JOGADOR / 2, // Posição inicial horizontal
y: tela.height - ALTURA_JOGADOR - 10, // Posição inicial vertical
velocidade: 5, // Velocidade do jogador
tiros: [], // Lista de tiros do jogador
pontuacao: 0 // Pontuação inicial
},
inimigos: [], // Lista de inimigos
teclas: {}, // Controle das teclas pressionadas
gameOver: false // Estado inicial do jogo
};
// Classe para gerenciar tiros (do jogador e dos inimigos)
class Tiro {
constructor(x, y, ehInimigo = false) {
this.x = x; // Posição horizontal do tiro
this.y = y; // Posição vertical do tiro
this.largura = 5; // Largura do tiro
this.altura = 10; // Altura do tiro
this.velocidade = ehInimigo ? 4 : -7; // Velocidade diferente para tiros inimigos e do jogador
this.ehInimigo = ehInimigo; // Define se é um tiro inimigo
}
// Desenha o tiro na tela
desenhar() {
contexto.fillStyle = this.ehInimigo ? 'red' : 'white';
contexto.fillRect(this.x, this.y, this.largura, this.altura);
}
// Atualiza a posição do tiro
mover() {
this.y += this.velocidade;
}
}
// Classe para gerenciar os inimigos
class Inimigo {
constructor() {
this.x = Math.random() * (tela.width - LARGURA_INIMIGO); // Posição inicial aleatória
this.y = 0; // Começa no topo da tela
this.velocidade = Math.random() * 2 + 1; // Velocidade aleatória
this.intervaloTiro = Math.random() * 100 + 50; // Intervalo aleatório para atirar
this.contadorTiro = 0; // Contador para controlar tiros
}
// Desenha o inimigo na tela
desenhar() {
contexto.fillStyle = 'red';
contexto.fillRect(this.x, this.y, LARGURA_INIMIGO, ALTURA_INIMIGO);
}
// Move o inimigo para baixo e atira periodicamente
mover() {
this.y += this.velocidade;
this.contadorTiro++;
if (this.contadorTiro >= this.intervaloTiro) {
this.atirar();
this.contadorTiro = 0;
}
}
// Faz o inimigo atirar
atirar() {
jogo.jogador.tiros.push(new Tiro(this.x + LARGURA_INIMIGO / 2, this.y + ALTURA_INIMIGO, true));
}
}
// Função para desenhar o jogador
function desenharJogador() {
contexto.fillStyle = 'blue';
contexto.fillRect(jogo.jogador.x, jogo.jogador.y, LARGURA_JOGADOR, ALTURA_JOGADOR);
}
// Move o jogador com base nas teclas pressionadas (somente se o jogo não estiver em game over)
function moverJogador() {
if (jogo.gameOver) return; // Impede movimentação em Game Over
if ((jogo.teclas['ArrowLeft'] || jogo.teclas['a']) && jogo.jogador.x > 0) {
jogo.jogador.x -= jogo.jogador.velocidade;
}
if ((jogo.teclas['ArrowRight'] || jogo.teclas['d']) && jogo.jogador.x < tela.width - LARGURA_JOGADOR) {
jogo.jogador.x += jogo.jogador.velocidade;
}
if ((jogo.teclas['ArrowUp'] || jogo.teclas['w']) && jogo.jogador.y > 0) {
jogo.jogador.y -= jogo.jogador.velocidade;
}
if ((jogo.teclas['ArrowDown'] || jogo.teclas['s']) && jogo.jogador.y < tela.height - ALTURA_JOGADOR) {
jogo.jogador.y += jogo.jogador.velocidade;
}
}
// O jogador atira ao pressionar espaço (somente se não estiver em game over)
function atirar() {
if (!jogo.gameOver) {
jogo.jogador.tiros.push(new Tiro(jogo.jogador.x + LARGURA_JOGADOR / 2, jogo.jogador.y));
}
}
// Atualiza a posição dos tiros e verifica colisões
function atualizarTiros() {
jogo.jogador.tiros = jogo.jogador.tiros.filter(tiro => {
tiro.mover();
// Colisão com inimigos
if (!tiro.ehInimigo) {
jogo.inimigos = jogo.inimigos.filter(inimigo => {
if (verificarColisao(tiro, inimigo)) {
jogo.jogador.pontuacao += 10;
elementoPontuacao.textContent = `Pontuação: ${jogo.jogador.pontuacao}`;
return false;
}
return true;
});
}
// Colisão de tiros inimigos com o jogador
if (!jogo.gameOver && tiro.ehInimigo && verificarColisao(tiro, jogo.jogador)) {
jogo.gameOver = true; // Ativa o estado de Game Over
}
return tiro.y > 0 && tiro.y < tela.height;
});
}
// Verifica colisão entre dois objetos retangulares
function verificarColisao(objeto1, objeto2) {
return !(objeto1.x > objeto2.x + LARGURA_INIMIGO ||
objeto1.x + objeto1.largura < objeto2.x ||
objeto1.y > objeto2.y + ALTURA_INIMIGO ||
objeto1.y + objeto1.altura < objeto2.y);
}
// Atualiza a posição dos inimigos
function atualizarInimigos() {
if (!jogo.gameOver) {
if (Math.random() < 0.02) {
jogo.inimigos.push(new Inimigo());
}
jogo.inimigos = jogo.inimigos.filter(inimigo => {
inimigo.mover();
return inimigo.y <= tela.height;
});
}
}
// Desenha todos os elementos na tela
function desenhar() {
contexto.clearRect(0, 0, tela.width, tela.height);
desenharJogador();
jogo.jogador.tiros.forEach(tiro => tiro.desenhar());
jogo.inimigos.forEach(inimigo => inimigo.desenhar());
// Exibe a mensagem de game over se o jogo acabou
if (jogo.gameOver) {
contexto.fillStyle = 'rgba(0, 0, 0, 0.5)';
contexto.fillRect(0, 0, tela.width, tela.height);
contexto.fillStyle = 'red';
contexto.font = '48px Arial';
contexto.textAlign = 'center';
contexto.fillText('GAME OVER', tela.width / 2, tela.height / 2);
contexto.font = '20px Arial';
contexto.fillText(`Pontuação: ${jogo.jogador.pontuacao}`, tela.width / 2, tela.height / 2 + 40);
contexto.fillText('Pressione ENTER para reiniciar', tela.width / 2, tela.height / 2 + 80);
}
}
// Ciclo principal do jogo
function loopJogo() {
moverJogador();
atualizarTiros();
atualizarInimigos();
desenhar();
requestAnimationFrame(loopJogo);
}
// Reinicia o estado do jogo
function reiniciarJogo() {
jogo.jogador.x = tela.width / 2 - LARGURA_JOGADOR / 2;
jogo.jogador.y = tela.height - ALTURA_JOGADOR - 10;
jogo.jogador.tiros = [];
jogo.inimigos = [];
jogo.jogador.pontuacao = 0;
elementoPontuacao.textContent = 'Pontuação: 0';
jogo.gameOver = false; // Desativa o estado de Game Over
}
// Eventos para controle do teclado
window.addEventListener('keydown', (evento) => {
jogo.teclas[evento.key] = true;
if (evento.key === 'Enter' && jogo.gameOver) reiniciarJogo(); // Reinicia o jogo com ENTER
if (evento.key === ' ' && !jogo.gameOver) atirar(); // Atira com ESPAÇO
});
window.addEventListener('keyup', (evento) => {
jogo.teclas[evento.key] = false;
});
// Inicia o loop principal
loopJogo();
Conclusão do Jogo em JavaScript
Com os passos acima, você terá um jogo funcional. Todo o código do jogo foi projetado para ser simples, mas com várias possibilidades de expansão, como incluir power-ups ou diferentes níveis de dificuldade.
Para ver o jogo completo em ação, acesse nosso CodePen com o jogo.
Gostou do tutorial? Deixe sua opinião nos comentários e compartilhe seu progresso com a gente!