segunda-feira, 25 de novembro de 2013

HTML5: Animações com Canvas

HTML5: Animações com CANVAS
           Olá! Segue agora uma postagem sobre um tema interessante: CANVAS. Mas, o que é isso? Como se sabe, a versão do HTML reinante nos dias de hoje é a versão 4. Porém, muitas grandes desenvolvedoras, no intuito de melhorar funcionalidades existentes e incluir novas, estavam elaborando uma nova versão pro HTML, o HTML5. 
Entre as novas possibilidades, destaca-se:
1) Conformidade com as especificações do CSS3
2) Reprodução de determinados conteúdos multimídia (Vídeo, audio) sem necessidade de plugins.
3) Possibilidade de armazenamento de cache
4) Tags novas (Canvas, video, audio, nav, sidebar, etc)

Pois bem! Dentre estas tags novas, falaremos agora do canvas. Mas, o que é isto? Em poucas palavras, ela permite que, sejam criadas animações wm 2D apenas com uso de javascript. É como se voçê criasse animações FLASH diretamente em javascript. Enfim, nota-se que esta tecnologia abre um grande leque de possibilidades para designers e programadores de jogos. 
Apenas como aprendizado, sugiro que leia o seguinte tutorial, que me serviu de portas para esta novidade, que agora compartilho:
Tutorial básico de CANVAS

Agora, segue abaixo algumas coisas (Bem toscas!),  sobre o que consegui fazer esta tecnologia:
Jogo de duas bolas
Ponteiro de relógio

Caso queira o link para ambos os testes, baixe-os, clicando no link abaixo:

Segue abaixo o código do primeiro exemplo, no qual, como voçê pode ver, é criado, pixel á pixel, todo o cenário e os personagens.
 <!DOCTYPE html> 
<html> 
<head> 
 Testes com CANVAS 
 
 
 
  
  
 
  

Controlando um boneco com CANVAS

Seu navegador não suporta HTML5

</body> </html>
No arquivo draw.js, estão todas as funções e módulos para controle da animação.

 
/*Toda vez que é  executado algo na tag CANVAS, deve-se instanciar o objeto especificado na variável context abaixo
 * pertencente á própria tag CANVAS. 
 * */
function carregaContexto(id){
   var elemento = document.getElementById(id);
   var context = elemento.getContext("2d");
   if(context){
   return context;
   }else{
   return false;
   }
  }
 /* Função abaixo para desenhar um triângulo simples */
function drawTriangulo(x,y,tamX,tamY){
   var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
   x = parseInt(x);
   y = parseInt(y);
   tamX = parseInt(tamX);
   tamY = parseInt(tamY);
   if(contexto){
    contexto.fillStyle = "#00FF00";
    contexto.moveTo(x-tamX,tamY);
    contexto.lineTo(tamX+x,tamY);
    contexto.lineTo(x,y);
    contexto.fill();
   }
  }
/* Função abaixo necessário para desenhar rostos */
function drawRosto(x,y,r){
 x = parseInt(x);
 y = parseInt(y);
 r = parseInt(r);
 var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
  contexto.fillStyle = "#FFE4B5";
  contexto.moveTo(x,y);
  contexto.arc(x,y,r,0,Math.PI*2,false);
  contexto.fill();
  //Desenhando os olhos
  contexto.fillStyle = "#000000";
  contexto.moveTo(x-7,y-4);
  contexto.fillRect(x-7,y-4,4,4);
  contexto.moveTo(x+7,y-4);
  contexto.fillRect(x+7,y-4,4,4);
  //Desenhando a boca
  contexto.beginPath();
  contexto.strokeStyle = "#FF0000";
  contexto.moveTo(x-4,y+10);
  contexto.lineTo(x+5,y+10);
  contexto.stroke();
 }
}
/* Função para se desenhar qualquer forma quadrada */
function drawQuadrada(x,y,tamX,tamY,cor){
 x = parseInt(x);
 y = parseInt(y);
 tamX = parseInt(tamX);
 tamY = parseInt(tamY);
 var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
  contexto.fillStyle = cor;
  contexto.beginPath();
  contexto.moveTo(x,y);
  contexto.fillRect(x,y,tamX,tamY);
  contexto.fill();
 }
}

/* Função abaixo desenha um sapato */
function drawSapato(x,y,tamX,tamY,cor){
 x = parseInt(x);
 y = parseInt(y);
 tamX = parseInt(tamX);
 tamY = parseInt(tamY);
 var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
  contexto.fillStyle = cor;
  contexto.beginPath();
  contexto.moveTo(x,y);
  contexto.fillRect(x,y,tamX/2,tamY/2);
  contexto.fill();
  contexto.beginPath();
  contexto.moveTo(x+tamX/2,y-tamY/2);
  contexto.arc(x+tamX/2,y+7,9,Math.PI*1.5,Math.PI*0.5,false);
  contexto.fill();
 }
}

/* Função para desenhar mãos */
function drawMao(x,y,r,cor){
var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
  contexto.fillStyle = cor;
  contexto.beginPath();
  contexto.moveTo(x,y);
  contexto.arc(x,y,r,0,Math.PI*2,false);
  contexto.fill();
  contexto.beginPath();
  contexto.moveTo(x,y-r);
  contexto.quadraticCurveTo(x+50,y-50,x+2,y+5);
  contexto.moveTo(x,y-r);
  contexto.quadraticCurveTo(x+5,y-100,x-20,y+5);
  contexto.fill();
 }
}

/* Função para desenhar relógio*/
function drawRelogio(x,y,r,cor){
 x = parseInt(x);
 y = parseInt(y);
 r = parseInt(r);
 var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
  contexto.fillStyle = cor;
  contexto.beginPath();
  contexto.moveTo(x,y);
  contexto.arc(x,y,r,0,Math.PI*2,false);
  contexto.fill();
  //Marcando os números do relógio
  contexto.fillStyle = "#FFFF00";
  contexto.fillText("12",x,y-(r-10));
  contexto.fillText("3",x+(r-10),y);
  contexto.fillText("6",x,y+(r-10));
  contexto.fillText("9",x-(r-10),y);
  //Desenhando o ponteiro
 
 }

}

/*Função abaixo move o braço do boneco com base no clique na mão do mesmo */

function moveBraco(x,y,tamB,bX,bY){
 var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
  x = parseInt(x);
  y = parseInt(y);
  tamB = parseInt(tamB);
  
  
  if(y >= bY){
   if(x >= bX){
    c = 2;
   }else{
    c = 4;
   }
  }else{
   if(x >= bX){
    c = 8;
   }else{
    c = 6;
   }
  }
  
  var minX = bX - tamB-15;
  var minY = bY-tamB;
  var maxX = bX + tamB-15;
  var maxY = bY+tamB;
  cX = (xmaxX?maxX:x));
  cY = (ymaxY?maxY:y));
  //Rotação do braço com base nas coordenadas
  drawQuadradaComRotacao(bX,bY,cX,cY,tamB,"#2F4F4F");
  //alert(cX + "-" + minX);
  drawMao(cX,cY,20,"#F0E68C");
 }
}
/*Desenhando o braço, para que sei posicionamento esteja relativo ao posicionamento da mão*/
function drawQuadradaComRotacao(x,y,cX,cY,tamB,cor){
 x = parseInt(x); //Coordenadas do braço
 y = parseInt(y);
 cX = parseInt(cX); // Coordenadas c são coordenadas da mão. São passadas como parâmetro para que o braço possa acompanhar a mão
 cY = parseInt(cY);
 tamB = parseInt(tamB); // Tamanho do braço
 var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
  contexto.fillStyle = cor;
  contexto.beginPath();
  contexto.moveTo(x,y);
  //Setar a coordenada do braço para que acompanhe a mão
  contexto.lineTo(cX,(cY-2));
  contexto.lineTo(cX,(cY-2) + tamB);
  contexto.lineTo(x,y + tamB);
  contexto.fill();
 }
}

/*Desenhando um corpo inteiro*/
function desenhoCorpo(){
 var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
 
 //Desenho de um corpo humano
   drawRosto(165,50,25);
   //Pesco词
   drawQuadrada(150,70,30,20,"#FFE4B5");
   //Barriga
   
   drawQuadrada(80,100,45,30,"#2F4F4F");
   
   //Perna direita
   drawQuadrada(130,210,30,100,"#000000");
   
   
   //P顥squerdo
   drawSapato(130,310,60,30,"#A52A2A");
   //M䯠direita
   drawMao(75,115,20,"#F0E68C");
   //Barriga
   drawQuadrada(125,90,80,120,"#2F4F4F");
   
   
   //Perna esquerda
   /*drawQuadrada(170,210,30,100,"#000000");
   //Pé ireito
   drawSapato(170,310,60,30,"#A52A2A");*/
   //Ch䯍
   drawQuadrada(0,325,document.getElementsByTagName('canvas')[0].width,document.getElementsByTagName('canvas')[0].height-310,'#8B4513');
   
  
 }
 
}

/* Função para mover o pé */
function movePe(x,y,tamX,tamY,cX,cY){
 var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
  //Desenhando a perna com base nas coordenadas c
  contexto.fillStyle =  "#000000";
  contexto.moveTo(x,y);
  contexto.lineTo(cX,cY);
  contexto.lineTo(cX+tamX,cY);
  contexto.lineTo(x+tamX,y);
  contexto.fill();
  drawSapato(cX,cY,60,30,"#A52A2A");
  
 }
}
/*Limpar a tela inteira. Executada quando se encerra o jogo, ou para que tudo se inicie do zero*/
function limpaTela(){

 drawQuadrada(0,0,document.getElementsByTagName('canvas')[0].width,document.getElementsByTagName('canvas')[0].height,'#FFFFFF');
}

No arquivo boneco.js, tem os atributos e métodos próprios dos personagens.
 
/*
 Criando o objeto Boneco, que será o responsável pelas movimentações no cenário(boneco.html)
*/


function Boneco(cor){
 this.cor = cor;
 this.dX = 0; //Coordenada X atual do objeto
 this.dY = 0; // Coordenada Y atual do objeto
 this.s = "d"; //Sentido do objeto: Pode ser d(Direito) ou e(Esquerdo)
 this.minY = 240; // Coordenada Y m[inima(Ponto Y do chão)
 this.sY = "C"; //Sentido horizontal do objeto
 this.maxY = 0;
}
/*Gambiarra abaixo para associar o método constroe á classe Boneco
 * Forma alternativa de definir métodos á uma classe
 * */
Boneco.prototype.constroe = function(x,y){
 this.dX = x;
 this.dY = y;
 this.maxY = 320;
 var contexto = carregaContexto(document.getElementsByTagName('canvas')[0].id);
 if(contexto){
  contexto.beginPath();
  contexto.fillStyle = this.cor;
  contexto.arc(x,y,20,0,2*Math.PI,false);
  contexto.fill();
  //Desenhando os olhos
  contexto.beginPath();
  contexto.fillStyle = "#FFFFFF";
  contexto.moveTo(x+2,y-2);
  contexto.arc(x+2,y-2,3,0,2*Math.PI,false);
  contexto.fill();
  
  contexto.beginPath();
  contexto.fillStyle = "#FFFFFF";
  contexto.moveTo(x+8,y-4);
  contexto.arc(x+8,y-4,3,0,2*Math.PI,false);
  contexto.fill();
 }
}

/*
 Método abaixo executa move o objeto Boneco para os lados. Quando o objeto está no limite(Final do caminho), seu sentido é alterado(Esquerdo ou Direito).
 Como terceiro parâmetro,  recebe o array de obstáculos a serem ultrapassados.
 Se x estiver entre propriedades minX e maxX
  Se for menor ou igual á minY do obstáculo
   Seta atributo dY como sendo equivalente á minY.
  Senão
   Se sentido é igual á direito
    Seta dx como equivalente á minX
   Senão
    Seta dx como equivalente á maxX
 Senão
  Se dx for maior do que maxX e sentido for D OR se dx for menor do que minX e sentido for E
   Seta dY como maxY(Toca o solo)
  
*/

Boneco.prototype.andar = function(dx,s,obstaculos){
 var  maxX = document.getElementsByTagName('canvas')[0].width;
 this.s = s;
 if(this.dX >= maxX){
   this.s = "e";
  }else if(this.dX <= 10){
   this.s = "d";
  }
   for(obs in obstaculos){
    if( (obstaculos[obs].minX <= this.dX)  &&   ( this.dX <= obstaculos[obs].maxX)){
     if(this.dY <= obstaculos[obs].minY){
      this.dY = obstaculos[obs].minY;
     }else{
      if(this.s == "d"  && (this.dX <= obstaculos[obs].minX + obstaculos[obs].width/2)){
       this.dX -= 2*dx;
       return;
      }else if(this.s == "e" && (this.dX >= obstaculos[obs].maxX - obstaculos[obs].width/2)){
       this.dX += 2*dx;
       return;
      }
     }
    }else if((this.dX > obstaculos[obs].maxX && this.s == "d") || (this.dX < obstaculos[obs].minX && this.s == "e")){
     this.dY = this.maxY;
    }
   }
 if(this.s == "d"){
  this.dX += dx;
 }else{
  this.dX -= dx;
 }
}

/*
 Método abaixo faz o objeto pular. Quando o usuário clica na barra de espaço, o usuário pula á uma altura maior do que a caixa 
*/

Boneco.prototype.pular = function(){
 var maxY = this.maxY;
  if(this.dY >= maxY){
   this.sY = "C";
  }else if(this.dY <= this.minY){
   //this.dY = this.minY;
   this.sY = "B";
  }
  if(this.sY == "C"){
   this.dY -= 20;
  }else if(this.sY == "B"){
   this.dY += 20;
  }
}


/*
 Método abaixo serve para criar obstáculos na tela.
*/

function Obstaculo(x,y,lar,tam,cor){
 lar = parseInt(lar);
 x = parseInt(x);
 y = parseInt(y);
 //Desenhando o objeto
 drawQuadrada(x,y,lar,tam,cor);
 this.minY = y;
 this.minX = x;
 this.maxX = x+lar;
 this.width = lar;
 this.altura = tam;
}

Quando se usa CANVAS, pode-se notar que os desenhos são feitos pixel-á-pixel. Ou seja, são feitos especificando as coordenadas de cada fígura. Por isso, apesar de o carregamento ser bem mais rápido, se comparado com o SVG (Mesmo propósito que o CANVAS, porém, mais fácil e menos flexível), demanda muito esforço de desenvolvimento. Além disso, é necessário que se tenha a disposição, um conjunto de funções básicas como: Andar, pular, tratamento de colisões; caso desenvolva animações mais complexas. Do contrário, estará perdendo tempo com detalhes que podem muito bem ser encapsulados.


Espero que tenham gostado, e, se gostou, poste seu comentário. Se não, manda sua crítica, para que possamos sempre melhorar.

Abraços