Desenvolvimento de Jogos 2D em HTML5: Gráfico e animação

Desenhando na tela e colocando elementos em movimento

Nesta série, o especialista em HTML5 David Geary mostra como implementar um vídeo game 2D em HTML5, uma etapa por vez. Esta parte do artigo aborda gráfico Canvas e animação HTML5. Você verá como desenhar os gráficos do jogo e como colocá-los em movimento. Você também verá a melhor maneira de animar com HTML5, como rolar o plano de fundo e como implementar paralaxe para simular três dimensões.

David Geary, Author and speaker, Clarity Training, Inc.

David GearyAutor de Core HTML5 Canvas, David Geary também é cofundador do Grupo de Usuários de HTML5 de Denver e autor de oito livros sobre Java, incluindo best-sellers sobre Swing e JavaServer Faces. David é palestrante frequente em conferências, incluindo JavaOne, Devoxx, Strange Loop, NDC e OSCON, e foi JavaOne Rock Star por três vezes. Ele escreveu a série de artigos JSF 2 fu e GWT fu para o developerWorks. Siga David no Twitter em @davidgeary.



23/Out/2012

Gráfico e animação são os aspectos mais fundamentais de qualquer vídeo game, portanto, neste artigo, eu começo uma breve visão geral da API 2D Canvas, seguida de uma discussão da implementação da animação central do Snail Bait. Neste artigo, você aprenderá a:

  • Desenhar imagens e primitivas gráficas em uma tela
  • Criar uma animação suave, sem tremulação
  • Implementar o loop de jogo
  • Monitorar a taxa de animação em quadros por segundo
  • Rolar o plano de fundo do jogo
  • Usar paralaxe para simular três dimensões
  • Implementar movimento baseado em tempo

O resultado final do código discutido neste artigo está na Figura 1:

Figura 1. Rolando o plano de fundo e monitorando a taxa de quadros
Rolando o plano de fundo e monitorando a taxa de quadros

O plano de fundo e as plataformas rolam horizontalmente. As plataformas estão no primeiro plano, portanto, elas movem-se notadamente mais rápido que o plano de fundo, criando um leve efeito de paralaxe. Quando o jogo começa, o plano de fundo rola da direita para a esquerda. No final do nível, o plano de fundo e as plataformas invertem a direção.

Nesse estágio do desenvolvimento, a corredora não se move. Além disso, o jogo ainda não tem detecção de colisão, portanto, a corredora flutua no ar quando não há plataformas embaixo dela.

No final, ícones acima e à esquerda da tela do jogo indicarão o número de vidas restantes (como mostra a Figura 1 no primeiro artigo desta série). Por ora, o jogo exibe a taxa de animação atual em quadros por segundo nesse local.

Gráficos de modo imediato

Canvas é um sistema gráfico de modo imediato, o que significa que ele desenha imediatamente tudo que é especificado e esquece imediatamente. Outros sistemas gráficos, como Scalable Vector Graphics (SVG), implementam gráficos de modo retido, o que significa que mantêm uma lista de objetos a desenhar. Sem o gasto adicional de manter uma lista de exibição, Canvas é mais rápido que SVG. No entanto, caso o desenvolvedor queira manter uma lista de objetos que os usuários possam manipular, é necessário implementar essa funcionalidade por si mesmo no Canvas.

Antes de continuar, você talvez queira experimentar o jogo como ele está na Figura 1; será mais fácil entender o código se fizer isso. (Consulte Download para obter a implementação de Snail Bait desta parte do artigo.)

Visão geral de Canvas HTML5

O contexto 2D do Canvas oferece uma extensa API gráfica que permite implementar tudo, de editores de texto a vídeo games de plataforma. No momento em que este artigo foi escrito, a API continha mais de 30 métodos, mas Snail Bait usa apenas alguns deles, mostrados na Tabela 1:

Tabela 1. Métodos de contexto 2D do Canvas usados por Snail Bait
MétodoDescrição
drawImage() Desenha uma imagem, total ou parcialmente, em um local específico na tela. Também pode desenhar em outra tela ou quadro de um elemento video.
save() Salva atributos de contexto em uma pilha.
restore() Captura atributos da pilha e aplica ao contexto.
strokeRect() Desenha um retângulo não preenchido.
fillRect() Preenche um retângulo.
translate() Converte o sistema de coordenadas. É um método eficiente, que pode ser útil em vários cenários. Toda a rolagem em Snail Bait é implementada com essa única chamada de método.

