Horizons

Aqui inicia-se uma série de artigos onde vamos juntos usar Javascript para escrever um jogo 2D simples que será executado ao abrir-se uma página HTML num navegador web - especificamente nas versões mais novas do Chrome e do Firefox. Neste primeiro post vamos construir um projeto que poderá ser usado como base para criar jogos com essas especificações. Vai ser um texto longo, então vamos começar logo!

(Antes de mais nada: o código-fonte do projeto que vamos construir pode ser encontrado aqui).

Nota

Esta série de posts sobre desenvolvimento de jogos pressupõe que você já sabe programar e que você já tem pelo menos um entendimento básico da linguagem de programação Javascript. Se esse não for o seu caso, eu posso recomendar o livro Eloquent Javascript (originalmente escrito em inglês, e aqui traduzido para o português) como uma excelente introdução à programação usando Javascript.

Estrutura básica

Projeto base
Estrutura de arquivos do nosso projeto base.

O projeto contém os seguintes arquivos/diretórios:

O arquivo index.html

Segue abaixo o código da página inicial do projeto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html>
  <head>
    <title>Base game</title>
    <link rel='stylesheet' type='text/css' href='css/game.css' />
  </head>
  <body>
    <main>
      <canvas id='background'></canvas>
      <canvas id='foreground'></canvas>
    </main>

    <script src='js/graphics/canvas.js'></script>
    <script src='js/game.js'></script>
    <script src='js/main.js'></script>
  </body>
</html>

Note que estamos usando dois elementos <canvas> aqui. A finalidade é sobrepor esses elementos para renderizar os objetos da tela em camadas: um canvas será utilizado para desenhar objetos no primeiro plano, e o outro será utilizado para desenhar objetos do cenário.

Podemos ver também, nas linhas 13 a 15, os arquivos de código Javascript anexados à página. Vamos discutí-los daqui a pouco.

O arquivo game.css

Temos também um arquivo CSS que ajusta estilos básicos para a página inicial:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
main {
  width: 800px;
  margin-left: auto;
  margin-right: auto;
}

canvas {
  width: 800px;
  height: 600px;


  position: absolute;
  top: 0px;


  border: 3px solid black;
  background: transparent;
}

#foreground {
  z-index: 0;
}

#background {
  z-index: -1;
}

Alguns pontos a serem considerados:

O código Javascript

Conforme vimos anteriormente, as linhas 13 a 15 do arquivo index.html incluem os arquivos Javascript do projeto para serem executados na ordem em que foram declarados. Essa ordem de declaração é importante para que a dependência entre as entidades do código seja respeitada. Os arquivos canvas.js e game.js declaram algumas dessas entidades, enquanto que o arquivo main.js é o ponto de partida do jogo.

Vamos começar analisando o arquivo canvas.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Canvas {
  constructor(id) {
    this.objects = [];

    const canvas = document.getElementById(id);
    this.context = canvas.getContext('2d');
  }

  add(object) {
    this.objects.push(object);
  }

  clear() {
    this.objects.forEach(object => object.clear(this.context));
  }

  update() {
    this.objects.forEach(object => object.update());
  }

  draw() {
    this.objects.forEach(object => object.draw(this.context));
  }
}

Esse arquivo define a classe Canvas, cujo propósito é representar um elemento <canvas> declarado na página HTML. Note que o construtor recebe um parâmetro contendo o identificador do elemento. De posse dele, podemos recuperá-lo no DOM e obter um objeto context, que é usado para desenhar na tela.

Podemos pensar em um Canvas como uma coleção de objetos que precisam ser atualizados e desenhados na página - armazenamos essa coleção no atributo objects. Temos então o método add() que adiciona um objeto ao array. Por fim, temos os métodos clear(), update() e draw() que apenas executam os métodos homônimos definidos para cada um dos objetos armazenados na coleção. Esses métodos serão úteis mais à frente.

Vamos agora analisar o arquivo game.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const FOREGROUND_CANVAS_ID = 'foreground';
const BACKGROUND_CANVAS_ID = 'background';
const ONE_SECOND = 1000;

class Game {
  constructor(fps) {
    this.fps = fps;
    this.foreground = new Canvas(FOREGROUND_CANVAS_ID);
    this.background = new Canvas(BACKGROUND_CANVAS_ID);
  }

  start() {
    const tick = () => {
      this.foreground.clear();
      this.foreground.update();
      this.foreground.draw();
    };

    setInterval(tick, ONE_SECOND / this.fps);
  }
}

Esse arquivo define a classe Game, cujo parâmetro no construtor contém a quantidade de quadros por segundo que queremos usar para executar o jogo. Esse valor representa quantas vezes por segundo os objetos do jogo serão desenhados em seus respectivos elementos <canvas>. Note também que nas linhas 8 e 9 criamos os objetos Canvas que representam o primeiro plano e o plano de fundo do jogo, respectivamente.

O método start() cria e executa um laço através da função setInterval(), e esse laço ficará rodando eternamente até que o jogador feche o jogo: isso é o que chamamos de game loop. O primeiro parâmetro de setInterval() é a função que será repetida, e o segundo parâmetro é o intervalo de repetição da função em milissegundos. Vamos analisar o cálculo do segundo parâmetro mais adiante.

A função tick() declarada localmente na linha 13 define aquilo que o jogo fará a cada repetição do laço. Nesse caso programamos o jogo para, nessa ordem: limpar todos os objetos do primeiro plano, atualizar seu estado e redesenhá-los.

Por fim, segue abaixo o código do arquivo main.js:

1
2
3
4
const FPS = 60;

const game = new Game(FPS);
game.start();

Aqui criamos um objeto Game passando como parâmetro para seu construtor uma constante FPS cujo valor é 60 (ou seja, vamos rodar o jogo a 60 quadros por segundo). Dessa forma, o intervalo de repetição pode ser calculado dividindo-se 1000 milissegundos (que equivale a um segundo) por 60, o que dá aproximadamente 16,7. Isso significa que nosso laço será executado uma vez a cada 16,7 milissegundos.

E, finalmente, iniciamos a execução do jogo na linha 4.

Conclusão

Você pode abrir o arquivo index.html no navegador para perceber que, visualmente, o código não faz nada ainda hehe.

Troll face

Por favor não fique desapontado :) O código que escrevemos hoje é apenas o ponto de partida para criar novos jogos. Sozinho, ele não faz nada útil… Ainda. Se você quiser visualizar a execução do laço, pode adicionar algumas instruções console.log() à função tick() definida na classe Game.

No próximo post vamos começar a criar um jogo básico para ver na prática o que aprendemos e desenvolvemos hoje. Até lá!

Ninguem comentou este post ainda.

Deixe um comentário!





Seu comentário será exibido após moderação.
spinner Enviado! Ops... Verifique se todos os campos estão preenchidos e tente de novo.