Desenvolvimento de Jogos para Programadores(as) WEB - Parte 1
Tags: webgl, javascript, gamedev, webdev
Published: 2021-02-23 14:46:23

### Sumário 

1. Introdução
2. Game Loop
3. Tick
3. UI
4. Ciclo de Vida
5. ECS
6. Informações Adicionais
7. Parte 2 - PixiJS

### Introdução

Neste artigo vamos buscar trazer a familiarização com DOM a forma que jogos funcionam internamente, assim conseguindo relacionar conceitos que um desenvolvedor(a) WEB possui com o que está sendo proposto.

Este artigo é recomendado para desenvolvedores(as) que possuem um bom conhecimento de DOM e sobre gerenciamento de estado, mas nada impede de seguir os exemplos já que tem muitos link's auxiliares :)

### Game Loop

Os loops de jogo são o exemplo perfeito de um **padrão de programação para jogos**. Quase todo jogo tem um, e relativamente poucos programas fora dos jogos os usam.

Podemos fazer um paralelo entre os *loop's* convencionais com os **Event Listeners** do *[DOM](https://tableless.com.br/entendendo-o-dom-document-object-model/)*, onde sempre estão esperando uma alteração para emitir um evento:

Loop convencional:
```js
while (true) {
 InstanceManager.escutarComando(InputManager.carregarTeclado());
}
```

* InstanceManager: Seria o gerenciador responsável pela instância atual da aplicação e do que está sendo exibido. Nele poderíamos acessar a vida de um jogador, o tempo atual de jogo e os itens que estão no mapa, por exemplo.

* InputManager: Seria o gerenciador responsável pela captura e emissão de dados que o sistema recebe, como por exemplo a entrada de dados do teclado/mouse.

DOM:

```js
document.addEventListener('keydown', (event) => {
  dispararTeclado(event.keyCode); // ou event.key
});
```

* Com um *Event Listener*, conseguimos escutar eventos a qualquer momento e realizar o que queremos de forma separada, sem a necessidade de um loop fixo.

* Nos exemplos iremos utilizar apenas JavaScript para melhor compreensão da semântica.

### Tick

Você deve ter se perguntado: **Se este loop não está bloqueando a entrada, o quão rápido ele gira?** A Cada volta no loop o estado do jogo avança em alguma quantidade, chamamos isso de *tick*.

Podemos fazer um paralelo diretamente com **FPS(Frames por Segundo)**. Por exemplo, se um jogo estiver renderizando 60 "fotos" por segundo, quer dizer que, no melhor dos casos, temos o nosso loop sendo percorrido a 60 ticks por segundo.

* É importante ressaltar o **frame-time**, que é o tempo normalmente contado em milissegundos entre a demora de renderização de um frame a outro, normalmente não é 100% exato. Por exemplo, podemos ter um jogo em que o *frametime* é alto devido a placa de vídeo em não conseguir renderizar a tempo a qualidade das texturas, ou o processador não conseguir calcular a ação de todos os *sprites* a tempo. Normalmente *game-engines* possuem recursos para normalizar esse tipo de situação.

