terça-feira, 4 de outubro de 2016

Jogo rápido: pixi + browserify + budo pra ser feliz

Good news everyone! hoje vamos prototipar um joguinho!

Sério mesmo.

Anote aí nossos ingredientes iniciais:

  1. Node.js rolando na sua máquina todo azeitado
  2. Um editor de códigos bacanudo. Pode ser o Visual Studio Code.
  3. O resto pegamos no caminho pro mercado
Um jogo é um programa (um apêpê se você for desses) como qualquer outro, exceto que ele demanda talentos que muitas vezes excedem o kit standard do programador. Mas se você for se bandear para a coisa isso não vai te impedir.

Comecemos pensando (olha as novidade!) como é esse jogo.
.
.
.
.
Mais um momento senhor.
.
.
.
.
Mais um momento.
.
.
Pronto.

  1. Neste jogo você deve impedir que um et/zumbi//monstro atravesse a fronteira. 
  2. Ao clicar/tocar (mencionei que o jogo deve rodar bem mobile ou desktop?) você dispara contra o monstro, eliminando assim este inimigo do jogo..
  3. Se um monstro conseguir cruzar o caminho, fim de jogo.
É só isso.

Agora vamos para as estruturas básicas de projeto. Crie uma pasta chamada zombiedefense etdefense, entre nela e largue um npm init nela:

Agora sua pasta de projeto conta com o arquivo package.json, evidência de que isso é uma pasta de projeto node.

Instale o pixi.js, que irá resolver todas as partes chatas e nos garantir crescimento saudável:

npm install pixi.js --save

Não esqueça do --save, caso contrário o npm não salva no package.json o pixi como dependência.

Temos ainda duas dependências de desenvolvimento:

npm install browserify budo --save-dev

O package.json deve estar parecido com isso aqui:

Agora é hora de criarmos nosso ponto de entrada. Abra aí o seu editor também e, na pasta do projeto, crie um index.js que vamos começar arrepiando o require do pixi.js:

Como? Por que PIXI e não pixi?

Como você sabe, o javascript precede toda a space-opera do node, commons.js e tudo mais que nasceu a favor ou contra  isso daí

O pixi.js nasceu num momento de transição e enquanto entidade global ele era declarado assim. Em letras garrafais.

Em sua versão browserifizada ele exporta um global com estas letras garrafais. A consequência disso é que, após o primeiro require("pixi.js"), você terá acesso ao PIXI global em qualquer outro módulo.

E já podemos testar! 

No terminal mande um npm run dev e:

Isso significa que você não ajeitou os scripts do npm ainda. Sim, agora você sabe como é o erro quando não temos isso acertado.

Abra o package.json e vamos criar o tal do script dev:
O que adicionamos: 

Na seção de scripts do package.json colocamos o comando abaixo sob a chave "dev":

budo index.js -l -o -s build.js

O budo é um servidor de prototipação rápida de aplicativos javascipt modernos especialmente pensado para se integrar com o browserify, que por sua vez é um module-loader que permite você organizar seu código em módulos, coisa que os browsers ainda estão prometendo entregar.

O mais bacana desse setup é ter reload automático de código e modularização. Resolvidos estes aspectos básicos de trabalho, hora de fazer o jogo acontecer, vamos retornar ao index.js e debater um pouco sobre os conceitos do pixi.js.

Primeiro que você vai precisar de um renderer:

var renderer = PIXI.autoDetectRenderer(480, 640);

Em segundo lugar, este renderer tem um atributo chamado view que deve ser anexado ao documento html:

document.body.appendChild(renderer.view);

No terminal mande um npm run dev outra vez depois que o navegador abrir, mande um ctrl+shift+i para inspecionar o documento. Você deve ter uma tela mais ou menos assim:

Um glorioso retângulo preto! Demais né não?

Agora temos uma canvas, uma tela e ela está pronta para desenhar. Mas o pixi.js possui mais um nível de abstração: os objetos de cena.

Pra desenhar qualquer coisa na tela, você deve primeiro criar um container: Vamos criar um:

var stage = new PIXI.Container();



O passo seguinte é adicionar os objetos no container. Vamos adicionar um extra-terrestre primeiro.

Como eu não sei desenhar, vou usar uma coleção de artes creative-commons muito boa e colocar este coleguinha aqui no ar:
No código faremos assim:

A mudança se dá por conta do uso do carregador de recursos. Há muito tempo atrás me disseram que a única coisa difícil no desenvolvimento de jogos era o gerenciamento eficiente de recursos. Imagine tentar desenhar um personagem cuja imagem não está carregada?

A despeito de termos colocado o loader e tudo mais, nossa tela segue preta. Qual a razão?

Isso é porque ainda não demos nosso container de desenho para a view renderizar.

renderer.render(stage);


A chamada foi colocada dentro do loader porque se chamarmos fora a imagem ainda não estará carregada. O resultado esperado é o seguinte:


Nosso alienígena está dentro do container criado, localizado na posição (0,0) dele. Diferente das coordenadas cartesianas vistas no colégio, no computador a origem localiza-se na canto superior esquerdo. Isso se dá porque nós escrevemos (e por tabela desenhamos!) da esquerda para a direita, de cima para baixo.

Até o momento temos uma canvas com um desenho estático. Para tornar isso uma animação precisamos de um loop de eventos:

O requestAnimationFrame foi introduzido para justamente garantir loops de animação mais suaves. Antes dele, se os jogos/animações em javascript quisessem um loop eles precisariam de um setInterval ou de um setTimeout, ambos com custo adicional de processamento.

Agora é hora de fazer este alienígena se mover.

Note que a mudança importante foi tornar o alien visível fora da fase de carregamento. Muitos frameworks/game engines possuem para suas entidades fases definidas de carregamento e de animação. Com pixi.js não há obrigação em código para isso, mas tal grau de liberdade tem seus pontos fortes.

Lembrando aqui do que o nosso jogo deve fazer, vamos adicionar um evento pra quando "acertarmos" o alien. Ele deve sair de cena, certo? vamos cuidar disso:

Agora toda vez que apertarmos ele some da tela. Caso você esteja em uma tela mobile, mude de alien.on("mousedown" para alien.on("touchstart". Ou dê nome pra função e exporte você mesmo.

O passo seguinte é dar game over sempre que o alien sair da tela:

Agora vamos adicionar um pouco de emoção ao randomizar a posição de onde o alienígena irá descer:

O passo seguinte é adicionarmos algum movimento neste alienígena.

Para tanto vamos primeiro trocar a imagem que estamos usando por esta aqui:

O passo seguinte é usar um atlas contendo as coordenadas necessárias para mapear nesta imagem os 12 frames contidos nela:

alien.json:
{
  "meta": {
    "image": "open_chars8.png",
    "size": {
      "w": 105,
      "h": 210
    }
  },
  "frames": {
    "alien_down0": {
      "frame": {
        "x": 0,
        "y": 103,
        "w": 32,
        "h": 48
      },
      "sourceSize": {
        "w": 32,
        "h": 48
      },
      "pivot": {
        "x": 0.5,
        "y": 0.5
      }
    },
    "alien_down1": {
      "frame": {
        "x": 36,
        "y": 103,
        "w": 32,
        "h": 48
      },
      "sourceSize": {
        "w": 32,
        "h": 48
      },
      "pivot": {
        "x": 0.5,
        "y": 0.5
      }
    },
    "alien_down2": {
      "frame": {
        "x": 73,
        "y": 103,
        "w": 32,
        "h": 48
      },
      "sourceSize": {
        "w": 32,
        "h": 48
      },
      "pivot": {
        "x": 0.5,
        "y": 0.5
      }
    }
  }
}

Este arquivo .json é tratado como asset e não como código. Tem, inclusive, ferramentas pra gerar este tipo de arquivo automaticamente.

No código, mudamos a implementação para usar uma classe do pixi.js que nos dá uma facilidade na hora de montar uma animação:


Agora adicionamos um contador de pontuação com um componente de texto simples:

Agora marcamos pontos, acertamos o alien, ele renasce em posições relativamente aleatórias...

Hora de organizar o código!

Crie um script chamado inimigo.js. ele cuidará de tudo o que envolva os bonecos que colocamos em tela. Ele terá os eventos. Ele terá o evento de animação. Tudo.

teremos uma fase de preload, onde informaremos o atlas de animação. teremos uma de load pra pendurar os eventos criados. Por fim, uma de update pra chamarmos no laço de animação.

Teremos um argumento ali em cima pra recebermos uma referência ao contêiner e qualquer outra referência do mundo exterior que seja necessária:

Uma vez separado em outro arquivo bastou fazer o var Inimigo = require("./inimigo") lá no começo do index.js e assim pudemos usar uma instância de inimigo para fazer o nosso alien.

O mesmo processo pode ser feito com o nosso placar de pontos:

E assim o browserify vai se pagando em suaves e modularizadas prestações.

Poderíamos nos alongar, mas isso é um gostinho pra você entender algumas das possibilidades de trabalho com o pixi.js combinado com browserify e o servidor de prototipação budo.

Como fato curioso e relativamente relevante deste tutorial, não foi necessário criar nenhum documento html. O budo fez um pra gente. Tivemos uma boa produtividade com menos (99) de cem linhas de código, uma vez que os atlas de sprite são assets para o pixi.

Outro ponto para estudos futuros é olhar os plugins do pixi.js e os frameworks derivados dele.

Este guia é amplamente baseado neste excelente tutorial sobre pixi, que você pode seguir caso deseje se aprofundar.

Por fim, o código fonte desta postagem pode ser encontrado aqui.

O demo pode ser visto aqui.

Sem mais e até outra hora!