Gráficos baseados em caminhos

Como Cocoa da Apple e Illustrator da Adobe, a API Canvas é baseada em caminhos, o que significa que, para desenhar primitivas gráficas em uma tela, o desenvolvedor cria um caminho e traça ou preenche esse caminho. Os métodos strokeRect() e fillRect() são métodos de conveniência que traçam ou preenchem um retângulo, respectivamente.

Tudo em Snail Bait é uma imagem, exceto as plataformas. O plano de fundo, a corredora e todos os heróis e vilões são imagens que o jogo desenha com o método drawImage().

Ao final, Snail Bait usará uma spritesheet — uma única imagem contendo todos os gráficos do jogo — mas, por ora, eu uso imagens separadas para o plano de fundo e a corredora. Eu desenho a corredora com a função mostrada na Listagem 1:

Listagem 1. Desenhando a corredora
function drawRunner() {
   context.drawImage(runnerImage,                                        // image
                     STARTING_RUNNER_LEFT,                               // canvas left
                     calculatePlatformTop(runnerTrack) - RUNNER_HEIGHT); // canvas top
}

A função drawRunner() passa três argumentos para drawImage(): uma imagem e as coordenadas esquerda e superior para desenhar a imagem na tela. A coordenada esquerda é uma constante, enquanto a coordenada superior é determinada pela plataforma na qual a corredora reside.

Eu desenho o plano de fundo de forma semelhante, como mostra a Listagem 2:

Listagem 2. Desenhando o plano de fundo
function drawBackground() {
   context.drawImage(background, 0, 0);
}

O versátil método drawImage()

É possível desenhar uma imagem inteira, ou qualquer área retangular dentro de uma imagem, em qualquer lugar dentro de uma tela com o método drawImage() do contexto 2D de Canvas, podendo opcionalmente ajustar a escala da imagem nessa ação. Além de imagens, também é possível desenhar o conteúdo de outra tela ou do quadro atual de um elemento video com drawImage(). É apenas um método, mas drawImage() facilita implementações simples de aplicativos interessantes que seriam difíceis de implementar de outra forma, como software de edição de vídeo.

A função drawBackground() na Listagem 2 desenha a imagem de plano de fundo em (0,0) na tela. Ainda neste artigo, modificarei a função para rolar o plano de fundo.

Desenhar as plataformas, que não são imagens, exige uso mais extenso da API Canvas, como mostra a Listagem 3:

Listagem 3. Desenhando plataformas
var platformData = [
    // Screen 1.......................................................
    {
       left:      10,
       width:     230,
       height:    PLATFORM_HEIGHT,
       fillStyle: 'rgb(150,190,255)',
       opacity:   1.0,
       track:     1,
       pulsate:   false,
    },
    ...
 ],
 ...

function drawPlatforms() {
   var data, top;

   context.save(); // Save the current state of the context

   context.translate(-platformOffset, 0); // Translate the coord system for all platforms
   
   for (var i=0; i < platformData.length; ++i) {
      data = platformData[i];
      top = calculatePlatformTop(data.track);

      context.lineWidth   = PLATFORM_STROKE_WIDTH;
      context.strokeStyle = PLATFORM_STROKE_STYLE;
      context.fillStyle   = data.fillStyle;
      context.globalAlpha = data.opacity;

      context.strokeRect(data.left, top, data.width, data.height);
      context.fillRect  (data.left, top, data.width, data.height);
   }

   context.restore(); // Restore context state saved above
}

O JavaScript na Listagem 3 define um array chamado platformData. Cada objeto no array representa metadados que descrevem uma plataforma individual.

A função drawPlatforms() usa os métodos strokeRect() e fillRect() do contexto do Canvas para desenhar os retângulos das plataformas. As características desses retângulos — que são armazenados nos objetos no array platformData— são usados para definir o estilo de preenchimento do contexto e o atributo globalAlpha, que define a opacidade de tudo que é desenhado depois na tela.

