Animações no Canvas com EaselJS

Por nossas pupilas passam grandes quantidades de conteúdos web. Só paramos a vista ante um titular interessante. Os titulares de nossa página são o ponto onde o visitante decide continuar ou não.

Por David Rousset -Traduçao CRV


Publicado em: 30/7/12
Valorize este artigo:
Se você quiser passar o tempo fazendo um jogo com o elemento Canvas de HTML5, você necessita uma solução para manejar as figuras animadas (?sprites?). Existem varias bibliotecas que nos podem ajudar a criar jogos, como ImpactJS e CraftyJS.

A final decidi utilizar EaselJS, que é a que utilizaram para fazer o website PiratesLoveDaisies ("os piratas adoram as margaridas") um interessante jogo de defesa de uma torre feito com HTML5. Vamos ver neste tutorial como podemos utilizar nossas próprias figuras e como podemos dar-lhes movimento.

Introdução

No site oficial de EaselJS vamos encontrar exemplos muito interessantes e algo de documentação básica. Vamos utilizar o exemplo sprites como ponto de partida junto com os recursos que temos no exemplo XNA 4.0 Platformer.

Se você acompanha meu blog, já sabe que me encanta brincar com este exemplo. Já o utilizei em outros dois artigos anteriores:

A equipe de XNA atualizou o exemplo platformer e já está disponível para Xbox 360, PC e Windows Phone 7: App Hub ? platformer. Baixe-a e jogue com ela, e depois tire as figuras para utilizá-las com EaselJS.

Neste caso vou utilizar dois arquivos PNG como fonte para as sequências do sprite.

Um monstro correndo, que consta de 10 sprites diferentes.

Nosso monstro, em repouso tem 11 imagens diferentes:

Nota: Estes exemplos não funcionam bem em Firefox 5.0, pelo visto devido a um erro na implementação de canvas. Os testes em IE9, IE10, Chrome 12, Opera 11 e Firefox Aurora 7.0 foram bem.

Tutorial 1: Criar o SpriteSheet e BitmapSequence

Vamos começar fazendo correr o monstro de um canto do canvas para o outro.

O primeiro passo é carregar toda a sequência completa contida dentro do arquivo PNG com este código:

var imgMonsterARun = new Image();

function init() {
//find canvas and load images, wait for last image to load
canvas = document.getElementById("testCanvas");

imgMonsterARun.onload = handleImageLoad;
imgMonsterARun.onerror = handleImageError;
imgMonsterARun.src = "img/MonsterARun.png";
}

Primeiro faremos uma chamada a este código para inicializar o conteúdo do jogo. Depois de carregá-lo podemos começar com o jogo propriamente.

EaselJS exibe um objeto chamado SpriteSheet para manejar o sprite. Assim, com este código:

var spriteSheet = new SpriteSheet(
imgMonsterARun, //image to use
64, //width of each sprite
64, //height of each sprite
{
walk_left: [0, 9]
});

? estou indicando que quero criar uma nova sequencia chamada "walk_left" (ir para a esquerda) que vai ser gerada com a imagem imgMonsterARun. Vamos dividir esta imagem em 10 frames com um tamanho de 64x64 pixels. Este é o objeto primário à hora de carregar nosso sprite e criar as sequencias. Poderíamos ter várias sequencias criadas a partir do mesmo arquivo PNG se quisermos, como se faz no exemplo de sprite chamado "rats" no site de EaselJS.

O seguinte que vamos necessitar é o objeto BitmapSequence, que nos ajuda a animar a sequencia e a posição dos sprites na tela.

Vamos dar uma olhada no código de inicialização deste BitmapSequence:

// create a BitmapSequence instance to display and play back the sprite sheet:
bmpSeq = new BitmapSequence(spriteSheet);

// set the registration point (the point it will be positioned and rotated around)
// to the center of the frame dimensions:
bmpSeq.regX = bmpSeq.spriteSheet.frameWidth/2|0;
bmpSeq.regY = bmpSeq.spriteSheet.frameHeight / 2 | 0;

// start playing the first sequence:
bmpSeq.gotoAndPlay("walk_left"); //animate

// set up a shadow. Note that shadows are ridiculously expensive. You could display hundreds
// of animated rats if you disabled the shadow.
bmpSeq.shadow = new Shadow("#454", 0, 5, 4);

bmpSeq.name = "monster1";
bmpSeq.speed = 1;
bmpSeq.direction = 90;
bmpSeq.vX = 3|0.5;
bmpSeq.vY = 0;
bmpSeq.x = 16;
bmpSeq.y = 32;

// have each monster start at a specific frame
bmpSeq.currentFrame = 0;
stage.addChild(bmpSeq);

O construtor do objeto BitmapSequence só necessita que lhe passemos como parâmetro o elemento SpriteSheet. Depois colocamos um nome à sequencia, definimos alguns parâmetros como a velocidade e a posição inicial do primeiro frame. Finalmente acrescentamos esta sequencia à lista de apresentação na tela utilizando o objeto Stage e seu método addChild().

No passo seguinte temos que ver o que queremos fazer no looping de animação. Este looping de animação se executa cada xxx milissegundos e nos permite atualizar a posição dos sprites. Para isto, EaselJS exibe o objeto Ticker que nos proporciona uma pulsação centralizada, emitindo uma batida cada certo número de milissegundos.

Tudo o que há que fazer é aderir ao evento da pulsação e implementar um método .tick() ao qual fazer a chamada correspondente.

Este código é para a instância de registro do evento:

Ticker.addListener(window);
// Best Framerate targeted (60 FPS)
Ticker.setInterval(17);
E aqui temos o código que se chamará a cada 17 ms (se é possível) para atualizar a posição de nosso monstro:
function tick() {
// Hit testing the screen width, otherwise our sprite would disappear
if (bmpSeq.x >= screen_width - 16) {
// We've reached the right side of our screen
// We need to walk left now to go back to our initial position
bmpSeq.direction = -90;
}

if (bmpSeq.x < 16) {
// We've reached the left side of our screen
// We need to walk right now
bmpSeq.direction = 90;
}

// Moving the sprite based on the direction & the speed
if (bmpSeq.direction == 90) {
bmpSeq.x += bmpSeq.vX;
bmpSeq.y += bmpSeq.vY;
}
else {
bmpSeq.x -= bmpSeq.vX;
bmpSeq.y -= bmpSeq.vY;
}

// update the stage:
stage.update();
}

O resultado final pode ser visto aqui: easelJSSpritesTutorial01 onde temos o código fonte completo.

Mas, espere! Temos dois problemas nesta animação!

  1. Os passos da animação não são muito bons, porque o personagem se move através de cada uma de suas imagens demasiado rápido
  2. O personagem só vai normalmente da direita para a esquerda, porque quando vai ao contrário caminha de costas!
Vamos arrumá-lo.

Tutorial 2: Como controlar a velocidade da animação e girar as figuras

A forma mais simples que encontrei de ajustar a velocidade da animação foi utilizar um operador de módulo para evitar desenhar/atualizar a sequencia a cada pulsação.

Neste momento há uma incidência aberta sobre isto em EaselJS 0.3.2. Vocês podem vê-lo em: https://github.com/gskinner/EaselJS/issues/60

Para que nosso personagem caminhe com normalidade da esquerda para a direita só temos que dar a volta a cada frame.

EaselJS exibe um objeto SpriteSheetUtils para fazer isto, e o método flip().
// {nameOfFlippedSequence:["derivativeSequence", flipHorizontally, flipVertically, optionNameOfNextSequence]}
spriteSheet = SpriteSheetUtils.flip(
spriteSheet,
{
walk_right: ["walk_left", true, false, null]
});

Con esto lo que hacemos básicamente es crear una secuencia derivada, llamada ?walk_right? que se basa en la anterior ?walk_left? pero a la que hemos rotado en sentido horizontal.

Finalmente, este es el código que ralentiza la velocidad de la animación y maneja cada una de las secuencias para que se ejecuten en base a la posición del personaje:

function tick() {
// To slow down the animation loop of the sprite, we're not redrawing during each tick
// With a Modulo 4, we're dividing the speed by 4
var speedControl = Ticker.getTicks() % 4;

if (speedControl == 0) {
// Hit testing the screen width, otherwise our sprite would disappear
if (bmpSeq.x >= screen_width - 16) {
// We've reached the right side of our screen
// We need to walk left now to go back to our initial position
bmpSeq.direction = -90;
bmpSeq.gotoAndPlay("walk_left")
}

if (bmpSeq.x < 16) {
// We've reached the left side of our screen
// We need to walk right now
bmpSeq.direction = 90;
bmpSeq.gotoAndPlay("walk_right");
}

// Moving the sprite based on the direction & the speed
if (bmpSeq.direction == 90) {
bmpSeq.x += bmpSeq.vX;
bmpSeq.y += bmpSeq.vY;
}
else {
bmpSeq.x -= bmpSeq.vX;
bmpSeq.y -= bmpSeq.vY;
}

// update the stage:
stage.update();
}
}

O resultado final pode ser visto em: easelJSSpritesTutorial02

Tutorial 3: Como carregar múltiplas sprites e jogar com múltiplas animações

Agora é o momento de carregar o estado ?tranquilo? de nosso monstro.

Aqui a ideia consiste em fazer que o monstro dê um pequeno passeio e depois fique quieto.

Teremos que carregar múltiplos arquivos PNG desde o servidor web. É muito importante esperar que todos os recursos estejam carregados, porque do contrário poderíamos estar tratando de desenhar na tela imagens que ainda não foram baixadas.

Uma maneira simples de fazê-lo é esta:

var numberOfImagesLoaded = 0;

var imgMonsterARun = new Image();
var imgMonsterAIdle = new Image();

function init() {
//find canvas and load images, wait for last image to load
canvas = document.getElementById("testCanvas");

imgMonsterARun.onload = handleImageLoad;
imgMonsterARun.onerror = handleImageError;
imgMonsterARun.src = "img/MonsterARun.png";

imgMonsterAIdle.onload = handleImageLoad;
imgMonsterAIdle.onerror = handleImageError;
imgMonsterAIdle.src = "img/MonsterAIdle.png";
}

function handleImageLoad(e) {
numberOfImagesLoaded++;

// We're not starting the game until all images are loaded
// Otherwise, you may start to draw without the resource and raise
// this DOM Exception: INVALID_STATE_ERR (11) on the drawImage method
if (numberOfImagesLoaded == 2) {
numberOfImagesLoaded = 0;
startGame();
}
}

Este código é bem simples. Por exemplo, não controla os erros adequadamente tratando de voltar a baixar a imagem no caso de que falhe a primeira tentativa..

Quando formos criar um jogo, teremos que escrever nossos próprios gestores de download de conteúdos se a biblioteca de JS que utilizamos não dispõe de um próprio.

Para acrescentar a sequencia de repouso do personagem e establecer os parâmetros de posição, necessitamos o mesmo tipo de código que já vimos antes:

var spriteSheetIdle = new SpriteSheet(
imgMonsterAIdle, //image to use
64, //width of each sprite
64, //height of each sprite
{
idle: [0, 10]
});

bmpSeqIdle = new BitmapSequence(spriteSheetIdle);

bmpSeqIdle.name = "monsteridle1";
bmpSeqIdle.speed = 1;
bmpSeqIdle.direction = 0;
bmpSeqIdle.x = 16;
bmpSeqIdle.y = 32;
Agora, no método tick(), o que temos que fazer é parar a animação uma vez que tenhamos chegado ao lado esquerdo da tela, e no seu lugar colocar em funcionamento a animação de repouso.

Este seria o código que para os pés de nosso monstro:

if (bmpSeq.x < 16) {
// We've reached the left side of our screen
// We need to walk right now
bmpSeq.direction = 90;
bmpSeq.gotoAndStop("walk_left");
stage.removeChild(bmpSeq);
bmpSeqIdle.gotoAndPlay("idle");
stage.addChild(bmpSeqIdle);
}

O resultado final pode ser visto aqui: easelJSSpritesTutorial03.
E isto é tudo, amigos! Se vocês sesentirem capazes de avançar mais, recomendo Jogos com HTML5; criação dos objetos básicos e gestão de colisões com with EaselJS.






Usuários :    login / registro

Manuais relacionados
Categorias relacionadas
O autor

Home | Sobre nós | Copyright | Anuncie | Entrar em contato