Jogo Quebra Blocos - Jogo de quebra-cabeças blocos – Apps no Google Play
Jogo de quebra-cabeças blocos – Apps no Google Play

Como construir um jogo quebra blocos do zero em JavaScript puro

A maioria dos tutoriais de breakout que você encontra na internet segue uma lógica simples: canvas, bola, raquete e blocos. O problema é que, quando você sai desse tutorial básico e tenta adicionar funcionalidades reais — combo, power-ups, níveis procedurais — o código vira um monte de estado espalhado sem estrutura. Eu passei uns meses refatorando meu próprio clone depois de perceber que meu jogo travava em certas telas porque eu estava atualizando a bola antes de verificar a colisão com os blocos remanescentes. Vamos colocar a lógica certa primeiro e a definição depois, porque entender a ordem de atualização é o que separa um jogo que funciona de um que dá bobeira nos frames finais.

Entendendo a ordem de atualização do jogo quebra blocos

O loop de jogo precisa rodar nesta sequência exata a cada frame: atualizar posição da bola, detectar colisões, mover a raquete, renderizar. Parece óbvio, mas esqueci disso na primeira versão e tinha momentos em que a bola passava direto por um bloco porque a verificação de colisão acontecia após a bola já ter saído da área de interseção. A correção foi inserir uma sub-divisão de física — dividir cada frame em quatro sub-steps de movimento — o que garantiu que a bola nunca atravessasse nenhum objeto, mesmo em velocidades altas. Basicamente, o coração do jogo quebra blocos é esse loop de atualização. Sem essa base limpa, todo o resto quebra.

Você vai precisar de um canvas HTML5 e de uma estrutura mínima com estas variáveis de estado: posição X e Y da bola, velocidade X e Y, posição X da raquete, largura e altura da raquete, array de blocos com propriedade de colisão, e pontuação. Nada além disso no início.

Implementação prática passo a passo

Comece pelo canvas. Um elemento simples dentro do HTML, com dimensões de 480 por 640 pixels, já serve para começar. Configure o contexto 2D e defina constantes no topo do arquivo para não acabar repetendo números mágicos pelo código — isso economiza tempo de manutenção e evita aquele erro clássico de alterar a largura do canvas e esquecer de atualizar os limites de colisão em cinco lugares diferentes. Defina a raquete como um retângulo simples. Comece com largura de 80 pixels, altura de 12, e posicione-a no fundo do canvas. Os controles são apenas setas esquerda e direita com movimentação a 7 pixels por frame. Tecla espaço dispara a bola. Simples.

Para os blocos, organize-os em linhas e colunas. Cada bloco precisa ter propriedades de visibilidade e retângulo de colisão. Eu costumo criar uma classe Bloco com os métodos update e draw, mas em versões simples um objeto com posX, posY, width, height, e active basta. A grade padrão é oito colunas por cinco linhas, com espaçamento de cinco pixels entre eles. A bola é onde a maioria das pessoas erra. Velocidade inicial de 4 pixels por frame nos eixos X e Y, com ângulo aleatório entre 30 e 60 graus na saída. O segredo é calcular deltaX e deltaY usando seno e cosseno do ângulo em vez de hardcodar valores. Quando a bola bate nas paredes laterais, inverte deltaX. No teto, inverte deltaY. Na raquete, inverte deltaY e adiciona um offset baseado em onde a bola tocou a raquete — centro é reflexão reta, bordas geram ângulos mais amplos. Isso é o que faz o jogo parecer responsivo e justo.

👉 Clique no botão abaixo para saber mais sobre o assunto!

O ponto mais importante e menos óbvio: colisão com blocos. Use AABB (Axis-Aligned Bounding Box) para detectar sobreposição, mas determine em qual face o contato ocorreu. Se a bola entrou pela lateral do bloco, inverta deltaX. Se entrou por cima ou por baixo, inverta deltaY. Testar apenas a posição anterior vs. nova não funciona bem quando a bola está rápida — é aí que entra a sub-divisão de que eu falei. Quatro sub-steps por frame eliminam a maior parte dos bugs de atravessamento. Eu tive um problema específico com blocos que se destruím e faziam a bola mudar de velocidade abruptamente. O jogo travava porque a velocidade final ultrapassava um limite não declarado e a bola ia para coordenadas irreais. A correção foi limitar a velocidade máxima a 8 pixels por sub-step e normalizar o vetor de velocidade a cada frame para manter a velocidade escalar constante, variando apenas a direção.