A chamada para context.translate() converte o sistema de coordenadas da tela — mostrado na Figura 2 — por um número especificado de pixels na direção horizontal. Essa conversão e as configurações de atributo são temporárias, pois são feitas entre chamadas para context.save() e context.restore().

Figura 2. O sistema padrão de coordenadas de Canvas
O sistema padrão de coordenadas de Canvas

Por padrão, a origem do sistema de coordenadas é o cano superior esquerdo da tela. É possível mover a origem do sistema de coordenadas com context.translate().

Eu falo sobre a rolagem do plano de fundo com context.translate() em Rolando o plano de fundo. Mas, nesse momento, você sabe quase tudo de que precisa sobre Canvas de HTML5 para implementar Snail Bait. Para o resto da série, irei tratar de outros aspectos do desenvolvimento de jogos em HTML5, começando com a animação.


Animações em HTML5

Basicamente, implementar animações é fácil: você desenha repetidamente uma sequência de imagens, que fazem parecer que os objetos são animados de alguma forma. Isso significa que é necessário implementar um loop que desenha periodicamente uma imagem.

Tradicionalmente, loops de animação eram implementados em JavaScript com setTimeout() ou, como ilustra a Listagem 4, setInterval():

Listagem 4. Implementando animações com setInterval()
setInterval( function (e) { // Don't do this for time-critical animations
   animate();               // A function that draws the current animation frame
}, 1000 / 60);              // Approximately 60 frames/second (fps)

Boa prática

Nunca use setTimeout() ou setInterval() para animações de tempo crítico.

O código na Listagem 4 irá sem dúvida produzir uma animação, ao chamar repetidamente a função animate() que desenha o próximo quadro de animação; no entanto, os resultados podem não ser satisfatórios, pois setInterval() e setTimeout() não sabem nada sobre animação. (Nota: É necessário implementar a função animate(), que não é parte da API Canvas.)

Na Listagem 4, eu defino o intervalo para 1000/60 milissegundos, que é igual a aproximadamente 60 quadros por segundo. Esse número é uma estimativa para a taxa de quadros ideal, e pode não ser uma boa estimativa. No entanto, como setInterval() e setTimeout() não sabem nada sobre animação, cabe a mim especificar a taxa de quadros. Seria melhor se o navegador especificasse a taxa de quadros, pois ele certamente sabe melhor do que eu quando desenhar o próximo quadro de animação.

Há uma desvantagem ainda maior no uso de setTimeout e setInterval(). Embora os intervalos de tempo passados para eles sejam especificados em milissegundos, os métodos não têm precisão de milissegundo; na verdade, de acordo com a especificação HTML, esses métodos — na tentativa de economizar recursos — podem alterar generosamente o intervalo especificado.

Para evitar essas desvantagens, não se deve usar setTimeout() e setInterval() para animações com tempo crítico. Em vez disso, use requestAnimationFrame().

requestAnimationFrame()

Na especificação Timing control for script-based animations (consulte Recursos), a W3C define um método no objeto window chamado requestAnimationFrame(). Diferentemente da setTimeout() ou setInterval(), requestAnimationFrame() destina-se especificamente a implementar animações. Por isso, não tem as mesmas desvantagens que setTimeout() e setInterval(). Também é simples de usar, como a Listagem 5 ilustra:

Listagem 5. Implementando animações com requestAnimationFrame()
function animate(time) {           // Animation loop
   draw(time);                     // A function that draws the current animation frame
   requestAnimationFrame(animate); // Keep the animation going
};

requestAnimationFrame(animate);    // Start the animation

Passamos a requestAnimationFrame() uma referência a uma função de retorno de chamada e, quando o navegador estiver pronto para desenhar o próximo quadro de animação, ele chamará esse retorno de chamada. Para manter a animação, o retorno de chamada também chama requestAnimationFrame().

Como é possível ver na Listagem 5, o navegador passa um parâmetro time para a função de retorno de chamada. Você talvez pergunte-se o que o parâmetro time significa. É a hora atual? É o momento no qual o navegador desenhará o próximo quadro de animação?

