arqueiro

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.

				
					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óvel
  • Matter.Bodies.rectangle(): Cria um corpo retangular no Matter.js

  • width/height: Armazenam as dimensões para renderização precisa

  • collapse: 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 global

  • setAngle(-90): Rotaciona o corpo para apontar verticalmente para cima

				
					
    // 6. Controle de Movimento (Para Baixo)
    if (keyIsDown(DOWN_ARROW) && this.body.angle < -85 ) {
      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 teclas

  • Incrementos/decrementos de 1° garantem movimento suave

  • setAngle() aplica as mudanças ao corpo físico

				
					  display() {
    // 6. Controle de Movimento (Para Baixo)
    if (keyIsDown(DOWN_ARROW) && this.body.angle < -85 ) {
      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áficas

  • translate(): Move a origem para a posição do arqueiro

  • rotate(): Aplica a rotação física ao sistema de coordenadas

  • imageMode(CENTER): Faz a imagem girar em torno do seu centro

  • image(): 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.

				
					class PlayerArrow {
  constructor(x, y, width, height, archerAngle) {
    var options = {
      isStatic: true,
      density: 0.1
    };
    this.width = width;
    this.height = height;
    this.body = Bodies.rectangle(x, y, this.width, this.height, options);
    this.image = loadImage("./assets/arrow.png");
    this.archerAngle = archerAngle;
    this.velocity = 0;
    World.add(world, this.body);
  }
				
			
  • isStatic: true: Torna o corpo imóvel
  • density: 0.1 define uma densidade baixa para um movimento mais suave.
  • Matter.Bodies.rectangle(): Cria um corpo retangular no Matter.js

  • width/height: Armazenam as dimensões para renderização precisa

  • this.archerAngle = archerAngle :Armazena o ângulo do arqueiro no momento do disparo

  • this.velocity = 0: Inicializa a velocidade da flecha como zero (será alterada ao disparar)
  • image: Carrega o arquivo PNG do assets (deve estar pré-carregado)

  • World.add(): Insere o corpo na simulação física global

				
					shoot(archerAngle) {
  // Ajusta ângulo para sistema Matter.js
  archerAngle += 90;

  // Cria vetor de velocidade (radianos)
 this.velocity = p5.Vector.fromAngle(archerAngle * (3.14 / 180))
  
  // Define força do disparo
  this.velocity.mult(0.5);

  // Aplica velocidade ao corpo físico
  Matter.Body.setVelocity(this.body, {
    x: degrees(this.velocity.x),
    y: degrees(this.velocity.y)
  });

  // Libera flecha para simulação física
  Matter.Body.setStatic(this.body, false);
}
				
			
  • archerAngle += 90 Ajusta o ângulo em +90 graus para corrigir a orientação padrão do Matter.js

  • p5.Vector.fromAngle() converte o ângulo de graus para radianos e cria vetor de velocidade

  • velocity.mult(0.5) 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.

				
					// Calcula ângulo temporário da flecha
var tmpAngle;

// Se flecha parada (sem velocidade vertical)
if (this.body.velocity.y === 0) {
  // Usa ângulo do arco + 90° (posição padrão)
  tmpAngle = this.anguloarco + 90;
} else {
  // Se em movimento: calcula ângulo baseado na velocidade (transforma em graus)
  tmpAngle = Math.atan(this.body.velocity.y / this.body.velocity.x) * (180 / 3.14);
}

// Aplica ângulo calculado ao corpo físico
Matter.Body.setAngle(this.body, tmpAngle);
				
			
  • this.body.velocity.y === 0 : Detecta flecha parada

  • this.anguloarco + 90 : Posição de repouso alinhada com o arco

  • Math.atan(): Calcula ângulo de trajetória

  • * (180/3.14): Converte radianos para graus

  • setAngle(): Atualiza rotação física da flecha

				
					  // Método para desenhar a flecha na tela
  display() {
  
  // Calcula ângulo temporário da flecha
var tmpAngle;

// Se flecha parada (sem velocidade vertical)
if (this.body.velocity.y === 0) {
  // Usa ângulo do arco + 90° (posição padrão)
  tmpAngle = this.anguloarco + 90;
} else {
  // Se em movimento: calcula ângulo baseado na velocidade (transforma em graus)
  tmpAngle = Math.atan(this.body.velocity.y / this.body.velocity.x) * (180 / 3.14);
}

// Aplica ângulo calculado ao corpo físico
Matter.Body.setAngle(this.body, tmpAngle);
    
    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áficas

  • translate(): Move a origem para a posição do arqueiro

  • rotate(): Aplica a rotação física ao sistema de coordenadas

  • imageMode(CENTER): Faz a imagem girar em torno do seu centro

  • image(): Desenha o sprite com dimensões armazenadas

Passo 4: Classe Board() (arquivo board.js)

Vamos criar o quadro, que será o alvo do jogador. O quadro é estático (parado) em todo momento do jogo.

				
					class Board {
  constructor(x, y, width, height) {
    var options = {
      isStatic: true
    };

    this.body = Bodies.rectangle(x, y, width, height, options);

    this.width = width;
    this.height = height;
    this.image = loadImage("./assets/board.png");

    World.add(world, this.body);
  }
				
			
  • isStatic: true: Torna o corpo imóvel
  • Matter.Bodies.rectangle(): Cria um corpo retangular no Matter.js

  • width/height: Armazenam as dimensões para renderização precisa

  • image: Carrega o arquivo PNG do assets (deve estar pré-carregado)

  • World.add(): Insere o corpo na simulação física global

				
					  display() {

    push();
    imageMode(CENTER);
    image(this.image, this.body.position.x, this.body.position.y, this.width, this.height);
    pop();
  }
}
				
			
  • push()/pop(): Isolam transformações gráficas

  • imageMode(CENTER): Faz a imagem girar em torno do seu centro

  • image(): Desenha o sprite com dimensões armazenadas

Passo 5: 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 board1, board2;

var arrow;
var playerArrows = [];
				
			
  • Engine: O “cérebro” que calcula todas as interações físicas
  • World: O “container” onde todos os objetos físicos existem
  • Body: Permite manipular objetos após a criação (ex.: girar paredes)
  • Constraint: Permite criar restrições e conexões entre corpos físicos

Passo 6: 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ário
  • loadImage("./assets/base.png"): Carrega a imagem da base do personagem
  • loadImage("./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 7: 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)
 
				
			
				
					  // Cria o motor de física
  engine = Engine.create();
  world = engine.world;
				
			
  • Engine.create(): Inicializa o motor de física
  • engine.world: Acessa o mundo físico criado
				
					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óvel
  • Bodies.rectangle(): Cria um retagulo físico
  • World.add(): Adiciona o corpo ao mundo físico
				
					 board1 = new Board(width - 300, 330, 50, 200);
  board2 = new Board(width - 550, height - 300, 50, 200);
				
			
  • Board(x, y, w, h): Cria o quadro com posição (xy) e dimensões (wh)
				
					  playerArcher = new PlayerArcher(
    340,
    playerBase.position.y - 112,
    120,
    120
  );

				
			
  • PlayerAcher(x, y, w, h): Cria o braço do player com posição (xy) e dimensões (wh)
				
					angleMode(DEGREES);
}

				
			
  • angleMode(DEGREES): Configura o ângulo para graus

Passo 8: Função draw()

A função draw() é chamada continuamente para atualizar e renderizar:

  • background(backgroundImg): Define a imagem de fundo do jogo

  • Engine.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);
				
			
				
					playerArcher.display();
				
			
				
					  board1.display();
  board2.display();
				
			
				
					  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ões
  • fill: Define cor do texto (branco)
  • textAlign: Alinhamento centralizado
  • textSize: Tamanho da fonte (40px)
  • text: Exibe texto
				
					  for (var i = 0; i < playerArrows.length; i++) {
    if (playerArrows[i] !== undefined) {
      playerArrows[i].display();
    }
  }
  
  }
				
			
  • for (var i = 0; i < playerArrows.length; i++): Loop através de todas as flechas disparadas

  • if (playerArrows[i] !== undefined): Verifica se a flecha atual existe

  • playerArrows[i].display(): Exibe cada flecha ativa na tela

Passo 9: Função keyReleased()

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 keyReleased() do p5.js, que detecta quando uma tecla é liberada após ser pressionada.

				
					function keyReleased() {
  if (keyCode === 32) {
    if (playerArrows.length) {
      var angle = playerArcher.body.angle;
      playerArrows[playerArrows.length - 1].shoot(angle);
    }
  }
}
				
			
  • keyCode === 32: detecta exclusivamente a tecla ESPAÇO

  • playerArrows.length: Verifica se existem flechas no array

  • var angle = playerArcher.body.angle: Obtém o ângulo atual do arqueiro

  • playerArrows[playerArrows.length - 1].shoot(angle): Dispara a última flecha criada com o ângulo atual

Passo 10: Função keyPressed()

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) {
    
    var angle = playerArcher.body.angle;
    var arrow = new PlayerArrow(playerArcher.body.position.x, playerArcher.body.position.y, 100, 10, angle);

    Matter.Body.setAngle(arrow.body, angle);
    playerArrows.push(arrow);
  }
}
				
			
  • keyCode === 32: detecta exclusivamente a tecla ESPAÇO

  • var angle = playerArcher.body.angle: Obtém o ângulo atual do arqueiro
  • var arrow = new PlayerArrow(x, y, 100, 10, angle):Cria uma nova flecha na posição do arqueiro

  • Matter.Body.setAngle(arrow.body, angle):Ajusta o ângulo do corpo físico da flecha

  • playerArrows.push(arrow): Adiciona a nova flecha ao array de flechas

Posts Similares

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *