Criando um jogo de Arqueiro em Javascript
O “Jogo do Arqueiro” é um jogo de física onde o objetivo é atirar flechas para acertar alvos, utilizando a biblioteca Matter.js para simular a física realista.
Conceitos Fundamentais
O Matter.js é uma biblioteca JavaScript que permite criar simulações físicas realistas em 2D. Com ela você pode:
Criar corpos com propriedades físicas (peso, atrito, elasticidade)
Simular colisões e interações entre objetos
Desenvolver jogos e experiências interativas
Exemplo Prático: Arqueiro Épico
Aqui está o jogo que usaremos como referência:
Passo 1: Configurando o ambiente
Antes de começarmos a programar, certifique-se de ter um ambiente de desenvolvimento configurado:
Um editor de código (VS Code)
- Um navegador (Google Chrome)
Arquivo de template
Passo 2: Classe PlayerArcher() (arquivo acher.js)
A classe PlayerArcher
controla o arqueiro do jogo, incluindo sua física, aparência e movimentos.
1. Criando as propriedades do Arqueiro
class PlayerArcher {
constructor(x, y, width, height) {
// 1. Configurações Físicas
const options = {
isStatic: true // Define que o corpo não será afetado por forças físicas
}
// 2. Criação do Corpo Físico
this.body = Matter.Bodies.rectangle(x, y, width, height, options);
// 3. Propriedades do Arqueiro
this.width = width; // Armazena a largura do arqueiro
this.height = height; // Armazena a altura do arqueiro
this.collapse = false; // Flag para estado de colisão/destruição
this.image = loadImage("./assets/playerArcher.png"); // Carrega o sprite
// 4. Adição ao Mundo Físico
World.add(world, this.body);
// 5. Configuração Angular Inicial
Matter.Body.setAngle(this.body, -90); // Aponta para cima (-90 graus)
isStatic: true
: Torna o corpo imóvelMatter.Bodies.rectangle()
: Cria um corpo retangular no Matter.jswidth/height
: Armazenam as dimensões para renderização precisacollapse
: Flag booleana para controle de estado (útil para game over)image
: Carrega o arquivo PNG do assets (deve estar pré-carregado)World.add()
: Insere o corpo na simulação física globalsetAngle(-90)
: Rotaciona o corpo para apontar verticalmente para cima
2. Criando o sistema de Controle
// 6. Controle de Movimento (Para Baixo)
if (keyIsDown(DOWN_ARROW) && this.body.angle < -73 ) {
this.body.angle += 1; // Incrementa o ângulo
Matter.Body.setAngle(this.body, this.body.angle); // Aplica a mudança
}
// 7. Controle de Movimento (Para Cima)
if (keyIsDown(UP_ARROW) && this.body.angle > -103) {
this.body.angle -=1; // Decrementa o ângulo
Matter.Body.setAngle(this.body, this.body.angle); // Aplica a mudança
}
Limites angulares (-73° e -103°) criam um arco de movimento de 30°
keyIsDown()
verifica pressionamento contínuo das teclasIncrementos/decrementos de 1° garantem movimento suave
setAngle()
aplica as mudanças ao corpo físico
3. Renderização Gráfica
display() {
// 6. Controle de Movimento (Para Baixo)
if (keyIsDown(DOWN_ARROW) && this.body.angle < -73 ) {
this.body.angle += 1; // Incrementa o ângulo
Matter.Body.setAngle(this.body, this.body.angle); // Aplica a mudança
}
// 7. Controle de Movimento (Para Cima)
if (keyIsDown(UP_ARROW) && this.body.angle > -103) {
this.body.angle -=1; // Decrementa o ângulo
Matter.Body.setAngle(this.body, this.body.angle); // Aplica a mudança
}
// 8. Transformações Gráficas
push(); // Salva o estado atual do sistema de coordenadas
translate(this.body.position.x, this.body.position.y); // Move origem para a posição do corpo
rotate(this.body.angle); // Rotaciona o sistema de coordenadas
imageMode(CENTER); // Define o ponto de ancoragem da imagem como centro
image(this.image, 0, 0, this.width, this.height); // Desenha a imagem
pop(); // Restaura o estado anterior do sistema de coordenadas
}
}
push()/pop()
: Isolam transformações gráficastranslate()
: Move a origem para a posição do arqueirorotate()
: Aplica a rotação física ao sistema de coordenadasimageMode(CENTER)
: Faz a imagem girar em torno do seu centroimage()
: Desenha o sprite com dimensões armazenadas
Passo 3: Classe PlayerArrow() (arquivo PlayerArrow.js)
Vamos criar a flecha que o arqueiro irá atirar. A flecha começa estática (parada) e, quando disparada, ganha velocidade e segue uma trajetória física realista.
1. Criando as propriedades da Flecha
class PlayerArrow {
constructor(x, y, width, height) {
// Configurações físicas da flecha
var options = {
isStatic: true, // Inicia parada até ser disparada
density: 0.1 // Define leveza para um movimento mais fluido
};
this.width = width; // Largura da flecha
this.height = height; // Altura da flecha
// Cria o corpo físico da flecha usando a biblioteca Matter.js
this.body = Bodies.rectangle(x, y, this.width, this.height, options);
// Carrega a imagem da flecha
this.image = loadImage("./assets/arrow.png");
// Adiciona a flecha ao "mundo" físico do jogo
World.add(world, this.body);
}
isStatic: true
: Torna o corpo imóveldensity: 0.1
define uma densidade baixa para um movimento mais suave.Matter.Bodies.rectangle()
: Cria um corpo retangular no Matter.jswidth/height
: Armazenam as dimensões para renderização precisaimage
: Carrega o arquivo PNG do assets (deve estar pré-carregado)World.add()
: Insere o corpo na simulação física global
2. Método para disparar a flecha
// Método para disparar a flecha
shoot(archerAngle) {
// Cria um vetor de velocidade baseado no ângulo do arqueiro
var velocity = p5.Vector.fromAngle(archerAngle);
velocity.mult(20); // Ajusta a força do disparo
// Torna a flecha dinâmica (não estática) para que a física atue
Matter.Body.setStatic(this.body, false);
// Aplica a velocidade ao corpo da flecha
Matter.Body.setVelocity(this.body, { x: velocity.x, y: velocity.y });
}
p5.Vector.fromAngle()
converte o ângulo em um vetor de direção.velocity.mult(20)
define a força do disparo (quanto maior, mais rápida a flecha).Matter.Body.setStatic(this.body, false)
permite que a flecha se mova.Matter.Body.setVelocity()
aplica a velocidade ao corpo físico.
3. Renderização Gráfica
// Método para desenhar a flecha na tela
display() {
push(); // Salva as configurações atuais do canvas
translate(this.body.position.x, this.body.position.y); // Move o sistema de coordenadas para a posição da flecha
rotate(this.body.angle); // Rotaciona conforme a direção do movimento
imageMode(CENTER);
image(this.image, 0, 0, this.width, this.height); // Desenha a flecha
pop(); // Restaura as configurações anteriores
}
}
push()/pop()
: Isolam transformações gráficastranslate()
: Move a origem para a posição do arqueirorotate()
: Aplica a rotação física ao sistema de coordenadasimageMode(CENTER)
: Faz a imagem girar em torno do seu centroimage()
: Desenha o sprite com dimensões armazenadas
Passo 4: Declaração de variáveis
Aqui estamos declarando as variáveis que serão usadas para armazenar os módulos do Matter.js e os corpos físicos.
// Módulos do Matter.js
const Engine = Matter.Engine;
const World = Matter.World;
const Bodies = Matter.Bodies;
const Constraint = Matter.Constraint;
// Variáveis do jogo
var engine, world;
var canvas;
var player, playerBase, playerArcher;
var arrow
Engine
: O “cérebro” que calcula todas as interações físicasWorld
: O “container” onde todos os objetos físicos existemBody
: Permite manipular objetos após a criação (ex.: girar paredes)Constraint
: Permite criar restrições e conexões entre corpos físicos
Passo 5: Função preload()
A função preload() é usada para carregar imagens e animações antes do jogo iniciar.
loadImage("./assets/background.png")
: Carrega a imagem de fundo do cenárioloadImage("./assets/base.png")
: Carrega a imagem da base do personagemloadImage("./assets/player.png")
: Carrega a imagem do personagem arqueiro
function preload() {
backgroundImg = loadImage("./assets/background.png");
baseimage = loadImage("./assets/base.png");
playerimage = loadImage("./assets/player.png");
}
Passo 6: Função setup()
A função setup()
é executada apenas uma vez e configura o ambiente do jogo.
createCanvas(windowWidth, windowHeight)
: Cria uma tela que ocupa toda a janela do navegador
function setup(){
createCanvas(windowWidth, windowHeight)
Agora criamos o motor de física:
// Cria o motor de física
engine = Engine.create();
world = engine.world;
Engine.create()
: Inicializa o motor de físicaengine.world
: Acessa o mundo físico criado
E criamos os corpos estáticos:
var options = {
isStatic: true
};
playerBase = Bodies.rectangle(200, 350, 180, 150, options);
World.add(world, playerBase);
player = Bodies.rectangle(250, playerBase.position.y - 160, 50, 180, options);
World.add(world,player)
isStatic: true
: Faz o corpo ficar imóvelBodies.rectangle()
: Cria um retagulo físicoWorld.add()
: Adiciona o corpo ao mundo físico
Criando o playerAcher:
playerArcher = new PlayerArcher(
340,
playerBase.position.y - 112,
120,
120
);
PlayerAcher(x, y, w, h)
: Cria o braço do player com posição (x
,y
) e dimensões (w
,h
)
Criando a flecha:
arrow = new PlayerArrow(
playerArcher.body.position.x,
playerArcher.body.position.y,
100,
10
);
PlayerArrow(x, y, w, h)
: Cria a flecha com posição (x
,y
) e dimensões (w
,h
)
E por fim configuramos os modos de desenho:
angleMode(DEGREES);
}
angleMode(DEGREES)
: Configura o ângulo para graus
Passo 7: Função draw()
A função draw() é chamada continuamente para atualizar e renderizar:
background(backgroundImg)
: Define a imagem de fundo do jogoEngine.update()
: Calcula todas as interações físicas a cada frame
function draw() {
// Define o fundo usando a imagem carregada
background(backgroundImg);
// Atualiza o motor de física
Engine.update(engine);
Agora, renderizando o playerAcher na tela:
playerArcher.display();
Mas também, renderizando a flecha na tela:
arrow.display();
Agora, renderizando os corpos para aparecem na tela:
image(baseimage,playerBase.position.x,playerBase.position.y,180,150)
image(playerimage,player.position.x,player.position.y,50,180)
// Título
fill("#FFFF");
textAlign("center");
textSize(40);
text("ARQUEIRO ÉPICO", width / 2, 100);
image():
Renderiza imagens na tela usando posições físicas e dimensõesfill
: Define cor do texto (branco)textAlign
: Alinhamento centralizadotextSize
: Tamanho da fonte (40px)text
: Exibe texto
Passo 8: Função keyPressed()
Agora que já temos a flecha (PlayerArrow
) pronta, vamos fazer com que o jogador possa atirá-la pressionando a tecla ESPAÇO. Para isso, usaremos a função keyPressed()
do p5.js, que detecta quando uma tecla é pressionada.
function keyPressed() {
if (keyCode === 32) {
arrow.shoot(playerArcher.body.angle);
}
}
keyCode === 32
detecta exclusivamente a tecla ESPAÇOkeyPressed()
executa apenas no frame exato do pressionamento (não em repetição)playerArcher.body.angle
fornece a direção atual do arqueiro para o disparoarrow.shoot()
ativa a física da flecha com velocidade proporcional ao ângulo