Surpreendentemente, não há definição desse tempo. A única coisa de que podemos ter certeza é que, em um dado navegador, ele representa sempre a mesma coisa, portanto, pode ser usado para calcular o tempo decorrido entre quadros, como eu mostro em Calculando a taxa de animação em FPS.

Um polyfill de requestAnimationFrame()

Em muitos aspectos, HTML5 é a utopia dos programadores. Livre de APIs proprietárias, os desenvolvedores usam HTML5 para implementar aplicativos que são executados em várias plataformas, no onipresente navegador. As especificações avançam rápido, incorporando constantemente novas tecnologias e refinando a funcionalidade existente.

Polyfills: Programando para o futuro

No passado, a maioria dos softwares multiplataformas eram implementados para o mínimo denominador comum. Polyfills mudam isso: eles dão acesso a recursos avançados caso estejam disponíveis e retornam a uma implementação com menos capacidade quando necessário.

A nova tecnologia, no entanto, geralmente entra na especificação através de funcionalidade já existente em navegadores específicos. Os fornecedores de navegador geralmente prefixam essa funcionalidade, de modo que não interfira com a implementação de outro navegador; requestAnimationFrame(), por exemplo, foi originalmente implementado pelo Mozilla como mozRequestAnimationFrame(). Em seguida, foi implementado pelo WebKit, que deu à sua função o nome de webkitRequestAnimationFrame(). Por fim, o W3C padronizou como requestAnimationFrame().

Implementações prefixadas por fornecedores e graus variados de suporte para implementações padrão tornam as novas funcionalidades difíceis de usar, portanto, a comunidade HTML5 inventou algo chamado de polyfill. Polyfills determinam o nível de suporte de um navegador para um recurso em particular e dão acesso direto a ele, caso o navegador o implemente, ou a uma implementação temporária, que faz o melhor para imitar a funcionalidade padrão.

Polyfills são simples de usar, mas podem ser complicadas de implementar. A Listagem 6 mostra a implementação de um polyfill para requestAnimationFrame():

Listagem 6. Polyfill de requestNextAnimationFrame()
// Reprinted from Core HTML5 Canvas

window.requestNextAnimationFrame =
   (function () {
      var originalWebkitRequestAnimationFrame = undefined,
          wrapper = undefined,
          callback = undefined,
          geckoVersion = 0,
          userAgent = navigator.userAgent,
          index = 0,
          self = this;

      // Workaround for Chrome 10 bug where Chrome
      // does not pass the time to the animation function
      
      if (window.webkitRequestAnimationFrame) {
         // Define the wrapper

         wrapper = function (time) {
           if (time === undefined) {
              time = +new Date();
           }
           self.callback(time);
         };

         // Make the switch
          
         originalWebkitRequestAnimationFrame = window.webkitRequestAnimationFrame;    

         window.webkitRequestAnimationFrame = function (callback, element) {
            self.callback = callback;

            // Browser calls the wrapper and wrapper calls the callback
            
            originalWebkitRequestAnimationFrame(wrapper, element);
         }
      }

      // Workaround for Gecko 2.0, which has a bug in
      // mozRequestAnimationFrame() that restricts animations
      // to 30-40 fps.

      if (window.mozRequestAnimationFrame) {
         // Check the Gecko version. Gecko is used by browsers
         // other than Firefox. Gecko 2.0 corresponds to
         // Firefox 4.0.
         
         index = userAgent.indexOf('rv:');

         if (userAgent.indexOf('Gecko') != -1) {
            geckoVersion = userAgent.substr(index + 3, 3);

            if (geckoVersion === '2.0') {
               // Forces the return statement to fall through
               // to the setTimeout() function.

               window.mozRequestAnimationFrame = undefined;
            }
         }
      }
      
      return window.requestAnimationFrame   ||
         window.webkitRequestAnimationFrame ||
         window.mozRequestAnimationFrame    ||
         window.oRequestAnimationFrame      ||
         window.msRequestAnimationFrame     ||

         function (callback, element) {
            var start,
                finish;


            window.setTimeout( function () {
               start = +new Date();
               callback(start);
               finish = +new Date();

               self.timeout = 1000 / 60 - (finish - start);

            }, self.timeout);
         };
      }
   )
