×

## Estados de jogo

Como mencionado anteriormente, um estado é um tipo especial de objeto que possui algumas funções que seguem um comportamento predefinido: preload, create e update. Nessa seção veremos mais sobre o comportamento de cada uma dessas funções.

1. Preload

   O `preload` é a primeira função a ser executada quando um estado é chamado. Ela está encarregada de carregar todos elementos de mídia para o cache e chamar o `create`. Ela executa uma única vez e o `create` só é chamado depois do último elemento de mídia ser carregado.

   É uma função opcional − se o estado atual não precisar de nenhum elemento de mídia novo ela não precisa existir.

2. Create

   O `create` é chamado pelo `preload` logo após todas mídias serem carregadas no cache. Ele executa uma única vez. Contém "código principal" do estado atual − desenho da tela, definição de variáveis, criação de eventos etc. Ao fim da sua execução, chama o `update` − se existir.

   É uma função necessária − precisa existir em todos estados, caso contrário não tem como mudar de estado.

3. Update

   O `update` é chamado logo após o fim da execução do `create`. Está sincronizado com o laço de jogo (game loop) e contém o código que reage a mudanças durante a execução. Aqui fica o código que reage a ações do jogador. Continua sendo executado iterativamente enquanto não sair do estado.

   É uma função opcional − algumas telas, por exemplo, só aguardam clique de mouse, ou seja, a única forma de mudar de estado é por um evento, não precisando de `update` pois ações de eventos ficam em funções próprias.

### No código

A tabela abaixo mostra a lista com todos estados do iFractions e em que arquivo estão contidos.

Estado | Arquivo
--- | ---
boot | `js/menus/menus_boot.js`
lang loadLang | `js/menus/menus_lang.js`
name | `js/menus/menus_name.js`
menu | `js/menus/menu_main.js`
customMenu | `js/menus/menu_custom.js`
map | `js/screens/map.js`
end | `js/screens/end.js`
studentReport | `js/moodle/studentReport.js`
squareOne | `js/games/squareOne.js`
circleOne | `js/games/circleOne.js`
squareTwo | `js/games/squareTwo.js`

É interessante perceber que, de forma simplista, os estados podem ser divididos em duas categorias: os que unicamente carregam elementos de mídia no cache e as telas.

Considere a figura abaixo que corresponde ao fluxo principal do jogo usando como parâmetros chamadas de estado. Com exceção dos estados `boot` e `loadLang` que só fazem carregamento, todos os outros estados correspondem a telas no jogo.

Quando abrimos o iFractions no navegador, o arquivo `index.html` é executado, carregando todos arquivos do diretório `js/`, inicializando o Canvas, e criando referência para todos estados. Então o primeiro estado é chamado: `boot`. Este estado carrega no cache toda mídia necessária para as telas de selecionar linguagem e inserir nome, além dos ícones persistentes em todas as telas. Os outros elementos de mídia são carregados a medida que vão sendo necessários quando um estado novo é chamado.

## O arquivo `gameMechanics.js`

O arquivo `js/gameMechanics.js` é como uma caixa de ferramenta que contém tudo que o iFractions usa durante a construção do código. Foi desenvolvido pelo LInE-IME-USP, com o intuito de diminuir dependência de arcabouços de terceiros quando não se mostrava necessário.

Usando o prefixo `game`, este arquivo provê diversas funções que fazem o gerenciamento de elementos de jogabilidade, elementos visuais e sonoros etc, que são usadas em todos outros arquivos JavaScript. A seguir temos a lista das funcionalidades providas por esse arquivo.

### 1 Gerenciando o comportamento dos estados

Como apresentado anteriormente, os estados são objetos que se comportam seguindo certas características. O `game.state` contém todo código que define esses comportamentos, além ser responsável por fazer a preparação para transicionar entre estados (limpar eventos, fila de mídia, variáveis etc). Ele provê as seguintes funções:

- Criar um novo estado:

      game.state.add(<params>)

- Chama/inicia o estado

      game.state.start(<params>)

### 2 Laço de jogo (Game loop)

Quando a função update é chamada dentro de um estado, o laço de jogo é iniciado (`game.loop`), sendo executando iterativamente até a saída do estado atual. Ele permite que verifiquemos a cada iteração se houve mudança no código por ação do usuário, e é por meio dele que é possível redesenhar tela, estabelecer um temporizador em segundos, executar animação de sprite etc.

É importante notar que elas não devem ser usadas diretamente, sendo chamadas pelo gerenciador de estados. Abaixo, as funções disponíveis:

- Inicia o laço de jogo:

      game.loop.start()

- Encerra o laço de jogo:

      game.loop.stop()

### 3 Carregando arquivos de mídia para o cache

O `game.load` recebe um vetor com os caminhos (URL) para arquivos de mídia e os carrega para o cache.

Ele deve ser usado exclusivamente dentro da função preload de um estado, garantindo que a função create só seja chamada quando todos elementos de mídia terminarem de ser carregados. É importante notar que um elemento de mídia não pode ser usada se não tiver sido antes carregado no cache.

As funções abaixo carregam elementos de mídia:

- Arquivos de idioma:

      game.load.lang(<url>)

- Audios:

      game.load.audio(<urls>)

- Imagens:

      game.load.image(<urls>)

- Spritesheets:

      game.load.sprite(<urls>)

### 4 Como essa mídia é carregada no cache

Levando em consideração que o iFractions é uma aplicação de página única, uma "mudança de tela" dentro do jogo corresponde, na verdade, a desenhar novos elementos gráficos no Canvas, sobrescrevendo a tela anterior.

Dessa forma, diferente de uma aplicação de varias páginas, onde a cada carregamento os elementos de mídia ficam prontos antes do código executar, ele utiliza chamadas assíncronas tanto para carregar os arquivos necessários logo no inicio do programa, como para carregar novos elementos durante a execução.

Para isso, os arquivos de idioma e de áudio são carregados usando Fetch API e imagens (e spritesheets) usando HTMLImageElement, ambos nativos do JavaScript.

### 5 Sobre o formato dos arquivos de idioma

Estes arquivos não precisam de extensão todo seu conteúdo segue o formato `<chave>=<tradução>`, onde as traduções não requerem uso de aspas.

Ex: `insert_name=DIGITE SEU NOME` (trecho do arquivo `/assets/lang/pt_BR`).

Quando o usuário seleciona um idioma, a função `game.load.lang(<caminho para arquivo>)` é chamada. Nela o conteúdo do arquivo referente ao idioma escolhido é acessado usando o Fetch API. A função então quebra esse texto em chave e tradução usando a quebra de linha como indicativo de que a tradução encerrou, e os salva num objeto que pode ser acessado da forma `game.lang.<chave>` (como é possível ver na próxima seção ).

### 6 Usando arquivos de idioma e áudio diretamente

O `gameMechanics.js` contém os objetos `audio, lang, image` e `sprite`, onde os elementos de mídia são salvos pelo `game.load` porém apenas `audio` e `lang` devem ser acessados diretamente.

Abaixo a forma de acesso desse tipos de mídia:

- Toca o som:

      game.audio.<nome do audio>.play()

- Retorna a tradução associada àquela chave de acordo com o idioma selecionado pelo jogador:

      game.lang.<chave da palavra>

Já os objetos `image` e `sprite` não devem ser acessados diretamente, mas sim por meio da função `game.add` − seção 7 − que salva os elementos gráficos na fila de mídia para o estado atual. A fila de mídia é usada exclusivamente por `game.render`, que é chamado pelo gerenciados de estados. Mais sobre os detalhes dessa implementação na seção 8.

### 7 Desenhando na tela (indiretamente)

As funções providas por `game.add` podem ser usadas sempre que for desejado colocar algum elemento gráfico na tela. No entanto, é importante notar que, por si só, ela não renderiza esses elementos na tela. Todo desenho no Canvas é realizado por `game.render` (seção 8), que é chamado pelo gerenciador de estados quando este redesenha o Canvas. Assim, o papel das funções abaixo é adicionar esses elementos para a fila de renderização. Obs.: Os parâmetros em negrito são obrigatórios enquanto os outros são opcionais.

- Imagem:

      game.add.image(<params>)

  `<params> = x, y, img, scale, alpha`
- Spritesheet:

      game.add.sprite(<params>)

  `<params> = x, y, img, curFrame, scale, alpha`
- Texto:

      game.add.text(<params>)

  `<params> = x, y, text, style, align`

Figuras geométricas também podem ser criadas usando `game.add.graphic`:

- Retângulo:

      game.add.graphic.rect(<params>)

  `<params> = x, y, width, height, lineColor, lineWidth, fillColor, alpha`
- Círculo:

      game.add.graphic.circle(<params>)

  `<params> = x, y, diameter, lineColor, lineWidth, fillColor, alpha`
- Arco:

      game.add.graphic.arc(<param>)

  `<params> = x, y, diameter, angleStart, angleEnd, anticlockwise, lineColor, lineWidth, fillColor, alpha`
- Linha (em construção):

      game.add.graphic.line(<param>)

  `<params> = x0, y0, x1, y1, lineWidth, lineColor, alpha`

#### 7.1 Algumas propriedades disponíveis para esses elementos

- Pivô:

      <mídia>.anchor(<valor horizontal>,<valor vertical>)

- Adicionar sombra na figura:

      <mídia>.shadow = <valor booleano>

- Rotação:

      <mídia>.angle = <ângulo em graus>

- Transparência:

      <mídia>.alpha = <valor entre 0 e 1>

A tabela abaixo apresenta uma visualização das propriedades disponíveis para cada elemento gráfico adicionado usando `game.add`. Em verde temos os parâmetros obrigatórios na criação, em amarelo o parâmetros opcionais, em azul as outras propriedades que podem ser usadas e em branco as propriedades que o elemento não tem acesso.