* Se quiser entender um pouco mais afundo sobre *frame-times*, [recomendo este fórum](https://www.clubedohardware.com.br/topic/1230664-an%C3%A1lise-de-frametimes-e-porque-isso-importa-introdu%C3%A7%C3%A3o-aos-01-e-1-%E2%80%9Clows%E2%80%9D/).

* No JavaScript, faz mais sentido em utilizar `setInterval()` do que o `while` para executar estes loops, assim garantido um tickrate "esperado".

### UI

Para o terror dos *backend* de plantão, "a regra de negócio dos jogos é feita no lado do cliente", e a interface é executada durante o *tick*, recebendo apenas as novas informações (se tiver) para renderizar alguma coisa.

As ferramentas gráficas modernas de construção de interfaces para jogos ficam "parados" até receber algum evento:

Loop tradicional:

```js
while (true) {
  const evento = esperandoEvento();
  if(evento?.keyCode == 73) { // Se o evento retornar algo, verifica se a tecla 'I' do teclado foi pressionado
    InstanceManager.renderizarInventario(); // Irá renderizar o inventário pelo tick atual, se o usuário parar de pressionar, o inventário é fechado.
  }
}
```

DOM:

```js
document.addEventListener('keypress', (event) => {
  const inventario = document.getElementById(event.target.id);
  event.keyCode == 73 ? document.body.appendChild(inventario) : inventario?.remove(); 
  // Se a tecla 'I' for pressionada, adicionamos o inventário ao DOM, ao contrário, retiramos do DOM se ele estiver sendo renderizado.
});
```

* Se quisermos que o inventário seja renderizado até o usuário fechar manualmente, deixamos um booleano `true` quando `keypress` for acionado, e `false` quando `keyup` for acionado.

* Os teclados possuem uma "trava" por padrão, esperando um tempo após o evento de pressionar para acionar outros eventos de 'keydown'. Podemos burlar da mesma forma do evento acima, usando um booleano. Em game-engines já temos este problema solucionado, agora para jogos na web as lib's [Pixi.js](https://pixijs.io/) e [P5.js](https://p5js.org/) também possuem formas internas de evitar este problema.

* [Neste site](https://keycode.info/), podemos encontrar o keyCode de cada tecla de uma maneira facilitada. 

* **keyCodes** não são padronizados, seja de navegador para navegador ou de sistema para sistema, recomendo fortemente em que procure a melhor forma de criar seu sistema de escuta.

* *É possível construir UI para jogos com HTML e CSS?* Sim! Uma das grandes tecnologias do mercado e utilizada pelas empresas como Riot Games (VALORANT), Ubisoft (The Division), entre outras, é feito com HTML/CSS/C++. Por mais que seja uma tecnologia "á encomenda", temos a opção do [Ultralight](https://ultralig.ht/), também sendo HTML/CSS/C++.

### Ciclo de Vida

Os *loop's* precisam possuir um padrão de código a ser executado, para evitar verificações dessincronizadas e a exibição de itens incorretamente. Para jogos em *real-time*, essa diferença pode parecer ser minúscula, mas para jogos por turno, por exemplo, pode causar de renderizar algo que sequer existe no contexto.

A seguir temos a forma padrão utilizada e que é recomendada:

Por Imagem:

![Imagem exibindo o ciclo de vida de um jogo padrão](https://i.imgur.com/ixWG8rA.png)

Por Código:

```js
while (true) {
  entradaDeDados();
  atualizar();
  renderizar();
}
```

* entradaDeDados(): Aonde seria escutado a entrada de ações externas, como teclado e mouse. No DOM, não teríamos necessidade de colocar dentro de um loop, como mostrado no começo do artigo.

* atualizar(): Aqui seria realizado todas as "regras de negócio" internas, como se o jogador levou dano, se um item foi adicionado no mapa, etc. Aqui também podemos realizar requisições HTTP e a serialização de dados.

* renderizar(): irá buscar se possui novas informações, se não, continua renderizando as antigas informações.

### ECS (Entity Component System)

Traduzido diretamente como [Sistema de Componente e Entidade](https://pt.wikipedia.org/wiki/Entity-component-system), é a forma que *game-engines* guardam e fornecem informações reativas para os nossos jogos. Na WEB são os Gerenciadores de Estado (Vuex do Vue, Redux/MobX do React), por seguirem a ideia de [Pub/Sub](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern).

Fluxo do Vuex:

![Vuex](https://i.imgur.com/nooN3QV.png)

Fluxo de um ECS Genérico:

![ECS](https://i.imgur.com/OXzNerK.png)

* No JavaScript, temos um ECS simples chamado de [Ensy](https://github.com/adngdb/entity-system-js), e um criado para [React.js](https://medium.com/@clevyr/entity-component-system-for-react-js-e3ab6e9be776), mas longe de ser o ideal. Devido a isso, para jogos HTML5 se tem a necessidade de criar a própria logística interna. [WebAssembly](https://webassembly.org/) pode ser uma boa alternativa para conseguir utilizar um ECS robusto.

### Informações Adicionais

* Os Ciclos de Vida de um jogo padrão se comparado ao DOM pode mudar drasticamente dependendo da implementação desejada.

* Se tratando de jogos HTML5, por mais que temos os [WebWorkers](https://crypt.codemancers.com/posts/2020-02-17-concurrency_in_javascript_browser/), JavaScript continua sendo single-thread, limitando a forma que podemos tratar eventos e a quantidade de *sprites* a serem computadas.

* Realizar requisições HTTP sem paralelismo pode ser um problema bem grande, principalmente se estiver no loop.

### Parte 2 - PixiJS

Ainda não está disponível :(

---

Este artigo foi escrito por Giovane Cardoso. [Twitter](https://twitter.com/NovoutT) - [Github](https://github.com/Novout)