();

Polyfill: a definição

A palavra polyfill é uma mistura das palavras inglesas polymorphism (polimorfismo) e backfill (reaterramento). Assim como o polimorfismo, polyfills selecionam o código apropriado no tempo de execução e preenchem as lacunas da funcionalidade ausente.

O polyfill implementado na Listagem 6 conecta uma função chamada requestNextAnimationFrame() ao objeto window. A inclusão de Next no nome da função diferencia da função requestAnimationFrame() subjacente.

A função que o polyfill designa a requestNextAnimationFrame() é requestAnimationFrame() se o navegador suportá-la, ou uma implementação com prefixo de fornecedor. Caso o navegador não suporte nenhuma das duas, a função é uma implementação ad hoc que usa setTimeout() para imitar requestAnimationFrame() o melhor que puder.

Quase toda a complexidade do polyfill envolve lidar com dois erros e constitui o código antes da instrução return. O primeiro erro envolve o Chrome 10, que passa um valor undefined para o tempo. O segundo erro envolve o Firefox 4.0, que restringe as taxas de quadro a 35-40 quadros por segundo.

Embora a implementação do polyfill requestNextAnimationFrame() seja interessante, não é necessário entendê-la. É necessário apenas saber usar, como eu mostro na próxima seção.


O loop de jogo

Agora que cuidamos dos pré-requisitos de gráficos e animações, é hora de colocar Snail Bait em movimento. Para começar, eu incluo o JavaScript para requestNextAnimationFrame() no HTML do jogo, como mostra a Listagem 7:

Listagem 7. O HTML
<html>
   ...

   <body>
      ...

      <script src='js/requestNextAnimationFrame.js'></script>
      <script src='game.js'></script>
   </body>
</html>

A Listagem 8 mostra o loop de animação do jogo, chamado de loop de jogo:

Listagem 8. O loop de jogo
var fps;

function animate(now) { 
   fps = calculateFps(now); 
   draw();
   requestNextAnimationFrame(animate);
} 
          
function startGame() {
   requestNextAnimationFrame(animate);
}

A função startGame(), que é chamada pelo manipulador de eventos onload da imagem de plano de fundo, inicia o jogo ao chamar o polyfill requestNextAnimationFrame(). Quando é hora de desenhar o primeiro quadro de animação do jogo, o navegador chama a função animate().

A função animate() calcula a taxa de quadro da animação, dada a hora atual. (Consulte requestAnimationFrame() para mais sobre o valor de time.) Após calcular a taxa de quadros, animate() chama uma função draw() que desenha o próximo quadro de animação. Posteriormente, animate() chama requestNextAnimationFrame() para manter a animação.

Calculando a taxa de animação em FPS

A Listagem 9 mostra como Snail Bait calcula sua taxa de quadros e como atualiza a leitura de taxa de quadros mostrada na Figura 1:

Listagem 9. Calculando fps e atualizando o elemento fps
var lastAnimationFrameTime = 0,
    lastFpsUpdateTime = 0,
    fpsElement = document.getElementById('fps');

function calculateFps(now) {
   var fps = 1000 / (now - lastAnimationFrameTime);
   lastAnimationFrameTime = now;

   if (now - lastFpsUpdateTime > 1000) {
      lastFpsUpdateTime = now;
      fpsElement.innerHTML = fps.toFixed(0) + ' fps';
   }

   return fps; 
}

A taxa de quadros é apenas o período decorrido desde o último quadro de animação, portanto, alguém poderia argumentar que é quadro por segundo e não quadros por segundo, o que não é exatamente uma taxa. É possível adotar uma abordagem mais rigorosa e manter uma taxa de quadros média por vários quadros, mas eu não considero isso necessário. Aliás, o tempo decorrido desde o último quadro de animação é exatamente o que eu precisarei em Movimento baseado em tempo.

A Listagem 9 também mostra uma técnica de animação importante: realizar uma tarefa em uma taxa menor que a taxa de animação. Se eu atualizar a leitura de quadros/segundo a cada quadro de animação, ela ficará ilegível, pois sempre estará sendo modificada. Em vez disso, eu atualizo a leitura uma vez por segundo.

Com o loop de jogo estabelecido e a taxa de quadros à mão, eu agora estou pronto para rolar o plano de fundo.


Rolando o plano de fundo

O plano de fundo de Snail Bait, mostrado na Figura 3, rola devagar na direção horizontal:

Figura 3. A imagem de plano de fundo
A imagem de plano de fundo

O plano de fundo rola suavemente porque as suas extremidades esquerda e direita são idênticas, como mostra a Figura 4:

Figura 4. Extremidades idênticas criam conversões suaves (esquerda: extremidade direita; direita: extremidade esquerda)
Extremidades idênticas criam conversões suaves (esquerda: extremidade direita; direita: extremidade esquerda)

Snail Bait desenha o plano de fundo duas vezes para rolar infinitamente, como mostra a Figura 5. Inicialmente, como mostra a captura de tela superior na Figura 5, a imagem de plano de fundo na esquerda está inteiramente na tela, enquanto a da direita está inteiramente fora da tela. Com o passar do tempo, o plano de fundo rola, como mostram as capturas de tela intermediária e inferior na Figura 5:

Figura 5. Rolando da direita para a esquerda: as áreas transparentes representam as partes das imagens fora da tela
Rolando da direita para a esquerda: as áreas transparentes representam as partes das imagens fora da tela

A Listagem 10 mostra o código relacionado à Figura 5. A função drawBackground() desenha a imagem duas vezes, sempre nos mesmos locais. A rolagem aparente é o resultado da conversão constante do sistema de coordenadas da tela para a esquerda, o que faz o plano de fundo parecer rolar para a direita.

(Aqui está uma maneira de entender a aparente contradição de converter para a esquerda, mas rolar para a direita: imagine a tela como um porta-retratos vazio em cima de uma longa folha de papel. O papel é o sistema de coordenadas, e convertê-lo para a esquerda é como deslizá-lo para a esquerda abaixo do porta-retratos [tela]. Por isso, a tela parece mover-se para a direita.)

Listagem 10. Rolando o plano de fundo
var backgroundOffset; // This is set before calling drawBackground()

function drawBackground() {
   context.translate(-backgroundOffset, 0);

   // Initially onscreen:
   context.drawImage(background, 0, 0);

   // Initially offscreen:
   context.drawImage(background, background.width, 0);

   context.translate(backgroundOffset, 0);
}

A função setBackground() converte o contexto de tela pelo número de pixels definido em -backgroundOffset na direção horizontal. Se backgroundOffset é positivo, o plano de fundo rola para a direita; se é negativo, rola para a esquerda.

Após converter o plano de fundo, drawBackground() desenha o plano de fundo duas vezes e, em seguida, converte o contexto de volta para onde estava antes de drawBackground() ser chamada.

Resta ainda um cálculo aparentemente trivial: calcular backgroundOffset, que determina quanto converter o sistema de coordenadas da tela para cada quadro de animação. Embora o cálculo em si seja trivial, ele tem grande significância, por isso eu o discuto a seguir.


Movimento baseado em tempo

A taxa de quadros da animação varia, mas não se pode deixar que essa variação afete a taxa na qual a animação avança. Por exemplo, Snail Bait rola o plano de fundo a 42 pixels/segundo independentemente da taxa de quadros subjacente da animação. As animações devem ser baseadas em tempo, o que significa que velocidades são especificadas em pixels/segundo, e não podem depender da taxa de quadros.

Usar movimento baseado em tempo para calcular o número de pixels para mover um objeto em um dado quadro é simples: Divida a velocidade pela taxa de quadros atual. A velocidade (pixels/segundo) dividida pela taxa de quadros (quadros/segundo) resulta em pixels/quadro, o que significa o número de pixels necessário para mover algo para o quadro atual.

Boa prática

A taxa de animação deve ser independente da taxa de quadros.

A Listagem 11 mostra como Snail Bait usa movimento baseado em tempo para calcular o deslocamento do plano de fundo:

Listagem 11. Definindo o deslocamento do plano de fundo
var BACKGROUND_VELOCITY = 42, // pixels / second
    bgVelocity = BACKGROUND_VELOCITY;

function setBackgroundOffset() {
   var offset = backgroundOffset + bgVelocity/fps; // Time-based motion

   if (offset > 0 && offset < background.width) {
      backgroundOffset = offset;
   }
   else {
      backgroundOffset = 0;
   }
}

Para calcular o número de pixels para mover o plano de fundo para o quadro atual, a função setBackgroundOffset() divide a velocidade do plano de fundo pela taxa de quadros atual. Em seguida, adiciona esse valor ao deslocamento atual do plano de fundo.

Para rolar continuamente o plano de fundo, setBackgroundOffset() reconfigura o deslocamento do plano de fundo para 0 quando tornar-se menor que 0 ou maior que a largura do plano de fundo.


Paralaxe

Se você já esteve no banco do passageiro de um carro em movimento e viu sua mão cortar os postes de luz em alta velocidade, você sabe que as coisas próximas movem-se mais rápido que as coisas distantes. Isso se chama paralaxe.

Snail Bait é um jogo de plataforma 2D, mas usa um leve efeito de paralaxe para fazer parecer que as plataformas estão mais perto do jogador do que o plano de fundo. Para implementar essa paralaxe, o jogo rola as plataformas notadamente mais rápido que o plano de fundo.

A Figura 6 mostra como Snail Bait implementa a paralaxe. A captura de tela superior mostra o plano de fundo em um momento particular, enquanto a captura inferior mostra o plano de fundo alguns quadros de animação depois. Nessas duas capturas de tela, é possível ver que as plataformas moveram-se muito mais que o plano de fundo no mesmo período.

Figura 6. Paralaxe: as plataformas (próximas) rolam mais rápido que o plano de fundo (distante)
Paralaxe: as plataformas (próximas) rolam mais rápido que o plano de fundo (distante)

A Listagem 12 mostra as funções que definem velocidades e deslocamentos das plataformas:

Listagem 12. Definindo velocidades e deslocamentos das plataformas
var PLATFORM_VELOCITY_MULTIPLIER = 4.35; 

function setPlatformVelocity() {
   // Platforms move 4.35 times as fast as the background
   platformVelocity = bgVelocity * PLATFORM_VELOCITY_MULTIPLIER; 
}

function setPlatformOffset() {
   platformOffset += platformVelocity/fps; // Time-based motion
}

Lembre-se da Listagem 8, que contém o loop de jogo de Snail Bait. Esse loop consiste em uma função animate() que o navegador chama quando é hora de desenhar o próximo quadro de animação do jogo. Essa função animate(), por sua vez, chama uma função draw() que desenha o próximo quadro de animação. O código da função draw() nesse estágio de desenvolvimento está na Listagem 13:

Listagem 13. A função draw()
function setOffsets() {
   setBackgroundOffset();
   setPlatformOffset();
}

function draw() {
   setPlatformVelocity();
   setOffsets();

   drawBackground();

   drawRunner();
   drawPlatforms();
}

A função draw() define a velocidade da plataforma e os deslocamentos do plano de fundo e das plataformas. Em seguida, ela desenha o plano de fundo, a corredora e as plataformas.


Próxima vez

No próximo artigo, mostrarei como encapsular o código de Snail Bait em um objeto JavaScript, para evitar colisões de namespace. Também mostrarei como pausar o jogo, incluindo como pausar automaticamente quando a janela perde o foco, e como reiniciar o jogo com um contador quando a janela reganha o foco. Você também verá como controlar a corredora do jogo com o teclado. Também aprenderá a usar transições de CSS e injetar funcionalidade no loop de jogo. Até a próxima.


Download

DescriçãoNomeTamanho
Sample codej-html5-game2.zip737KB

Recursos

Aprender

Obter produtos e tecnologias

  • Replica Island: O código fonte desse popular vídeo game de plataforma de software livre para Android está disponível para download.

Discutir

  • Participe da Comunidade do developerWorks. Entre em contato com outros usuários do developerWorks, enquanto explora os blogs, fóruns, grupos e wikis orientados ao desenvolvedor.

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java, Software livre
ArticleID=842140
ArticleTitle=Desenvolvimento de Jogos 2D em HTML5: Gráfico e animação
publish-date=10232012