Desenvolvimento de jogos

Técnicas de desenho 2D e bibliotecas para jogos da Web

Michael Oneppo

Por muito tempo, havia realmente apenas uma forma de tornar jogos na Web interativos: Flash. Goste ou não, Flash tinha um sistema rápido de criação. Todos o usaram para criar animações, aventuras point-and-click e muitas outras experiências.

Quando os navegadores alinharam os padrões da Web com o HTML5, houve uma verdadeira explosão de opções para o desenvolvimento rápido, gráficos de alta qualidade — sem a necessidade de plug-ins. Este artigo apresentará uma pequena amostra dos métodos de desenho, bem como as tecnologias subjacentes e algumas bibliotecas para tornar mais fáceis de usar. Não abordarei bibliotecas especificamente destinadas para jogos. Há tantos deles que eu vou guardar essa discussão para outro artigo.

Padrões de desenho

Com o início do HTML5, três formas comuns de desenho em 2D surgiram: o Modelo de Objeto de Documento (DOM), a tela e o formato de Elementos Gráficos Vetoriais Escaláveis (SVG). Antes de passar para a pesquisa de bibliotecas que usam essas tecnologias, analisarei como cada uma funciona para entender melhor as vantagens e desvantagens de cada método.

Como era de se esperar, a maneira mais simples de desenhar gráficos em HTML é realmente com HTML. Criando um número de elementos de plano de fundo ou imagem e usando uma biblioteca jQuery, você pode fazer rapidamente sprites, que você pode se mover sem redesenhar a cena. O navegador fará isso para você. Esse tipo de estrutura é frequentemente chamada de gráfico da cena. No caso de HTML, o gráfico de cena é o DOM. Porque você usará o CSS para definir o estilo de suas sprites, você também pode usar transições e animações CSS para adicionar algum movimento suave à sua cena.

O principal problema com esse método é que ele depende do renderizador do DOM. Isso pode deixar as coisas mais lentas quando você tiver uma cena complexa. Eu não recomendaria usar mais do que algumas centenas de elementos. Portanto, algo mais complexo do que uma correspondência de três ou um jogo de plataforma pode ter problemas de desempenho. E um súbito aumento do número de elementos, como em um sistema de partículas, pode causar dificuldades na animação.

Outro problema com esse método é que você precisa usar o CSS para elementos de estilo. Dependendo de como você escreve CSS, pode ser muito lento ou rápido o suficiente. Finalmente, escrever código destinado para HTML pode ser difícil para mover para um sistema diferente, como o C++ nativo. Isso é importante se você deseja portar seu jogo para algo como um console. Aqui está um resumo das vantagens:

  • Cria a estrutura básica de uma página da Web
  • jQuery e outras bibliotecas facilitam a movimentação das coisas
  • Sprite são relativamente fáceis de configurar
  • Sistema de animação interno com transições e animações de CSS

E um resumo dos contras:

  • Muitos elementos pequenos podem retardá-lo
  • É necessário usar o CSS para elementos de estilo
  • Não há imagens vetoriais
  • Pode ser difícil portar para outras plataformas

Canvas do HTML5

O elemento canvas aborda muitos dos contras. Ele fornece um ambiente de processamento de modo imediato — uma faixa simples de pixels. Você informa o que desenhar em JavaScript e ele desenha imediatamente. Como está convertendo seus desenhos de comandos em pixels, você pode rapidamente acumular uma longa lista de comandos de desenho sem sobrecarregar o sistema. Você pode desenhar geometria, texto, imagens, gradientes e outros elementos. Para saber mais sobre como usar a tela para jogos, leia o artigo de David Catuhe em bit.ly/1fquBuo.

