Conhecendo o código

Essa página apresenta os conteúdos dos arquivos gameMechanics.js e globals.js. Para visualizar a documentação detalhada dos parâmetros usados nas funções em gameMechanics.js e das variáveis globais, bem como sobre os estados do iFractions clique aqui (desatualizado). Basta navegar pelo menu lateral. Esta documentação gerada usando JSDoc está em inglês e cobre apenas a porção de javascript do código.


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.

Fluxo principal de estados.

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 ).

No iFractions, os arquivos usados para fazer a tradução dos textos ficam no diretório /assets/lang.

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.addseçã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...