Power-ups e progressão de níveis

Depois que o loop básico está funcionando, os power-ups entram como objetos soltos que caem verticalmente. Os três mais comuns: alargador da raquete, bola multiplas e raquete magnética. O alargador é o mais fácil de implementar — apenas ajusta a largura da raquete temporariamente. Bola múltipla exige clonar o vetor de velocidade da bola principal em duas ou três direções diferentes. Já a raquete magnética requer repensar o sistema de controle: em vez de posicionar a raquete pelo mouse ou teclado, ela segue a posição X da bola com um atraso calculado por interpolação linear. Para progressão de níveis, o método mais eficiente é definir um array de configurações de nível, cada um com diferentes disposições de blocos, cores e tipos. Blocos indestrutíveis (que precisam de dois ou três hits), blocos que soltam power-ups, e blocos com pontuações diferentes. Um gerador procedural simples pode criar grades simétricas com padrões reconhecíveis — cruzes, degradês, linhas diagonais.

Aqui vai uma coisa que ninguém conta: usar um sistema de grid baseado em matricial para os blocos é muito mais flexível do que posicionar cada bloco manualmente. Basta definir uma matriz 2D onde 1 é bloco sólido, 0 é vazio, 2 é bloco reforçado, e converter isso em objetos no início do nível. Isso reduz o tempo de criação de níveis de algo como uma hora de posicionamento manual para cerca de dez minutos de configuração da matriz.

Limitações e problemas conhecidos

O modelo AABB com sub-divisão funciona bem para a maioria dos casos, mas tem um ponto fraco: colisão diagonal precisa. Quando a bola atinge o canto exato entre dois blocos adjacentes, a determinação da face de colisão fica ambígua e o jogo pode invertes a direção errada. A solução prática é adicionar uma tolerância de quelques pixels na verificação de proximidade dos cantos, forçando a priorização da face mais próxima. Também considere usar círculos para a bola em vez de um ponto matemático, porque isso resolve muitos problemas de colisão nos cantos dos blocos naturalmente. Outro problema real: performance em dispositivos móveis. Canvas 2D com dezenas de blocos ativos e múltiplas bolas roda bem em desktop, mas em celulares mais antigos começa a cair para menos de 30 FPS. Se o público-alvo inclui dispositivos móveis, considere usar WebGL via bibliotecas como Three.js ou PixiJS, ou então limitar o número máximo de objetos ativos na tela simultaneamente. Eu cortei o número máximo de blocos simultâneos para 25 por nível em uma versão mobile e a performance melhorou drasticamente sem impacto perceptível na jogabilidade.

Se você quer algo mais rápido para prototipar, engines como Phaser ou Godot já vêm com físicos de colisão otimizados e eliminam toda essa complexidade manual. Mas se o objetivo é aprender como o jogo funciona por dentro — e isso vale bastante para quem quer entrar na área de desenvolvimento de jogos — fazer do zero em JavaScript puro é o caminho certo.

O que realmente diferencia um jogo quebra blocos bom de um mediano

Não é a quantidade de blocos oupower-ups. É a sensação de controle. O jogador precisa sentir que cada rebatida da bola é consequência direta de onde ela tocou na raquete, e não de uma física caprichosa. Isso significa calibrar a velocidade inicial, o coeficiente de restituição e o mapeamento entre posição de toque na raquete e ângulo de saída. Teste com valores diferentes e peça para alguém jogar sem explicar as regras. Se a pessoa consegue intuir como controlar a bola apenas experimentando, você acertou o balanceamento. O código fonte completo com todos os exemplos mencionados aqui pode ser encontrado em repositórios abertos no GitHub. Procure por "breakout game javascript canvas" — existem várias implementações maduras que servem como base sólida para estudo e modificação.