1

Colocando sprites no jogo da cobrinha

#HTML #JavaScript #CSS
Leonardo Almeida
Leonardo Almeida

Colocando sprites no jogo da cobrinha


Nesse artigo você vai aprender como utilizar o canvas drawImage e aprimorar ainda mais o seu projeto snake game aqui da DigitalInnovationOne



  • importando o sprite com javascript


const sprites = new Image();
sprites.src = "./snake-graphics.png";


Como renderizar imagens com canvas


para renderizarmos uma imagem no canvas, utilizamos uma função chamada drawImage

essa função recebe muitos parâmetros e nos vamos conversar sobre eles


ctx.drawImage(
 image, 
 sx, sy, 
 sWidth, sHeight, 
 dx, dy, 
 dWidth, dHeight
);


image = seria a source, onde você indica qual imagem vc quer renderizar

sx, sy, sWidth e sHeight são para nos informarmos o corte que deve ser feito dentro da imagem que escolhemos.


veja a imagem abaixo


https://prnt.sc/10vo00y


como você pode ver o nossa imagem ela é toda subdividida em tilesets de 64 px e é a partir disso que vamos informar qual e o recorte da imagem que queremos utilizar no drawImage


sx, sy = a posição inicial dentro do nosso spritesheet

sWidth, sHeight, = o tamanho do retângulo que deve ser recortado na nossa imagem, que no nosso caso e sempre 64px


agora os parâmetros dx, dy, dWidth e dHeight são a posição e o tamanho respectivamente onde queremos que seja renderizado dentro do canvas, que no nosso caso vão ser as posições do array da cobrinha


  • Renderizando sprite de comida


function drawFood(){
 //context.fillStyle = "red";
 //context.fillRect(food.x, food.y, box, box);
  
 context.drawImage(
   sprites,           //spritesheet
   0, 192,            // x = 0 y = 192 (64+64+64) posição inicial do recorte
   64, 64,            // tamanho do recorte no nosso spritesheet
   food.x, food.y,    //posição da comida
   box,box            // tamanho da comida
 );
}
​


  • Renderizando sprite da cabeça


a primeira posição do array será a nossa cabeça e a ultima será a nossa calda


function criarCobrinha (){
 //Cria cabeça
 context.drawImage(
   sprites,
   254, 0,
   64, 64,
   snake[0].x, snake[0].y,
   box,box
 );
​
 // cria o resto do corpo 
 for(i = 1; i < snake.length - 1; i++){
   context.fillStyle = "green";
   context.fillRect(snake[i].x, snake[i].y, box, box);
 }
  
 // cria calda
 // nada por enquanto
}


como você pode perceber isso causa um bug, a cabeça da cobra não acompanha a direção de movimento , vamos mudar isso.


Refatorando


nesse momento precisamos fazer algumas mudanças no código.

vamos adicionar uma direction no array snake e apagar a variável direction.


snake[0] ={
 x: 8 * box,
 y: 8 * box,
 direction: {
   x: 1,
   y:0
 }
}
//let direction = "right";


agora a direção da nossa cobrinha vai seguir a seguinte regrinha


x = 1, y = 0 >> direita

x = -1, y = 0 >> esquerda

x = 0, y = 1 >> baixo

x = 0, y = -1 >> cima


agora vamos substituir alguns códigos para a nossa logica funcionar


  • vamos comparar e modicar o direction da posição 0 "cabeça" da cobra


function update(event){
 //if(event.keyCode == 37 && direction != 'right') direction = 'left';
 //if(event.keyCode == 38 && direction != 'down') direction = 'up';
 //if(event.keyCode == 39 && direction != 'left') direction = 'right';
 //if(event.keyCode == 40 && direction != 'up') direction = 'down';
​
 if(event.keyCode == 37 && snake[0].direction.x != 1)
   snake[0].direction = { x : -1 , y : 0 }; //left  
 if(event.keyCode == 38 && snake[0].direction.y != 1)
   snake[0].direction = { x : 0 , y : -1 }; //up  
 if(event.keyCode == 39 && snake[0].direction.x != -1) 
   snake[0].direction = { x : 1 , y : 0 }; //right  
 if(event.keyCode == 40 && snake[0].direction.y != -1)
   snake[0].direction = { x : 0 , y : 1 }; //down
}


  • movimentando a cabeça da cobra. substitua o código comentado


