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.

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ó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

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 teclas

  • Incrementos/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á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.

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ó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

  • image: 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á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: 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í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 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á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 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ísica
  • engine.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óvel
  • Bodies.rectangle(): Cria um retagulo físico
  • World.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 (xy) e dimensões (wh)

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 (xy) e dimensões (wh)

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 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);
				
			

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ões
  • fill: Define cor do texto (branco)
  • textAlign: Alinhamento centralizado
  • textSize: 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ÇO

  • keyPressed() executa apenas no frame exato do pressionamento (não em repetição)

  • playerArcher.body.angle fornece a direção atual do arqueiro para o disparo

  • arrow.shoot() ativa a física da flecha com velocidade proporcional ao ângulo

Posts Similares

Deixe um comentário

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