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
- abra o seu projeto idêntico ao final da aula, você também pode baixa-lo no github da professora aqui
- baixe o spritesheet em https://rembound.com/files/creating-a-snake-game-tutorial-with-html5/snake-graphics.png
- coloque-a no diretório raiz
- 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
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/
Diogo Chesca
29/03/2021 15:38