28 agosto 2014

O que aprendi com a programação hoje: dia 2 - animação em Javascript

Esse post faz parte de uma série: o que aprendi com a programação, e tudo começou no dia 1.

Estou contribuindo em um projeto no qual estamos utilizando animações visuais para compor a mecânica de um jogo. Essas animações, produzem alguns efeitos que são exibidas durante a execução do jogo.

Em projetos que desenvolvemos usando a linguagem de programação Javascript, normalmente, o fazemos para aplicações voltadas para Web, ou seja, a interface visual vai rodar dentro do navegador de internet (browser). Possivelmente, você já deve ter visto algum jogo dentro do Facebook ou aplicativos que se integram na tela da rede social. A grande parte desses jogos e aplicativos que rodam pelo browser, são integrados lá e utilizam as tecnologias HTML 5 e CSS, além da linguagem Javascript, que permite integrar essas partes.


Aprendi a criar uma animação usando HTML usando Canvas do Javascript e vou tentar explicar como funciona uma animação usando essa linguagem, assim como, tentarei listar algumas dicas do que foi feito para resolver alguns problemas encontrados e que estão relacionados com animação.

A primeira coisa que você deve saber é que um Canvas é uma área de desenho. Neste espaço chamado Canvas, você poderá exibir um objeto qualquer, esse objeto será posicionado dentro de um sistema cartesiano (vide figura), e pode ter uma cor sólida ou baseado em uma imagem pronta. Dentro da área do Canvas o browser irá desenhar através de um processo de pintura, onde serão definidas as posições que os objetos devem ser exibidos em coordenadas x e y.

Para mais informações, acesse uma ótima referência sobre Canvas neste site: http://diveintohtml5.com.br/canvas.html





Um exemplo de arquivo bola.html




Bola



 

header

This browser is not supported

footer


Se quisermos desenhar um objeto usando Canvas podemos fazer de diferentes formas, porém, percebi junto a minha equipe que uma forma intuitiva para nós foi adotar uma representação na estrutura do código Javascript, tal como usamos as classes na linguagem Java. Vejamos o exemplo:

Exemplo de aquivo javascript chamado bola.js

var Animacao = {
 // variaveis globais
 CANVAS : null,
 CONTEXT : null,
 bola : null,

 // inicializa a Animacao
 init : function() {
  // variaveis de canvas
  Animacao.CANVAS = document.querySelector(".canvas");
  Animacao.CONTEXT = Animacao.CANVAS.getContext("2d");

  // registra eventos
  Animacao.addEventListeners();


  bola = new Bola(0,0);
  bola.init();

  setTimeout(Animacao.pintar,100);


 },

 // limpa a tela
 clearCanvas : function() {
  Animacao.CONTEXT.clearRect(0, 0, Animacao.CANVAS.width, Animacao.CANVAS.height);
 },

 // registra eventos
 addEventListeners : function() {
  Animacao.CANVAS.addEventListener("click", Animacao.clickAnimacaoHandler);
 },

 clickAnimacaoHandler : function(e) {

  console.log("clique da Animacao aqui");

  Animacao.pintar();

 },

 pintar : function() {

  // desenha a Animacao aqui
  console.log("animar aqui");

  if (bola) {

   Animacao.clearCanvas();

   bola.update();
   bola.draw();

  }

 },

};


function Sprite(image) {
 this.image = new Image();
 this.image.src = image + ".jpg";

 this.draw = function(x, y) {
  Animacao.CONTEXT.clearRect(x, y, this.image.width, this.image.height);
  Animacao.CONTEXT.drawImage(this.image, x, y);
 };

 this.animate = function() {
  // TODO animate here
  console.log("animar o item durante seu deslocamento");
 };
}

