Desenvolvendo Jogos em HTML 5 – Parte 2


No ultimo post sobre HTML 5 eu falei um pouco sobre como desenhar com canvas, nesse pretendo explicar um pouco sobre animação no canvas, assim como no outro usarei um pouco de matemática, não se preocupe com as formulas e cálculos, não são a parte importante do tutorial e provavelmente não se aplicam a outros exemplos, eu atropelei um pouco as explicações para não ficar um texto gigante.

Antes de aplicarmos as animações ao nosso jogo exemplo, faremos um outro exemplo de animação, simularemos o comportamento do experimento “Pendulum Wave” que é fácil de ser recriado e demonstra muito bem o mecanismo de animação que queremos usar, alem de eu considerar esse experimento particularmente impressionante, se nunca viu essa experiência impressione-se com o vídeo abaixo.

Trabalhando com desenvolvimento de sites eu raramente precisei usar orientação a objetos em JavaScript, normalmente os problemas se resolvem em funções de pouca linhas independentes, isso porque na maioria das vezes estamos trabalhando com elementos do HTML que já tem uma estrutura com uma serie de propriedades que nos apenas alteramos, mas nesse caso o nosso único elemento é o canvas, nos criamos tudo sobre ele e não estamos usando nenhuma biblioteca para ajudar o trabalho, então a complexidade é muito maior e a orientação a objectos, que normalmente é um tiro de bazuca em formigas, será essencial para facilitar nosso trabalho.

nesse exemplo também vou trabalhar em um arquivo separado para facilitar a edição, o HTML segue com apenas o básico, um canvas:

<body onload="animacao()">
<canvas id="myGame" width="800" height="600" style="background:green;">
<p>Seu navegador não suporta HTML5!</p>
</canvas>

</body>

e nosso arquivo de script conterá todo o código da animação, mais uma vez, sem nenhuma imagem.

Se você esta acostumado a fazer animações por código no Flash deve estar esperando algumas facilidades, no caso do Flash você pode colocar um objeto no palco e move-lo ou aplicar qualquer transformação a essa instância do objeto facilmente, no caso do html 5 precisamos trabalhar em um nível mais baixo como no OpenGL, redesenhando os objetos a cada quadro. A primeira impressão sobre essa técnica é de que ela é menos eficiente do que método usado pelo flash em termos de desempenho, mas isso não é verdade, embora o processo todo lembre o algoritmo do pintor os objetos estão apenas sendo posicionados e não renderizados, posteriormente caberá ao navegador aplicar suas próprias técnicas de render.

para a nossa simples simulação criaremos uma classe pendulo com um método Draw() que se encarregará de desenhar o pêndulo, no nosso caso manteremos um vetor com todos o elementos a serem desenhados e a cada quadro percorreremos esse vetor desenhando cada um dos elementos, além de pratico parece ser a abordagem mais popular, então se você procurar por códigos na internet eles não devem fugir muito disso.

Em primeiro lugar vamos definir a classe e seus atributos, estou evitando usar o mínimo de variáveis para facilitar o entendimento:

function Pendulo(x,y,l,r,teta)
{
this.x = x;
this.y = y;
this.l = l;
this.r = r;
this.teta = teta;
this.f = 0;
//consideranto a massa = 1 por unidade de medida volumetrica
this.m = (4/3)*Math.PI*this.r^3
this.p = this.m * gravidade;
this.a = 0;
}

Os parâmetros são X e Y do ponto onde o pêndulo está preso, L o comprimento da linha ideal, R o raio da esfera e Teta o ângulo inicial do pêndulo em relação ao vetor gravitacional.

para facilitar os cálculos que envolvem a posição da bola, que varia com o tempo, usaremos duas funções auxiliares:

this.getBX = function()
{
return x+Math.sin(this.teta)*l;
}

this.getBY = function()
{
return y+Math.cos(this.teta)*l
}

para desenhar a bola do pêndulo usaremos o recurso de arco do canvas:

context.arc(this.getBX(), this.getBY(), this.r, 0, Math.PI*2, true);

preenchido como expliquei na parte anterior do tutorial, a linha será desenhada por:

context.moveTo(this.getBX(), this.getBY());
context.lineTo(this.x,this.y);

portanto o método fica desse jeito:

this.draw = function()
{
var gradient = context.createRadialGradient(this.getBX(),this.getBY(),0,this.getBX(),this.getBY(),this.r);
gradient.addColorStop(0, '#CCC');
gradient.addColorStop(1, '#555');
context.beginPath();
context.fillStyle = gradient;
context.arc(this.getBX(), this.getBY(), this.r, 0, Math.PI*2, true);
context.moveTo(this.getBX(), this.getBY());
context.lineTo(this.x,this.y);
context.closePath();
context.stroke();
context.fill();
this.updateTeta();
}

o próximo passo é descrever matematicamente a movimentação do pêndulo, como a forca é definida por um vetor ortogonal a linha:

e sabemos que F = m.a podemos calcular a aceleração linear e facilmente encontrar a aceleração angular pelo arco seno da força, é isso que as linhas abaixo fazem:

this.updateTeta = function()
{
this.f = this.p*Math.sin(this.teta);
this.a = this.a + this.f/this.m;
this.teta = this.teta - Math.asin(this.a/this.l);
}

Pronto, já temos um pêndulo funcionando, basta agora acertar os valores de atualização da tela e a gravidade, como estamos usando Pixels no lugar de metros e milissegundos no lugar de segundos não espere um resultado realista, eu gostei de

var gravidade=0.398;
var drawInterval = 5;
var pend10Interval = 30;

pend10Interval define o tempo do décimo pêndulo, para criar o efeito que queremos precisamos alinhar os pêndulos em um looping com os períodos definidas por

periodo = (pend10Interval/((pend10Interval-9)+i));

Como nossa classe trabalha com o construtor: Pendulo(x,y,l,r,teta), precisamos na verdade determinar o comprimento (l). A wikipedia nos lembra que

O que nos permite deduzir que:

l = gravidade * Math.pow(((pend10Interval/((pend10Interval-9)+i))/Math.PI*2),2);

colocando tudo em um looping e escrevendo as ultimas funções auxiliares:

function animacao()
{
canvas = document.getElementById('canvas');
context = canvas.getContext("2d");
for
{
l = gravidade * Math.pow(((pend10Interval/((pend10Interval-9)+i))/Math.PI*2),2);
pendulos[i]=new Pendulo(500,0,l*2000,10,Math.PI/4);
}
setInterval(draw, drawInterval);
}

function draw()
{
clear();
for (i=0; i i<pendulos.length; i++)
{
pendulos[i].draw();
}
}

function clear()
{
context.clearRect ( 0 , 0 , 1000 , 1000 );
}

O resultado final:

Resultado final

Veja funcionando aqui e baixe o código aqui.

Agora voltando ao jogo, eu tive que reescrever o código em uma classe, sem grandes alterações usando a ideia apresentada acima, eu havia citado que provavelmente teria que reescrever partes do código pois estou fazendo o tutorial conforme estudo Canvas, algo me diz que vou passar por isso novamente na hora de implementar a interação com o mouse.

A classe ficou com essa estrutura:

function Card()
{
this.draw = function() {}

this.drawCardBack = function() {}

this.cardBorderDraw = function() {}

this.cardBackDraw = function(x1,y1,x2,y2, r, nLinhasTextura) {}
}

a primeira coisa que você vai estranhar é que não existem mais os parametros no formato:

Card( x1,y1,x2,y2, r)

isso porque eu estou usando uma técnica para simular polimorfismo, ou seja, agora eu posso instanciar a classe com todos os parâmetros, ou com um só, que no caso é um vetor com todos, isso vai facilitar a implementação quando tiver-mos que usar um baralho completo:

var parametros = Card.arguments;
if(parametros.length == 1){
parametros = parametros[0];
}
this.x1 = parametros[0];
this.y1 = parametros[1];
this.x2 = parametros[2];
this.y2 = parametros[3];
this.r = parametros[4];

tambem não estou passando mais o parâmetro com o nome do canvas, isso porque agora usaremos o context como uma variável global, mais uma vez apenas para facilitar, tanto faz como você implementa.

Como nesse caso o código é mais complexo que no exemplo acima, dividi em dois arquivos, card.js com os códigos relativos a carta em si, e geral.js com o funcionamento da animação, mas na carta adicionaremos ainda duas funções:

this.moveTo = function(x,y) {}
function easing(t, b, c, d) {}

a função easing calcula a posição da carta levando em conta uma equação de suavização do movimento, onde b é o valor inicial, c o final, d a quantidade de passos e t o passo atual. O site http://www.gizma.com/easing/ tem uma grande biblioteca de funções de easing com um demonstrativo grafico do seu comportamento, vale a visita, no caso estamos usando easing in e out do tipo curva quadrática (olha ela ai denovo), a função fica assim:

function easing(t, b, c, d)
{
t /= d/2;
if (t &lt; 1) return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};

a função this.moveTo() atualiza a posição do objeto usando um loop do tipo setInterval() que chama a função especificada periodicamente, clearInterval para esse loop. Escrevi bem extenso e ainda acho que não ficou muito simples de entender:

this.moveTo = function(x,y)
{
var i = 0;
var xInicial = this.x1;
var yInicial = this.y1;
x = x-xInicial;
y = y-yInicial;
var w = this.x2 - this.x1;
var h = this.y2 - this.y1;
var _this = this;
var intervalId = setInterval(function()
{
_this.x1 = easing(i,xInicial,x,motionTime);
_this.x2 = _this.x1+w;
_this.y1 = easing(i,yInicial,y,motionTime);
_this.y2 = _this.y1+h;
i++;
if(i&gt;=motionTime)
{
clearInterval(intervalId);
}
}
,drawInterval);
}

o principal truque ai é que easing retorna o que deve ser adicionado e não o valor final, por isso eu atualizo x e y nas linhas:

x = x-xInicial;
y = y-yInicial;

com isso escrito e os conceitos de animação explicados podemos testar com a função animacao(), escrevi uma animação bem simples que demonstra bem a ideia:

var cartas = new Array();
var drawInterval = 1;
var motionTime = 10;
var formatoCarta = [600,350,750,550, 10];
function animacao() {
canvas = document.getElementById('myGame');
context = canvas.getContext("2d");
var qtdcartas = 8;
for
{
cartas[i] = new Card(formatoCarta);
};
var intervalId = setInterval(draw, drawInterval);

setTimeout(function() {cartas[0].moveTo(10,10);},200);
setTimeout(function() {cartas[1].moveTo(180,10);},400);
setTimeout(function() {cartas[2].moveTo(350,10);},600);
setTimeout(function() {cartas[3].moveTo(530,10);},800);
setTimeout(function() {cartas[4].moveTo(10,25);},1000);
setTimeout(function() {cartas[5].moveTo(180,25);},1200);
setTimeout(function() {cartas[6].moveTo(350,25);},1400);
setTimeout(function() {cartas[7].moveTo(530,25);},1600);

};

o codigo para download esta disponivel nesse link e a animação pode ser vista diretamente clicando na imagem abaixo.

Resultado final

na próxima parte do tutorial, para agilizar os posts, falarei mais da mecânica do jogo do que de HTML, explicarei a questão das fileiras de cartas e alguns truques como uso de uma função callback nas animações.


Uma resposta para “Desenvolvendo Jogos em HTML 5 – Parte 2”