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.
- 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 ocreate
. Ela executa uma única vez e ocreate
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.
- Create
O
create
é chamado pelopreload
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 oupdate
− se existir.É uma função necessária − precisa existir em todos estados, caso contrário não tem como mudar de estado.
- Update
O
update
é chamado logo após o fim da execução docreate
. 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...