function Bola(x, y) {
 this.x = x;
 this.y = y;
 this.clicked = false;
 this.sprite;

 this.init = function() {
  this.sprite = new Sprite("bola");
 };

 this.update = function() {

  this.y += (this.sprite.image.height/5);

 };

 this.draw = function() {
  if (this.clicked) {
    console.log("foi clicado");
    this.animate();
  }

  this.sprite.draw(this.x, this.y);
 };

 this.click = function() {
  this.clicked = true;
  this.draw();
 };

 this.animate = function() {
  this.type.sprite.animate();
 };

}


// o javascript vai carregar esse metodo a cada refresh no browser
window.onload = Animacao.init;


Note que esse arquivo representa a estrutura do Javascript para representar o conceito da Bola, bem como, um conceito de Sprite (herança de Java ME), onde podemos criar esse objeto para representar a imagem desejada, com seu x e y, um método para desenhar na tela e outro para atualizar suas posições.

Essa representação proporcionou reusar o código de forma legível para nossa equipe. Permitiu por exemplo, que qualquer função possa ser executada simplesmente invocando o nome da estrutura e sua função.

Como que animação de nossa bola funciona em nosso código Javascript?

Assumindo que animação ocorra a cada click sobre a bola, posso programar um evento que mude a posição do Sprite x e y, alterando seus valores e mandando o Canvas pintar a tela novamente. Quando o Canvas pintar a tela, as posições x e y da bola já foram alteradas para novas posições de acordo com uma direção desejada e portanto, vai ser mostrado após a nova pintura da janela, outra bola em um ponto da janela diferente da interior.

Esse efeito produz uma percepção ao usuário que chamamos de animação. Porém, para esse efeito ser considerado uma animação, tem que parecer um deslocamento parecido com o que ocorre com uma bola real. Para que o efeito fique mais parecido com o mundo real é necessário diminuir o intervalo de deslocamente do x e y, além de aumentar a quantidade de pinturas deste objeto no Canvas. Existe uma relação conhecida chamada Frames/segundos. Essa relação pode ser considerada em seu algoritmo para tornar o efeito de pintura repetitivo e constante, dando a impressão que existe realmente uma bola se movendo. Isso sem considerar efeitos da física, tal como os jogos modernos exploram muito bem.

Portanto, a cada click do mouse eu desejo que a bola se desloque de um ponto a outro. Alguns problemas podem ser notados nessa animação. Ela não está parecendo uma animação. :) Chamamos isso de animação com fluidez no seu efeito perceptível pelo usuário. Quanto mais próximo do mundo real, melhor. Mas sabemos que temos limitações óbvias, tais como:
  • poder computacional para explorar cálculos matemáticos;
  • experiência dos desenvolvedores que estão acostumados a fazerem aplicativos tradicionais e que, geralmente, não requerem fazer animações específicas;
  • um objeto, quando desenhado e visto de uma perspectiva 2D tem diveras percepções de acordo com o seu desenho, mas para parecer uma animação fluida, haveria necessidade de desenha-lo em diversos ângulos (frame a frame), para cada movimento desejado. Imagine uma animação dos cabelos do ser humano. Quantas possibilidades de diferentes maneiras posso representar dependendo da direção do vento? Quanto mais próximo do real, mais difícil de representar; 

O que podemos fazer para tornar a animação um pouco melhor?

Estudar técnicas de animação 2D é um bom começo. Existem diversos framework para JavaScripts que prometem simplificar o uso de animações. Porém, decidimos tocar o projeto com a linguagem Javascript nativamente porque precisávamos aprender melhor essa linguagem e acreditamos que o esforço de fazer desta forma  compensaria porque absorveríamos maior entendimento. De fato isso se concretizou.

Então recomendo que quando haver necessidade de fazer algo usando a linguagem pura ou usar algo pronto, considere avaliar o seu caso em relação a produtividade vs. aprendizado. Precisávamos aprender a linguagem porque acreditávamos que teríamos outras dificuldades, caso a base do conhecimento essencial não estivesse bem desenvolvida previamente. Usar a linguagem pura, nos permitiu desenvolver melhor nossas habilidades. Hoje porém, poderemos considerar utilizar frameworks de terceiros, de forma mais segura, sobre o que realmente precisamos para nosso problema atual.