Então, qual é o lado ruim? Como a tela esquece o que desenhei no momento que está concluído, você precisa redesenhar a cena cada vez que desejar alterar. E se você quiser modificar uma forma de maneira complexa, como curvar ou animar, você precisa fazer os cálculos e redesenhar o item. Isso significa que você precisa manter muitos dados sobre sua cena em suas próprias estruturas de dados. Isso não é realmente um grande problema, considerando que há bibliotecas que tornam isso mais fácil. Se você realmente deseja fazer algo personalizado, lembre-se de que a tela não manterá as informações para você. Finalmente, a tela não inclui animações. É necessário desenhar sua cena em etapas sucessivas para criar uma animação suave. Aqui está um resumo das vantagens:

  • Desenha diretamente — cenas podem ser mais complexas
  • Oferece suporte a muitos elementos visuais diferentes

E um resumo dos contras:

  • Nenhuma memória inerente da cena; você precisa criá-la sozinho
  • Transformações complexas e animações devem ser feitas manualmente
  • Nenhum sistema de animação

SVG: Elementos gráficos vetoriais escaláveis

Como uma marcação baseada em XML para descrever visuais 2D, o SVG é semelhante ao HTML. A principal diferença é que o SVG é destinado para desenhar, enquanto o HTML é projetado principalmente para texto e layout. Como tal, o SVG tem alguns recursos avançados de desenho como formas suaves, animações complexas, deformações e até mesmo os filtros de imagem como borrar. Como o HTML, o SVG tem uma estrutura de gráfico da cena, portanto, você pode examinar elementos SVG, adicionar formas, alterar suas propriedades e não se preocupar em redesenho tudo. O navegador fará isso para você. O vídeo "Trabalhando com SVG em HTML5" do Channel 9 (bit.ly/1DEAWmh) explica mais.

Como o HTML, cenas complexas podem sobrecarregar o SVG. O SVG pode manipular alguma complexidade, mas não pode corresponder muito a complexidade proporcionada pelo uso de tela. Além disso, as ferramentas para manipulação de SVG podem ser complexas, embora haja outras ferramentas para simplificar o processo. Aqui está um resumo das vantagens:

  • Muitas opções de desenho como superfícies curvas e formas complexas
  • Estruturados sem precisar redesenhar

E um resumo dos contras:

  • Complexidade pode sobrecarregar
  • Difícil de manipular

Bibliotecas de desenho 2D

Agora que você sabe sobre os padrões disponíveis para desenhar na Web, posso dar uma olhada em algumas bibliotecas que podem facilitar o desenho e a animação. Vale a pena observar que raramente você desenha sem fazer algo mais com o desenho. Por exemplo, é geralmente necessário gráficos para reagir à entrada. Bibliotecas ajudam a tornar tarefas comuns associadas ao desenho mais fáceis.

KineticJS Deseja um gráfico da cena para tela? O KineticJS é uma biblioteca de tela extremamente poderosa que começa com um gráfico de cena e adiciona mais funcionalidade. Na linha de base, o KineticJS permite definir camadas na tela que contêm formas para desenhar. Por exemplo, a Figura 1 mostra como desenhar um círculo vermelho simples usando KineticJS.

Figura 1 Desenhando um círculo com KineticJS

// Points to a canvas element in your HTML with id "myCanvas"
var myCanvas = $('#myCanvas'); 
var stage = new Kinetic.Stage({
  // get(0) returns the first element found by jQuery,
  // which should be the only canvas element
  container: myCanvas.get(0),
    width: 800,
    height: 500
  });
   
var myLayer = new Kinetic.Layer({id: “myLayer”});
stage.add(myLayer);
var circle = new Kinetic.Ellipse({
  // Set the position of the circle
  x: 100,                                            
  y: 100,
   
  // Set the size of the circle
  radius: {x: 200, y: 200},
   
  // Set the color to red
  fill: '#FF0000'  
});
 
myLayer.add(circle);
stage.draw();

Você precisará chamar a linha final da Figura 1 sempre que desejar que a cena seja redesenhada. O KineticJS fará o resto, lembrando do layout da cena e garantindo que tudo seja desenhado corretamente.