| image | sprite | text | geom.rect | geom.circle | geom.arc | (em progresso) geom.line
x |  |  |  |  |  |  |
y |  |  |  |  |  |  |
img |  |  |  |  |  |  |
scale |  |  |  |  |  |  |
alpha |  |  |  |  |  |  |
curFrame |  |  |  |  |  |  |
width |  |  |  |  |  |  |
height |  |  |  |  |  |  |
text |  |  |  |  |  |  |
style |  |  |  |  |  |  |
align |  |  |  |  |  |  |
font |  |  |  |  |  |  |
fill |  |  |  |  |  |  |
lineColor |  |  |  |  |  |  |
lineWidth |  |  |  |  |  |  |
fillColor |  |  |  |  |  |  |
diameter |  |  |  |  |  |  |
angleStart |  |  |  |  |  |  |
angleEnd |  |  |  |  |  |  |
anticlockwise |  |  |  |  |  |  |
xAnchor |  |  |  |  |  |  |
yAnchor |  |  |  |  |  |  |
anchor |  |  |  |  |  |  |
xWithAnchor |  |  |  |  |  |  |
yWithAnchor |  |  |  |  |  |  |
shadow |  |  |  |  |  |  |
shadowColor |  |  |  |  |  |  |
shadowBlur |  |  |  |  |  |  |

Lista de propriedades de cada elemento gráfico adicionados usando `game.add` − em verde os parametros obrigatórios na criação; em amarelo os parametros opcionais; em azul as outras propriedades disponíveis.

### 8 Desenhando na tela (diretamente)

Em `game.render` está o código que pega um objeto previamente criado em `game.add` e desenha na tela usando as propriedade do elemento Canvas.

Ele utiliza uma fila de renderização − `game.render.queue` − para estabelecer a ordem de desenho na tela e é chamada exclusivamente pelo gerenciador de estados − que redesenha a tela a cada chamada do laço de jogo.

É importante notar que o processo de desenho na tela foi dividido em `game.add` e `game.render` por conta do laço de jogo, que redesenha a tela diversas vezes.

Resumindo a estrutura: `game.add` cria o elemento e o salva na fila de renderização e `game.render` (re)desenha os elementos da fila de renderização na tela sempre que é chamado pelo gerenciador de estados − `game.state` − que, por sua vez, segue o tempo estabelecido pelo laço de jogo − `game.loop`.

- Desenha no Canvas todos elementos que estão na na fila de renderização:

      game.render.all()

- Limpa a fila de renderização. É chamado pelo gerenciador de estados ao sair de um estado para que elementos antigos não sigam sendo redesenhados na tela:

      game.render.clear()

### 9 Funções matemáticas

Algumas funções são necessárias diversas vezes no jogo então foram centralizadas em `game.math`.

- Retorna um inteiro entre dois números

      game.math.randomInRange(<params>)

- Retorna um divisor aleatório de um número:

      game.math.randomDivisor(<params>)

- Converte de grau para radiano:

      game.math.degreeToRad(<params>)

- Retorna a distância do centro de um ícone para o ponteiro do mouse:

      game.math.distanceToPointer(<params>)

- verifica se ponteiro está sobre um ícone (frequentemente usada para validar eventos de mouse):

      game.math.isOverIcon(<params>)

### 10 Eventos de mouse

O `game.event` encapsula eventos para que sejam usados pelo gerenciador de estado. Atualmente, ele captura eventos de mouse e toque de tela.

- Captura evento de clique de mouse (e toque de tela):

      game.event.add('click',<função>)

- Captura evento de movimento de mouse. Usado para ver se o ponteiro está sobre um ícone:

      game.event.add('move',<função>)

É importante entender a forma como o Canvas lida com eventos. Elementos desenhados no Canvas não são isolados do mesmo, assim, o evento é capturado pelo Canvas como um todo e não pelo elemento separadamente.

Assim, quando um evento é capturado, a função definida como parâmetro na criação do evento é chamada. Por convenção usamos os nomes `onInputOver()` e `onInputDown()` e precisam ser declaradas dentro do estado que contém o evento.

Assim, quando estas funções são chamadas, sabemos que um evento foi capturado pelo Canvas, mas não sabemos se o ponteiro está sobre um elemento válido. Então essas funções devem fazer essa verificação. Para isso usamos sempre que possível a função `game.math.isOverIcon()`, que recebe a lista de ícones válidos e as coordenadas do ponteiro e retorna se este está sobre algum deles.

### 11 Animação de sprites

As funções providas por `game.animation` gerenciam as animações do jogo. Essas animações são feitas iterando pelos frames de um spritesheet a cada iteração do laço de jogo.

- Começa a executar uma animação:

      game.animation.play(<params>)

- Para uma animação:

      game.animation.stop(<params>)

Uma vez que seja adicionada a propriedade `animation` a um sprite ele pode ser animado usando estas funções acima.

- Adiciona a um spritesheet a propriedade de animação:

      <sprite>.animation = [<nome>,[<frames>]]

### 12 Temporizador

Usado no iFractions para calcular o tempo que o jogador levou para terminar um jogo, o `game.timer` está associado ao laço de jogo e conta os segundos passados desde o inicio do seu contador até o fim.

- Inicia o contador:

      game.timer.start()

- Para o contador:

      game.timer.stop()

- Retorna o tempo percorrido:

      game.timer.elapsed

## O arquivo `globals.js`

Em construção...

Voltar - tecnologias utilizadas