Esse site utiliza cookies
Nós armazenamos dados temporariamente para melhorar a sua experiência de navegação e recomendar conteúdo do seu interesse.
Ao utilizar os nossos serviços, você concorda com as nossas políticas de privacidade.
Esse site utiliza cookies
Nós armazenamos dados temporariamente para melhorar a sua experiência de navegação e recomendar conteúdo do seu interesse.
Ao utilizar os nossos serviços, você concorda com as nossas políticas de privacidade.
Postado em 16 dezembro 2022
Atualizado em 16 dezembro 2022
O jogo de quebra-blocos (block breaker) é um jogo clássico e de fácil desenvolvimento. Este projeto aborda sobre conceitos extremamente básicos na programação de jogos, como por exemplo conflito e inversão de direção.
O usuário controla um bloco que fica localizado na parte inferior da tela, podendo apenas movimentar no sentido horizontal. O objetivo do jogador é quebrar os blocos na parte superior da tela com uma bola que fica em constante movimento, cuidando para que ela não escape dos limites da tela na parte de baixo.
Esse projeto mostra como desenvolver um jogo de quebra-blocos do modo mais simples possível. Nesse tutorial não terá tela de início, tela de fim e nem recursos gráficos para enfeitar. Serão apenas aplicados a lógico do jogo.
Esse projeto será desenvolvido em javascript e estará utilizando a biblioteca p5.js. O editor utilizado será o editor disponibilizado pela p5.js que também é utilizado no tutorial de jogo da serpente postado no nosso site.
O jogo será desenvolvido levando em consideração os seguintes valores:
Constante | Valor |
---|---|
Altura da tela | 500 |
Largura da tela | 700 |
Altura de cada bloco | 20 |
Largura de cada bloco | 100 |
Diâmetro da bola | 15 |
Como dito anteriormente, o jogo não terá tela de início e nem tela de começo. Mesmo quando todos os blocos tiverem sido destruídos o jogo continua rodando até que a bola escape pela parte inferior da tela.
O primeiro e único estágio será simples, possuindo cinco blocos em quatro linhas. A imagem abaixo descreve melhor.
Quando a bola bater nos limites da tela, ela será redirecionada para o lado oposto que ela seguia antes. Porém, caso a bola chegue no limite da tela na parte inferior da tela, ela não será redirecionada e o jogo irá parar de rodar.
Essa lógica funciona da mesma forma quando a bola entra em colisão com algum bloco, seja do usuário ou seja um obstáculo. Porém, ao bater nas extremidades do bloco a bola volta pelo mesmo rumo que ela venho, como mostra a imagem abaixo:
As constantes serão as variáveis com o valor fixo durante o jogo inteiro. O valor das constantes será igual aos valores mostrados na etapa de planejamento.
// Tamanho padrão da tela
const screenWidth = 700;
const screenHeight = 500;
// Tamanho padrão do bloco
const blockDefaultWidth = 100;
const blockDefaultHeight = 20;
// Tamanho padrão da bola
const defaultBallSize = 15;
As constantes acima são importantíssimas e servem de base para os cálculos que serão feitos durante o jogo.
A classe “Block” será utilizada para o uso do usuário e dos obstáculos, possuindo apenas uma função com o objetivo de exibir o bloco na tela.
class Block {
constructor(x,y, width, height, color = {
red: 255,
green: 255,
blue: 255
}) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color
}
draw() {
fill(this.color.red, this.color.green, this.color.blue);
rect(this.x, this.y, this.width, this.height);
}
}
Como mostra no construtor da classe, a cor padrão do bloco será branca, a menos que sua cor seja definida.
Os atributos da bola são as coordenadas, diâmetro, velocidade, direção e ativação. A variável de ativação (isBoucing) é responsável por gerenciar se a bola está ou não em movimento.
Outra variável importantíssima é a de direção. A variável de direção é um objeto contendo o valor atual da direção horizontal e vertical da bola.
class Ball {
constructor(x, y, diameter = defaultBallSize) {
this.x = x;
this.y = y;
this.diameter = diameter;
this.isBouncing = false;
this.speed = 5;
this.direction = {
x: -1,
y: -1
};
}
}
A lógica da direção da bola é a seguinte:
Coordenada | Valor | Efeito |
---|---|---|
x | -1 | Movimento horizontal para a esquerda |
x | 1 | Movimento horizontal para a direita |
y | -1 | Movimento vertical para cima |
y | 1 | Movimento vertical para baixo |
Ao todo a bola possui três funções declaradas:
A função de exibição da bola é extremamente simples.
draw() {
// Faz com que a cor da bola seja constatemente branca
fill(255, 255, 255);
// Desenha a bola na tela
circle(this.x, this.y, this.diameter);
return this.bounce();
}
Diferentemente de outras funções de exibição, a bola retorna um valor boolean informando se a bola colidiu com os limites da tela.
A função de movimento também é bastante simples e é invocada na função de exibição da bola. Enquanto a bola estiver ativada com o atributo “isBouncing”, ela estará em movimento. Caso contrário apenas um boolean com valor “true” será retornado.
bounce() {
// Checa se a bola está em movimento
if (this.isBoucing) {
// Movimento a bola baseando-se na direção atual
this.y += this.speed * this.direction.y;
this.x += this.speed * this.direction.x;
return this.rebounce();
}
return true;
}
É possível observar acima que a movimentação da bola é bastante dependente da direção atual da bola.
Responsável por redirecionar a bola quando ela colide com os limites da tela. A função de redirecionamento também checa se a bola saiu dos limites pelo lado inferior da tela.
rebounce() {
// Limite da tela na parte superior
if (this.y - (this.diameter / 2) <= 0) {
// Muda a direção da bola para baixo
this.direction.y = 1;
}
// Limite da tela na parte inferior
if (this.y + (this.diameter / 2) > screenHeight) {
return false;
}
// Limite da tela nos lados esquerdo e direito
if (this.x - (this.diameter / 2) <= 0 ||
this.x + (this.diameter / 2) >= screenWidth) {
this.direction.x *= -1;
}
return true;
}
Ao retornar o valor boolean “false”, a função avisa se a bola saiu dos limites que causam o fim do jogo.
Na função “setup” do p5.js definimos valores que só precisam ser declarados uma vez durante o jogo. Nesse jogo queremos inicializar as seguintes variáveis:
A declaração da tela de exibição será pulada por ser uma função padrão do ps5.js.
O bloco do usuário deverá começar aparecendo no centro da tela no sentido horizontal e no fundo da parte inferior da tela no sentido vertical.
setup() {
let coordenadaX = (screenWidth / 2) - (blockDefaultWidth / 2);
let coordenadaY = screenHeight - blockDefaultHeight;
playerBlock = new Block(coordenadaX, coordenadaY, blockDefaultWidth, blockDefaultHeight);
}
O valor da coordenada x deve resultar no resultado abaixo:
Se a largura do bloco tem um valor de 100, isso significa que a extremidade da direita do bloco irá ficar localizada na coordenada x com valor de 400.
A bola deve ficar situada encima do bloco do usuário de modo centralizado.
var balls = [];
...
setup() {
let ball = new Ball(playerBlock.x + (playerBlock.width / 2), playerBlock.y - (defaultBallSize / 2));
balls.push(ball);
}
Como mostra no jogo da serpente, o tamanho da bola é medido apenas por um ponto, o centro do círculo.
O valor da coordenada x da bola é igual ao valor da coordenada x do bloco do usuário somado com a metade da largura do mesmo.
Os blocos que o usuário deve quebrar usando a bola devem estar situados na parte superior da tela. Quando a bola entrar em contato com o bloco, o bloco desaparecerá e a bola será redirecionada para o lado contrário. Caso a bola entre em contato com umas das extremidades do bloco ela irá ser redirecionada pelo mesmo rumo que venho.
Para facilitar o mapeamento das coordenadas de cada bloco, utilizaremos um arranjo (array) multidimensional contendo zeros e uns. Os zeros representam ausência e os uns representam a presença de blocos.
var stageOne = [
[0,0,0,0,0,0,0],
[0,1,1,1,1,1,0],
[0,1,1,1,1,1,0],
[0,1,1,1,1,1,0],
[0,1,1,1,1,1,0]
];
Levando em consideração as constantes de altura e comprimento da tela que são respectivamente 500 e 700, podemos confirmar que cabem 7 blocos no sentido horizontal. Essa conclusão vem do cálculo do comprimento da tela pelo comprimento do bloco que é 100.
Em seguida, ainda na função “setup” declaramos as instâncias de cada bloco e armazenamos em um array.
var blocks = [];
...
for (let i = 0; i < stageOne.length; i++) {
for (let j = 0; j <= stageOne.length; j++) {
if (stageOne[i][j] == 1) {
let x = j * blockDefaultWidth;
let y = i * blockDefaultHeight;
let block = new Block(x, y, blockDefaultWidth, blockDefaultHeight, greenColor);
blocks.push(block);
}
}
}
Pelo fato da variável “stageOne” ser multidimensional, temos uma repetição dentro da outra, como mostra no código acima. A ordem da criação dos blocos pode ser representada com a tabela abaixo:
a | b | c | d | e | f | g |
---|---|---|---|---|---|---|
X | X | X | X | X | X | X |
X | 01 | 02 | 03 | 04 | 05 | X |
X | 06 | 07 | 08 | 09 | 10 | X |
X | 11 | 12 | 13 | 14 | 15 | X |
X | 16 | 17 | 18 | 19 | 20 | X |
A letra “X” representa os espaços ausentes de blocos.
O conteúdo declarado dentro da função “draw” é repetido infinitamente até que o usuário interrompa fechando a aba ou o navegador.
O bloco do usuário pode ser exibido com uma única linha de código:
playerBlock.draw();
Lembrando que a instância do “playerBlock” foi inicializada na função de inicialização. Outras instâncias que foram inicializadas e serão utilizada nessa função são:
Enquanto a existir uma bola visível na tela, o jogo irá ter continuidade e o usuário poderá mover o seu bloco. Caso contrário, o jogo para e deve ser reinicializado.
Pensando de uma forma mais ao longo prazo, como possibilidade de múltiplas bolas visíveis na tela, as instâncias de cada bola será armazenada em um array. Porém, nesse projeto só uma bola estará presente.
for(let i = 0; i < balls.length; i++) {
let isBallStillShowing = balls[i].draw();
if (! isBallStillShowing) {
balls.splice(i, 1);
continue;
}
...
}
O código acima checa se a bola ainda está visível na tela. Caso a bola esteja fora da visibilidade da tela, ela será eliminada do array.
O usuário pode apenas se movimentar para esquerda e direita. Porém, a movimentação do bloco só pode ser dentro da visibilidade da tela.
...
if (keyIsDown(LEFT_ARROW) && (playerBlock.x >= 0)) {
playerBlock.x -= 5;
if (! balls[i].isBoucing) {
balls[i].x -= 5;
}
}
if (keyIsDown(RIGHT_ARROW) && (playerBlock.x + playerBlock.width <= screenWidth)) {
playerBlock.x += 5;
if (! balls[i].isBoucing) {
balls[i].x += 5;
}
}
No começo do jogo o usuário tem que pressionar a tecla espaço para que a bola comece a se movimentar. Caso ela não esteja em movimento e o usuário mova o bloco, a bola irá acompanhar o bloco do usuário.
...
if (keyIsDown(32) && !balls[i].isBoucing) {
balls[i].isBoucing = true;
}
Ao apertar a tecla espaço que tem um valor de 32 como mostra no código acima, a bola começa a entrar em movimento.
Ao entrar em contato com o bloco do usuário a direção vertical da bola muda sem excessões.
// Collision
if (balls[i].y + (balls[i].diameter / 2) >= playerBlock.y &&
balls[i].x + (balls[i].diameter / 2) >= playerBlock.x &&
balls[i].x - (balls[i].diameter / 2) <= playerBlock.x + playerBlock.width) {
balls[i].direction.y = -1;
balls[i].direction = calculateDirectionBouncing(playerBlock, balls[i]);
}
Entretanto, caso a bola acabe entrando em contato com uma das extremidades do bloco do usuário, ela irá voltar pelo mesmo caminho que venho. A função responsável por calcular a direção da bola em colisão com a extremidade é a “calculateDirectionBouncing”.
function calculateDirectionBouncing(block, ball) {
let blockRightSide = block.width * (9 / 10);
let blockLeftSide = block.width * (1 / 10);
let direction = ball.direction;
if (ball.x > blockRightSide + block.x) {
direction.x = 1;
}
if (ball.x < blockLeftSide + block.x) {
direction.x = -1;
}
return direction;
}
O bloco será divido em 10 pequenos pedaços. O primeiro bloco e último bloco da esquerda ou da direita serão considerados as extremidades. Ao entrar em contato com uma dessas extremidades o valor da direção horizontal é forçado para uma determinada direção.
Uma vez que o mapeamento já foi feito na fase de inicialização, só precisamos exibir na função “draw”. Aproveitando o loop para exibir os blocos, checamos por colisões com a bola.
var i = blocks.length;
while (i--) {
blocks[i].draw();
for(let j = 0; j < balls.length; j++) {
if (balls[j].x + (balls[j].diameter / 2) >= blocks[i].x &&
balls[j].x - (balls[j].diameter / 2) <= blocks[i].x + blocks[i].width &&
balls[j].y + (balls[j].diameter / 2) >= blocks[i].y &&
balls[j].y - (balls[j].diameter / 2) <= blocks[i].y + blocks[i].height) {
balls[j].direction = calculateDirectionBouncing(blocks[i], balls[j]);
balls[j].direction.y *= -1;
// Remove bloco colidido
blocks.splice(i, 1);
}
}
}
O motivo para usarmos “while” invés do “for” é pelo fato de que estamos removendo os blocos do loop quando detectamos uma colisão. Se usássemos “for”, isso poderia trazer comportamentos inesperados dos blocos.
Uma vez que chegamos até aqui, o jogo ja estará pronto.
O código do jogo pode ser acessado por esse link. O jogo ainda precisa de muitas melhorias, porém está satisfatório.
O jogo de quebra-blocos mostra como é importante o planejamento do jogo antes de seu desenvolvimento. Ao deixarmos a altura dos blocos inferior ao diâmetro da bola, teremos comportamentos inesperados como múltiplos blocos atingidos com uma única colisão. Esse tipo de comportamento pode passar despercebido na fase de planejamento, porém deverá ser revelado na fase de testes. Escrever um código flexível e ajustável usando cálculos de matemática torna a modificação de valores como constantes mais conveniente.
Um exemplo a não seguir nesse projeto é o mapa dos blocos armazenados na variável “stageOne”. Caso modificarmos constantes como o tamanho da tela ou comprimento do bloco, anomalias podem acontecer, como por exemplo, blocos não centralizados e no pior dos casos blocos escapando dos limites de visibilidade da tela.
Postagens mais vistas
Os 5 principais componentes do computador são a unidade de controle, unidade aritmética e lógica, memória, dispositivo de entrada e dispositivo de saída.
A porta é um número de 16 bits que é adicionado no final do endereço IP, insinuando qual aplicativo está vinculado e atuando nessa porta.
Rede local de computadores (LAN) é um conjunto de computadores ou dispositivos conectados uns aos outros de forma isolada em um pequeno local.