Há algumas coisas interessantes no KineticJS que o tornam muito poderoso. Por exemplo, a propriedade fill de um objeto pode ser um grande número de itens, incluindo um gradiente:

fill: {
  start: {x: 0, y: 0},
  end: {x: 0, y: 200},
  colorStops: [0, '#FF0000', 1, '#00FF00']
},

Ou uma imagem:

// The "Image" object is built into JavaScript and
// Kinetic knows how to use it
fillPatternImage: new Image('path/to/an/awesome/image.png'),

O KineticJS também tem um sistema de animação, que permite mover as coisas criando um objeto Animation ou usando um objeto Tween para propriedades de transição em formas na sua cena. A Figura 2 mostra ambos os tipos de animação.

Figura 2 Animações usando KineticJS

// Slowly move the circle to the right forever
var myAnimation = new Kinetic.Animation(
  function(frame) {
    circle.setX(myCircle.getX() + 1);
  },
  myLayer);
 
// The animation can be started and stopped whenever
myAnimation.start();
// Increase the size of the circle by 3x over 3 seconds
var myTween = new Kinetic.Tween({
  node: circle,
  duration: 3,
  scaleX: 3.0,
  scaleY: 3.0
});
 
// You also have to initiate tweens
myTween.play();

O KineticJS é poderoso e amplamente usado, especialmente para jogos. Verifique o código, exemplos e documentação em kineticjs.com.

Paper.js Paper.js fornece mais do que apenas uma biblioteca para simplificar o desenho na tela. Ele fornece uma versão ligeiramente modificada do JavaScript chamado PaperScript para simplificar tarefas comuns de desenho. Ao incluir o PaperScript em seu projeto, vincule-o como um script comum, apenas com um tipo diferente de código:

<script type=“text/paperscript" src=“mypaperscript.js”>

Isso permite o Paper.js interpretar o código de modo ligeiramente diferente. Há realmente apenas duas partes. Primeiro, PaperScript tem dois objetos internos chamados Point e Size. O PaperScript inclui esses objetos para uso comum em suas funções e fornece a capacidade de adicionar, subtrair e multiplicar diretamente esses tipos. Por exemplo, para mover um objeto no PaperScript, você pode fazer isso:

var offset = new Point(10, 10);
 
var myCircle = new Path.Circle({
  center: new Point(300, 300),
  radius: 60
});
 
// Direct addition of Point objects!
myCircle.position += offset;

A segunda coisa que o Paper.js interpreta diferente está a resposta aos eventos. Considere que você escreve o código a seguir no JavaScript:

function onMouseDown(event) {
  alert("Hello!");
}

Isso não fará nada porque a função não está associada a qualquer evento do elemento. No entanto, escrevendo o mesmo código no PaperScript, o Paper.js detectará automaticamente essa função e irá associá-la ao evento para baixo do mouse. Saiba mais sobre isso em paperjs.org.

Fabric.js O Fabric.js é uma biblioteca de tela e com recursos, com a capacidade de combinar vários efeitos avançados e formas em uma página da Web sem muito código. Alguns recursos importantes incluem filtros de imagem, como remoção de plano de fundo, classes personalizadas para criar seus próprios objetos compostos e suporte a "desenho livres", onde você pode apenas desenhar na tela em vários estilos. O Fabric.js é semelhante ao KineticJS porque tem um gráfico de cena, exceto com uma estrutura mais concisa, que algumas pessoas preferem. Por exemplo, você não precisa nunca redesenhar a cena:

var canvas = new fabric.Canvas('myCanvas');
var circle = new fabric.Circle({
  radius: 200,
    fill: '#FF0000',
    left: 100,
    top: 100
});
 
// The circle will become immediately visible
canvas.add(circle);

Isso não é uma grande diferença, mas o Fabric.js fornece controles de renderização refinados que combinam o redesenho automático e manual. Para dar um exemplo, o aumento de um círculo no Fabric.js tem esta aparência:

circle.animate(
  // Property to animate
  'scale',
  // Amount to change it to
  3,
  {
    // Time to animate in milliseconds
    duration: 3000,
    // What's this?
    onChange: canvas.renderAll.bind(canvas)
  });

Ao animar algo no Fabric.js, você precisa saber o que fazer quando um valor é alterado. Na maior parte, você deseja redesenhar a cena. É o que o canvas.renderAll.bind(canvas) faz referência. Esse código retorna uma função que processará a cena inteira. No entanto, se você estiver animando muitos objetos dessa maneira, a cena seria desnecessariamente redesenhada uma vez para cada objeto. Em vez disso, você pode suprimir o redesenho da cena inteira e redesenhar as animações por conta própria. A Figura 3 demonstra essa abordagem.

Figura 3 Maior controle para redesenhar no Fabric.js

var needRedraw = true;
 
// Do things like this a lot, say hundreds of times
circle.animate(
  'scale',
  3,
  {
    duration: 3000,
       
    // This function will be called when the animation is complete
    onComplete: function() {
      needRedraw = false;
    }
  });
 
// This function will redraw the whole scene, and schedule the
// next redraw only if there are animations going
function drawAnimations() {
  canvas.renderAll();
  if (needRedraw) {
    requestAnimationFrame(drawAnimations);
  }
}
 
// Now draw the scene to show the animations
requestAnimationFrame(drawAnimations);

O Fabric.js fornece muitas personalizações, para que você possa otimizar o desenho somente quando precisar. Para alguns, isso pode ser difícil de fazer. No entanto, para muitos jogos complexos, isso pode ser um recurso fundamental. Confira mais em fabricjs.com.

Raphaël Raphaël é uma biblioteca de SVG útil que elimina a maioria da complexidade ao lidar com o SVG. Raphaël usa SVG quando disponível. Quando não estiver, Raphaël implementa SVG no JavaScript usando as tecnologias que estiverem disponíveis no navegador. Cada objeto gráfico criado no Raphaël também é um objeto DOM, com todos os recursos que os objetos DOM aproveitam, como associação de manipuladores de eventos e o acesso ao jQuery. O Raphaël também tem um sistema de animação que permite definir animações independentes dos objetos desenhados, permitindo a reutilização pesada:

var raphael = Raphael(0, 0, 800, 600);
 
var circle = raphael.circle(100, 100, 200);
circle.attr("fill", "red");
circle.animate({r: 600}, 3000);
 
// Or make a custom animation
var myAnimation = Raphael.animation(
  {r: 600},
  3000);
circle.animate(myAnimation);

Nesse código, em vez de desenhar um círculo, o Raphaël colocará um documento SVG na página com um elemento do círculo. Estranhamente, o Raphaël não oferece suporte ao carregamento de arquivos SVG. O Raphaël tem uma comunidade sofisticada, portanto, há um plug-in para ele disponível em bit.ly/1AX9n7q.

Snap.SVG O Snap.svg parece um pouco com o Raphaël:

var snap = Snap("#myCanvas"); // Add an SVG area to the myCanvas element
var circle = snap.circle(100, 100, 200);
circle.attr("fill", "#FF0000");
circle.animate({r: 600}, 1000);

Uma das principais diferenças é que o Snap.svg inclui importação direta de SVG:

Snap.load("myAwesomeSVG.svg");

A segunda diferença chave é que o Snap.svg fornece ferramentas internas poderosas para pesquisar e editar a estrutura SVG in-loco, se você conhece a estrutura do SVG com a qual você está trabalhando. Por exemplo, imagine que você quisesse tornar todos os grupos (marcas "g") em seu SVG invisíveis. Depois do SVG ser carregado, você deve adicionar essa funcionalidade em um retorno de chamada para o método load:

Snap.load("myAwesomeSVG.svg", function(mySVG) {
  mySVG.select("g").attr("opacity", 0);
});

O método select funciona muito como o seletor "$" do jQuery e é bem avançado. Veja o Snap.svg em snapsvg.io.

Um pouco mais: p5.js

Muitas dessas bibliotecas fornecem um pouco mais para tarefas comuns. Isso cria uma variedade de tecnologias para atender a uma ampla variedade de aplicativos, de desenho simples a mídia interativas e experiências de jogos complexos. O que mais há no meio espectro — algo mais do que as soluções de desenho simples, mas não exatamente um mecanismo de jogo completo?

Um projeto digno de nota é p5.js, que faz parte da linguagem de programação Processing popular (consulte processing.org). Essa biblioteca JavaScript fornece um ambiente interativo de mídia ao implementar o Processing no navegador. O p5.js consolida as tarefas mais comuns em um conjunto de funções que você precisa definir para responder a eventos do sistema, como o redesenho da cena ou entrada de mouse. É muito como o Paper.js, mas também com bibliotecas multimídia. Aqui está um exemplo que demonstra como essa abordagem resulta em um código mais conciso de gráficos:

float size = 20;
function setup() {
  createCanvas(600, 600);
}
 
function draw() {
  ellipse(300, 300, size, size);
  size = size + .1;
}

Esse programa cria um círculo que aumenta de tamanho até que ele ocupe a tela. Veja o p5.js em p5js.org.

Então, o que usar?

Claramente há prós e contras em telas e SVG. Também há bibliotecas que reduzem significativamente as desvantagens para qualquer uma das abordagens. Então, o que você deve usar? Em geral, eu não recomendaria usar o HTML simples. Há uma grande probabilidade de que um jogo moderno excederá a complexidade gráfica que ele pode suportar. Isso nos leva a escolher entre o SVG e a tela, que é difícil.

Para gêneros de jogos altamente diferentes, a resposta é um pouco mais fácil. Se você estiver criando um jogo com centenas de milhares de partículas, você desejará usar a tela. Se você estiver criando um jogo de aventura point-and-click com um estilo de quadrinhos, pode considerar o SVG.

Para a maioria dos jogos, ele não diminui no desempenho, como muitos fariam acreditar. Você pode gastar horas conversando sobre qual biblioteca usar. No final, porém, é o tempo no qual você poderia estar escrevendo o jogo.

Minha recomendação é fazer uma seleção com base em seus ativos de arte. Se você estiver fazendo suas animações de caractere no Adobe Illustrator ou Inkscape, por que converter cada quadro de animações para pixels? Use a arte de vetor nativamente. Não trabalhe tanto enchendo sua arte na tela.

Por outro lado, se sua arte é principalmente baseada em pixels ou se você vai gerar efeitos complexos em uma base por pixels, a tela é uma opção perfeita.

Mais uma opção

Se você procura o melhor desempenho possível e estiver disposto a lidar com um pouco mais de complexidade para que isso aconteça, é altamente recomendável que você considere o Pixi.js. Ao contrário de qualquer outra coisa mostrei neste artigo, o Pixi.js usa WebGL para renderização 2D. Isso fornece algumas das grandes melhorias de desempenho.

A API não é tão simples como os outros descritos aqui, mas não é uma grande diferença. Além disso, o WebGL não é suportado em tantos navegadores quanto as outras tecnologias. Portanto, em sistemas mais antigos o Pixi.js não tem nenhuma vantagem de desempenho. De qualquer maneira, faça sua escolha e aproveite o processo.


Michael Oneppo é um tecnólogo criativo e antigo gerente de programas da Microsoft na equipe do Direct3D. Seus esforços recentes incluem trabalhar como CTO na tecnologia sem fins lucrativos Library For All e explorar um mestrado no Programa de Telecomunicações Interativas da NYU.​

Agradecemos aos seguintes especialistas técnicos da Microsoft pela revisão deste artigo: Shai Hinitz