function iniciarJogo(){  
​
 //if(snake[0].x > 15*box && direction == "right") snake[0].x = 0;
 //if(snake[0].x < 0 && direction == 'left') snake[0].x = 16 * box;
 //if(snake[0].y > 15*box && direction == "down") snake[0].y = 0;
 //if(snake[0].y < 0 && direction == 'up') snake[0].y = 16 * box;
​
 if(snake[0].x > 15*box && snake[0].direction.x == 1) snake[0].x = 0;
 if(snake[0].x < 0 && snake[0].direction.x == -1) snake[0].x = 16 * box;
 if(snake[0].y > 15*box && snake[0].direction.y == 1) snake[0].y = 0;
 if(snake[0].y < 0 && snake[0].direction.y == -1) snake[0].y = 16 * box;

 // if(direction == "right") snakeX += box;
 // if(direction == "left") snakeX -= box;
 // if(direction == "up") snakeY -= box;
 // if(direction == "down") snakeY += box;
​
 if(snake[0].direction.x == 1) snakeX += box;
 if(snake[0].direction.x == -1) snakeX -= box;
 if(snake[0].direction.y == -1) snakeY -= box;
 if(snake[0].direction.y == 1) snakeY += box;  


  • newHead precisa ser executado antes de dar POP na posição anterior para podermos ter acesso ao direction


let newHead ={
   x: snakeX,
   y: snakeY,
   direction: {
     x: snake[0].direction.x,
     y: snake[0].direction.y
   }
 }
  
snake.unshift(newHead); //método unshift adiciona ...
  
if(snakeX != food.x || snakeY != food.y){
 snake.pop(); //pop tira o último elemento da lista
}else{
 food.x = Math.floor(Math.random() * 15 +1) * box;
 food.y = Math.floor(Math.random() * 15 +1) * box;
} 


Usando o direction como ancora


pronto, agora cada nodo da nossa cobrinha tem armazenado a informação da direção onde esta localizado o o próximo nodo da cobrinha, e com ele podemos pegar também a direção do nodo anterior, mas isso fica para daqui a pouco, agora vamos consertar a cabeça da cobrinha


  • laço condicional para impressão da cabeça da cobrinha


//Cria cabeça
let spriteHeadPosition = {
 x:254, 
 y:0, 
} 
​
if( snake[0].direction.x === 1) 
 spriteHeadPosition = { x:256,y:0 } //head sprite right
if( snake[0].direction.x === -1) 
 spriteHeadPosition = { x:192,y:64 } //head sprite left
if( snake[0].direction.y === 1) 
 spriteHeadPosition = { x:256,y:64 } //head sprite down
if( snake[0].direction.y === -1) 
 spriteHeadPosition = { x:192,y:0 } //head sprite up
​
context.drawImage(
 sprites,
 spriteHeadPosition.x, spriteHeadPosition.y,
 64, 64,
 snake[0].x, snake[0].y,
 box,box
);
​


  • agora vamos renderizar a calda da cobrinha, apenas quando o tamanho da cobrinha for maior que 1


//cria calda
if(snake.length > 1){
   //cria calda
   let spriteTailPosition = {
     x:256,
     y:128 
   } 
​
   if( snake[snake.length -1].direction.x > 0) 
     spriteTailPosition = { x:256,y:128 } 
   if( snake[snake.length -1].direction.x < 0) 
     spriteTailPosition = { x:192,y:192 }
   if( snake[snake.length -1].direction.y > 0) 
     spriteTailPosition = { x:256,y:192 }
   if( snake[snake.length -1].direction.y < 0) 
     spriteTailPosition = { x:192,y:128 }
  
   context.drawImage(
     sprites,
     spriteTailPosition.x, spriteTailPosition.y,
     64, 64,
     snake[snake.length -1].x, snake[snake.length -1].y,
     box,box
   );
 }

Renderizando o corpo da cobrinha


agora vem a parte difícil, precisamos escolher o Sprite certo para renderizar, porem o corpo da cobrinha tem muitos sprites possíveis, mas agora com acesso as directions de cada nodo isso vai ficar muito mais fácil.

primeiro vamos fazer um loop pelo corpo da cobrinha descartando a cabeça e a calda

atenção ao valor de i = 1 e length - 1


// cria o resto do corpo
for(i = 1; i < snake.length-1; i++){
  //context.fillStyle = "green";
  //context.fillRect(snake[i].x, snake[i].y, box, box);   
}


depois vamos usar o direction de cada posição para detectar em quais direções estão os próximo nodo e o anterior da cobrinha

vamos começar com o próximo nodo


// cria o resto do corpo
for(i = 1; i < snake.length-1; i++){
 //context.fillStyle = "green";
 //context.fillRect(snake[i].x, snake[i].y, box, box); 
  
 let haveRight = haveLeft = haveUp = haveDown = false;
​
 if(snake[i].direction.x > 0)
  haveRight = true;  //tem na direita
 if(snake[i].direction.x < 0)
  haveLeft = true;   //tem na esquerda
 if(snake[i].direction.y > 0)
  haveDown = true;   //tem em baixo
 if(snake[i].direction.y < 0)
  haveUp = true;     // tem em cima
}


agora para pegar a direção do nodo anterior e bem simples, avançamos 1 posição do array e invertemos o valor de seu direction.


let nodoAnteriorX = snake[i+1].direction.x * -1; //inverte o valores
let nodoAnteriory = snake[i+1].direction.y * -1; //inverte o valores
​
if(nodoAnteriorX < 0) haveLeft = true;    //tem na esquerda
if(nodoAnteriorX > 0 ) haveRight = true;  //tem na direita
if(nodoAnteriory < 0) haveUp = true;      //tem em cima
if(nodoAnteriory > 0) haveDown = true;    //tem em baixo
​
​


agora basta realizarmos um laço condicional para decidirmos qual sprite devemos renderizar


let spriteBodyPosition = {
 x:64, 
 y:0, 
}
​
if( haveLeft && haveRight) spriteBodyPosition = { x:64,y:0 };  
if( haveUp && haveDown) spriteBodyPosition = { x:128,y:64 }; 
if( haveLeft && haveDown) spriteBodyPosition = { x:128,y:0 };
if( haveLeft && haveUp) spriteBodyPosition = { x:128,y:128 }; 
if( haveRight && haveDown) spriteBodyPosition = { x:0,y:0 }; 
if( haveRight && haveUp) spriteBodyPosition = { x:0,y:64 }; 
​
context.drawImage(
 sprites,
 spriteBodyPosition.x, spriteBodyPosition.y,
 64, 64,
 snake[i].x, snake[i].y,
 box,box
);


pronto, agora a sua cobrinha esta mais viva do que nunca!


e aê qual seria o próximo passo para deixar esse jogo mais incrível ainda ?



spritesheet - https://rembound.com/articles/creating-a-snake-game-tutorial-with-html5

snake-game base - https://github.com/SpruceGabriela/snake-the-game

canvas DrawImage - https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage


veja o snake game project completo com outras alterações como (buffer de movimento, score, tela de game over, resposividade e funcionalidades mobile entre outros)

github - https://github.com/LeonardoVita/javascript-snake-game

live link - https://leonardovita.github.io/javascript-snake-game/

1
14

Comentários (2)

1
Diogo Chesca

Diogo Chesca

29/03/2021 15:38

Show, top demais!

1
⚡Eros Lima

⚡Eros Lima

26/03/2021 08:20

Parabéns Leonardo Almeida

muito bem detalhado, explicado, trabalhado, e app testado completamente top! Veja cliquem no link do game muito rápido,


"não trava como o meu rs" e é

**a versão mais estiloZa qu3 eu já *_*


Obs.: *Pode dar dicas ainda no final, continuando... de como tu pensou na foto tão top que usou e como podemos estilizar (customizar) o game. O que acha?

Desenvolvedor Full Stack Jr

Brasil