Mas voltando ao foco da pergunta, podemos melhorar animação, e de fato melhoramos fazendo o deslocamento do x e y com intervalos menores e pinturas mais constantes do Canvas. Considerando que o desenho da imagem da bola possua 180 pixel de largura e altura, pode-se dividir em 5 partes, e para cada parte fazer um pintura.

Então, no caso de desejar fazer a bola descer a cada click, pode:

var Animacao = {
 // variaveis globais
 CANVAS : null,
 CONTEXT : null,
 bola : null,
 emProcessoAnimacao : false,
 contadorAnimacao : 0,

 // inicializa a Animacao
 init : function() {
  // variaveis de canvas
  Animacao.CANVAS = document.querySelector(".canvas");
  Animacao.CONTEXT = Animacao.CANVAS.getContext("2d");

  // registra eventos
  Animacao.addEventListeners();


  bola = new Bola(0,0);
  bola.init();

  setTimeout(Animacao.pintar,100);


 },

 // limpa a tela
 clearCanvas : function() {
  Animacao.CONTEXT.clearRect(0, 0, Animacao.CANVAS.width, Animacao.CANVAS.height);
 },

 // registra eventos
 addEventListeners : function() {
  Animacao.CANVAS.addEventListener("click", Animacao.clickAnimacaoHandler);
 },

 clickAnimacaoHandler : function(e) {

  Animacao.emProcessoAnimacao = true;
  Animacao.pintar();
 },

 pintar : function() {

  // desenha a Animacao aqui
  Animacao.clearCanvas();

  bola.update();

  bola.draw();

  // chamar novamente o metodo (pelo menos 5 vezes)
  if (Animacao.emProcessoAnimacao) {

    Animacao.contadorAnimacao++;

    if (Animacao.contadorAnimacao >= 5) {
     Animacao.contadorAnimacao = 0;
     Animacao.emProcessoAnimacao = false;

    } else {

     // repintar a tela novamente
     setTimeout(Animacao.pintar,50);
    }


  }


 },

};


function Sprite(image) {
 this.image = new Image();
 this.image.src = image + ".jpg";

 this.draw = function(x, y) {
  Animacao.CONTEXT.drawImage(this.image, x, y);
 };
}

function Bola(x, y) {
 this.x = x;
 this.y = y;
 this.clicked = false;
 this.sprite;

 this.init = function() {
  this.sprite = new Sprite("bola");
 };

 this.update = function() {

  this.y += (this.sprite.image.height/10);

 };

 this.draw = function() {
  this.sprite.draw(this.x, this.y);
 };

 this.click = function() {
  this.clicked = true;
  this.draw();
 };

 this.animate = function() {
  this.type.sprite.animate();
 };

}


// o javascript vai carregar esse metodo a cada refresh no browser
window.onload = Animacao.init;


animação fica mais fluída
Note o uso da função  do Javascript: setTimeout(tempo_mili_segundos), essa função permite que possamos agendar um intervá-lo de tempo em mili segundos e passar como argumento  qual função desejamos chamar. Assim podemos chamar a função pintar() constantemente para garantir as 5 pinturas desejadas.

Se você atualizar o código e executar em seu browser, poderá perceber como a animação agora parece mais uma decida um pouco mais suave. Quanto mais suave, em teoria é melhor para a percepção do usuário.
Para baixar os arquivos clique aqui.





Na próxima dica falarei como podemos setar as ferramentas envolvidas de desenvolvimento e como foi o nosso aprendizado da preparação do ambiente de programação Javascript e sua forma de execução no browser. Essa foi uma das melhores sensações que tive do atual estado de ferramentas Javascript, e fiquei extremamente surpreso o quanto foi "fácil" acompanhar a execução, inclusive usando a depuração em tempo real.

Nenhum comentário: