Explorar E-books
Categorias
Explorar Audiolivros
Categorias
Explorar Revistas
Categorias
Explorar Documentos
Categorias
Javascript e Phaser
Esse é um livro Leanpub. A Leanpub dá poderes aos autores e editores a partir do processo de
Publicação Lean. Publicação Lean é a ação de publicar um ebook em desenvolvimento com
ferramentas leves e muitas iterações para conseguir feedbas dos leitores, pivotar até que você
tenha o livro ideal e então conseguir tração.
Autor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Prefácio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Sobre o livro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Como ler o livro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Download do Phaser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Multiplataforma . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Onde aprender . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
e tipo de jogo pode ser feito com o Phaser . . . . . . . . . . . . . . . . . . . . . . . . . 10
Fundamentos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Estrutura de um jogo Phaser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Código mínimo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
Littera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Imagens e sprites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
TextureAtlas e Spritesheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Animação de sprites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Animação com spritesheet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Animação com TextureAtlas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Sons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Partículas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Teclado e mouse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Teclado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Mouse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Botões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Tween . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Timeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Path e follower . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Configuração do Phaser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Física no Phaser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Habilitando a física . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Movimento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Detecção de colisões . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Tilemap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Tiled . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Construção de um autódromo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Layer de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Tiles de colisão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Beth . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
index.html . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
game.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
scenes/BootScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
scenes/PreloaderScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
scenes/MenuScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
scenes/CreditsScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
scenes/GameScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
config/phaser.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
classes/Beth.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
classes/Audio.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
classes/Botao.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
classes/Colisoes.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
Chien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187
Estrutura de pastas e arquivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188
index.html . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
game.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
scenes/BootScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
scenes/PreloaderScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195
scenes/MenuScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
scenes/CreditsScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203
scenes/GameScene.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205
classes/Mundo.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
classes/Chien.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
classes/Vehicle.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
classes/VehiclesGroup.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 216
classes/Colisoes.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
classes/Animacoes.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
classes/Audio.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
classes/Buon.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 225
Conclusão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 259
Gidenilson A Santigo
Gidenilson Alves Santiago nasceu no ‘ano que não acabou’ (1968). Aos 15 anos ganhou do seu
tipo o microcomputador CP200, com 16K de RAM e processador Z-80. Iniciou seus estudos de
programação, aos 15 anos, em BASIC lendo o manual do micro. Ao esgotar o estudo do BASIC
partiu para ASSEMBLY Z-80. Seu próximo micro foi o TK2000 da Microdigital que era bem mais
poderoso com incríves 64K de RAM e cores. Como já fizera antes, aprendeu a programar o BASIC
e depois a linguagem ASSEMBLY do processador 6502.
Desde então o autor vem acompanhando o desenvolvimento da programação, sendo progra-
mador baend (PHP) e frontend (Javascript), tendo trabalhado em empresas de soware e
como freelancer. Atualmente se dedica ao estudo de JavaScript, jogos 2D e jogos educativos
com destaque para educação musical.
Paralelamente o autor é músico e habilitado em educação musical pela Universidade Federal de
São Carlos - UFSCar.
Prefácio
Em um mundo de acentuados avanços tecnológicos, um dos mercados que mais cresce é o
de jogos eletrônicos. Com isto em vista, mais e mais pessoas se aventuraram no estudo do
desenvolvimento de jogos de todos os gêneros, modalidades e em diferentes plataformas. Além
de novos jogos, novas formas de desenvolvê-los e projetá-los foram surgindo. Uma destas formas
é o uso de frameworks, e neste livro você aprenderá como usar o Phaser para desenvolver
seus próprios jogos. A escolha do Phaser é devida ao intuito do autor em usar ferramentas
gratuitas, acessíveis a qualquer um que quiser iniciar seus estudos em desenvolvimento de jogos
sem gastar muito. Para o autor, usar ferramentas de baixo custo e fácil acesso contribui para a
democratização do conhecimento, viabilizando a mais pessoas prosseguirem seus estudos sem
se preocupar com grandes gastos em ferramentas de trabalho, pesquisa e lazer. Criar jogos é
uma atividade que pode ser muito divertida e cativante, e neste livro você poderá iniciar seus
estudos, ou aprender mais sobre esta poderosa ferramenta de criação de jogos que é o Phaser.
Bom proveito e boa leitura!
Daniel Lopes Santiago\
(Gamer Senior)
Sobre o livro
O objetivo deste livro é iniciar o leitor na criação de jogos 2D, em HTML2 e Javascript, com o
framework Phaser.
Esse livro deve ser lido por quem está pelo menos familiarizado com Javascript nos seguintes
aspectos:
1. Orientação a objetos
2. Arow functions
3. Classes e extends
Download do Phaser
O Phaser é um framework muito ativo, com várias atualizações por ano. Isso é muito bom pois
estamos trabalhando com uma ferramenta em constante desenvolvimento. Por isso para você
acompanhar os exemplos desse curso é importante que utilize a mesma versão do Phaser que
estamos usando, a 3.20.
Por conveniência em todos exemplos aqui mostrados estamos utilizando o Phaser a partir de um
CDN <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
Em desenvolvimento é melhor usar a versão não minificada, para termos a ajuda do Phaser na
hora de encontrar algum erro no nosso código.
¹https://github.com/gidenilson/cursophaser3
6 Sobre o livro
Para produção, como sempre, usamos a versão minificada a partir do CDN ou baixada direta-
mente na pasta do no nosso game.
Na página de download do Phaser encontramos todas essas possibilidades.
fig A
Se quando você estiver acompanhando este curso a versão do Phaser for diferente da 3.20, você
pode escolher a versão acessando o repositório do Phaser no Github, ou escolhendo a versão no
CDN do Phaser em https://www.jsdelivr.com/package/gh/photonstorm/phaser
fig B
Features
As features do Phaser são as seguintes:
WEBGL & CANVAS,
PRELOADER,
PHYSICS,
SPRITES,
GROUPS,
ANIMATION,
PARTICLES,
CAMERA,
INPUT,
SOUND,
TILEMAPS,
DEVICE SCALING,
PLUGIN SYSTEM,
MOBILE BROWSER,
DEVELOPER SUPPORT,
WEB FIRST
Multiplataforma
Uma grande vantagem no desenvolvimento de jogos em HTML5 é a possibilidade de publicar os
jogos para várias plataformas com apenas o mesmo código. Um jogo desenvolvido com o Phaser
pode ser exportado para iOS, Android, browser e etc.
10 Introdução
Onde aprender
O Phaser conta com uma excelente documentação (em inglês) e mais de 1700 exemplos dos seus
recursos e funcionalidades. No site encontramos também muitos tutorias bastante didáticos.
Por isso antes de continuar lendo este livro faça uma visita e navegue no site do Phaser.
hp://phaser.io
fig 1
Também é um bom momento dizer que todas as scenes do jogo podem ter 4 métodos especiais:
init(), preload(), create(), update().
18 }
19 }
O método init() é o primeiro bloco a ser executado na inicialização da scene. Neste bloco
podemos colocar nossas variáveis de inicialização.
O método preload() é onde os assets do jogo são carregados para o cae do jogo.
O método create() é executado em seguida do método preload() e é onde fica a maior parte da
lógica do nosso jogo.
Esses 3 métodos são executados uma vez na amada da scene. Mas o próximo método, update(),
é executado repetidamente numa taxa de 60 fps (frames por segundo). É neste método que
escrevemos partes da lógica que necessitam atualização a cada frame.
Código mínimo
Como já dissemos anteriormente, um jogo em Phaser pode ser um único arquivo de código.
Vamos então nesse momento ver como fica o código mínimo para um jogo nascer:
index.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Text</title>
6 </head>
7 <body>
8 <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
9 <script src="game1.js"></script>
10 </body>
11 </html>
game1.js
1 var config = {
2 type: Phaser.AUTO,
3 width: 800,
4 height: 600,
5 scene: {
6 create: create
7 }
8 }
9 var game = new Phaser.Game(config);
10 function create ()
11 {
12 // code
13 }
fig 2
Ou seja, só uma quadro preto na tela. Mas se abrirmos o console do desenvolvedor no browser
veremos que o Phaser já está rodando:
fig 3
No código acima podemos observar que só temos um objeto de configuração e apenas o método
create(). A referência a uma scene está neste arquivo de configuração, mas não temos nenhum
outro código designando uma scene. Isso acontece porque com um código mínimo podemos ter
apenas uma scene, que será o próprio código.
Os métodos init(), preload(), create(), e update() não são obrigatórios, mas aparecem só
quando necessário.
GitBash
Baixe o Git no site hps://git-scm.com
Não tem nenhum segredo para instalar o Git (que já vem com o GitBash. Siga as instruções do
site.
Se você fez a instalação padrão, o programa de instalação do Git criou um atalho de contexto
para abrir o GitBash nas pastas, escolhendo essa opção clicando com o botão direito do mouse.
16 Preparação do ambiente de desenvolvimento
fig 5
NodeJs
A instalação do NodeJs também é corriqueira. Acesse o site, baixe o arquivo de instalação e
instale na sua máquina (hps://nodejs.org).
http-server
Já tendo instalado o GitBash e o Nodejs, você já pode instalar o servidor hp-server com o
comando npm.
Crie uma pasta para o seu projeto e abra o GitBash clicando com o botão direito do mouse sobre
a pasta e selecionando ‘Git Bash Here’ no menu de contexto.
Agora digite o comando npm install http-server -g
Isto instalará o hp-server de forma global, para estar acessível de qualquer pasta dentro do
console de linha de comando.
Para testar, digite no console o comando http-server
Você deverá ter a seguinte saída no console:
fig 6a
Agora se você abrir o browser no endereço http://localhost:8080 ainda não acontecerá nada.
Mas se você criar nesta pasta um arquivo index.html e amar http://localhost:8080/index.html
você verá que o hp-server está funcionando.
Nos nossos exemplos o procedimento para testar o jogo será sempre este:
Olá mundo
Vamos agora criar o nosso ‘olá mundo’?
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Olá mundo</title>
7 </head>
8 <body>
9 <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
10 <script src="game1.js"></script>
11 </body>
12 </html>
1 var config = {
2 type: Phaser.AUTO,
3 scene: {
4 create: create
5 }
6 }
7 var game = new Phaser.Game(config)
8 function create() {
9 this.add.text(400, 300, 'Olá, mundo!')
10 }
fig 6b
Texto simples
No modo de texto simples o Phaser utiliza as fontes que normalmente estão disponíveis por
padrão do browser tais como: Arial, Helvetica, Times New Roman, Times, Courier New,
Courier, Verdana, Georgia, Palatino, Garamond, Bookman, Comic Sans MS, Trebuchet MS,
Arial Black e Impact.
Durante o curso utilizaremos algumas dessas propriedades. Mas se você quiser descobrir o que
faz cada uma delas procure por Phaser.Types.GameObjects.Text na documentação do Phaser.
Para experimentar as formas de trabalhar com texto no Phaser utilizaremos uma pequena
estrutura com 2 arquivos: index.html e game.js. Então crie uma pasta e coloque dentro estes
2 arquivos.
index.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Text</title>
6 </head>
7 <body>
8 <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
9 <script src="game1.js"></script>
10 </body>
11 </html>
game1.js
20 Trabalhando com texto
1 var config = {
2 type: Phaser.AUTO,
3 scene: {
4 create
5 }
6 }
7 var game = new Phaser.Game(config);
8
9 function create() {
10 this.texto = this.add.text(400, 300, 'Trabalhando com texto', {
11 fontSize: 40,
12 fontFamily: "Arial"
13 })
14 this.texto.setStroke('#aa0000', 4);
15 this.texto.setShadow(2, 2, "#333333", 2, true, true);
16 this.texto.setOrigin(0.5);
17 }
Como você pode observar, além da criação do texto com this.add.text, temos mais 3 linhas
de código que amam métodos que transformam o texto. O objeto Phaser.GameObjects.Text
possui ainda muitos outros métodos. Para conhecer todos volte a consultar a documentação e
faça testes com cada um dos métodos.
Agora vamos fazer uma brincadeira. Crie outro arquivo javascript, por exemplo game2.js, e insira
o seguinte código:
1 var config = {
2 type: Phaser.AUTO,
3 scene: {
4 create: create
5 },
6 backgroundColor: 0xbdbdbd
7 }
8
9 var game = new Phaser.Game(config);
10
11 function create() {
12 this.counter = 0
13 this.texto = this.add.text(400, 300, 'contagem: 0', {
14 fontSize: 40,
15 fontFamily: "Arial"
16
17 });
18 this.texto.setStroke('#aa0000', 4);
19 this.texto.setShadow(2, 2, "#333333", 2, true, true);
20 this.texto.setOrigin(0.5);
21
22
23 this.texto.setInteractive();
24 this.texto.on('pointerdown', (pointer) => {
25 this.texto.text = `contagem: ${this.counter} family: ${fonts[this.counter]}`;
26 this.texto.setFontFamily(fonts[this.counter]);
27 this.counter++;
28 });
29 }
30 var fonts = [
31 "Arial",
32 "Helvetica",
33 "Times New Roman",
34 "Times",
35 "Courier New",
36 "Courier",
37 "Verdana",
38 "Georgia",
39 "Palatino",
40 "Garamond",
41 "Bookman",
42 "Comic Sans MS",
43 "Trebuchet MS",
44 "Arial Black",
45 "Impact",
46 ];
1 <script src="game2.js"></script>
Isto é só um exemplo para você começar a perceber o que pode ser feito com o Phaser. Se este
código não está claro agora para você, não se preocupe; vamos ver o que significa tudo isso no
decorrer do curso.
[
,y])]setOrigin([x] [,y])
Todos os objetos do Phaser têm um ponto de origem a partir do qual ocorre o posicionamento
na tela, a rotação e etc.
Esse ponto de origem de divide em originX, que é a posição de origem em relação ao eixo
horizontal, e originY, que é a posição em relação ao eixo vertical.
O valor padrão da origem X e Y é 0.5, isso significa que todos os objetos são posicionados com
base no seu centro.
Se definirmos a originX como 0 o ponto de origem horizontal passa a ser o canto esquerdo do
objeto, e se o valor for definido para 1 a origem horizontal passa a ser o lado direito do objeto.
No eixo vertical, definido em originY, o valor 0 faz que a origem do objeto passe a ser o topo,
enquanto que o valor 1 define a origem para a base inferior do objeto.
Para definirmos o ponto de origem dos objetos amamos o método setOrigin( [x] [, y]).
Podemos tambem amar esse método passando apenas 1 valor. Nesse caso estaremos definindo
originX e originY com o mesmo valor passado.
fig 6c
Web fontes
No Phaser podemos utilizar web fontes a partir dos arquivos de fonte (TTF) disponíveis no código
fonte dos nossos jogos. Para isso basta incluir o arquivo da fonte no CSS no index.html que
carrega o jogo e fazer uma referência a essa fonte em alguma tag do html.
Como exemplo vamos incluir e utilizar a fonte Mansalva que pode ser encontrada no repositório
Google Fonts. (este arquivo está no código fonte do curso)
Vamos criar a seguinte estrutura de pasta e arquivos:
1 + webfontes
2 + font
3 - Mansalva-Regular.ttf
4 - index.html
5 - game1.js
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Font</title>
6 <style media='screen' type='text/css'>
7 @font-face {
8 font-family: Mansalva;
9 src: url('font/Mansalva-Regular.ttf');
10 }
11 </style>
12 </head>
13 <body>
14 <div style="font-family:BigShoulders; position:absolute;
15 left:-1000px; visibility:hidden;">.</div>
16
17 <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
18 <script src="game1.js"></script>
19
20 </body>
21 </html>
E no game1.js:
1 var config = {
2 type: Phaser.AUTO,
3 scene: {
4 create
5 },
6 backgroundColor: 0xbdbdbd
7 }
8 var game = new Phaser.Game(config);
9 function create() {
10 this.counter = 0
11 this.texto = this.add.text(400, 300, 'Fonte Mansalva', {
12 fontSize: 40,
13 fontFamily: "Mansalva"
14 })
15 this.texto.setStroke('#aa0000', 4)
16 this.texto.setShadow(2, 2, "#333333", 2, true, true)
17 this.texto.setOrigin(0.5)
18 }
fig 7
Bitmap fontes
Classe Phaser.GameObjects.BitmapText
Um objeto BitmapText é uma instância dessa classe. Na documentação do Phaser você poderá
ver todos os métodos e propriedades que podem ser usados para trabalhar com esse objeto.
O objeto BitmapText funciona usando um arquivo de textura e um arquivo XML ou JSON que
mapeia os caracteres dentro da textura. Durante a renderização, cada letra do texto é renderizada
na tela, espaçada proporcionalmente e alinhada para corresponder ao mapeamento da fonte.
Os objetos BitmapText são menos flexíveis que os objetos de texto, pois possuem menos recursos,
como sombras, preenimentos e a capacidade de usar fontes da Web; no entanto, você troca essa
flexibilidade pela velocidade de renderização. Você também pode criar BitmapTexts visualmente
atraentes, processando a textura da fonte em um editor de imagens, aplicando preenimentos e
quaisquer outros efeitos necessários.
Bitmap fonte é uma técnica que utiliza 2 arquivos para escrever os caracteres na tela: um arquivo
de imagem e um arquivo de texto com o mapeamento dos caracteres dentro do respectivo arquivo
de imagem. O arquivo texto pode estar nos formatos XML, JSON ou CSV. Geralmente para o
formato XML, temos um arquivo com a extensão FNT.
Por exemplo:
imagem font.png
fig 8
fig 9
Bitmap fontes são muito fáceis de serem utilizadas no Phaser. A classe responsável por essa
feature é Phaser.GameObjects.BitmapText.
Para criar uma instância desta classe utilizamos o metódo this.add.bitmapText(x, y, font [,
text] [, size] [, align]).
1 + BitmapText
2 font.png
3 font.fnt
4 index.html
5 game1.js
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>BitmapText</title>
6 </head>
7 <body>
8 <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
9 <script src="game1.js"></script>
10
11 </body>
12 </html>
E no arquivo game1.js:
1 var config = {
2 type: Phaser.AUTO,
3 scene: {
4 preload,
5 create
6 },
7 backgroundColor: 0x000
8 }
9 var game = new Phaser.Game(config)
10 function preload(){
11 this.load.bitmapFont('fonte', 'font.png', 'font.fnt');
12 }
13 function create() {
14 this.counter = 0;
15 this.texto = this.add.bitmapText(100, 100, 'fonte', 'Minha Fonte', 32)
16 }
A partir de um arquivo de webfont (f) podemos criar nossos arquivos font.png e font.fnt,
usando para isso um aplicativo.
Vamos neste curso apresentar dois aplicativos para essa finalidade: um aplicativo on-line
denominado Liera acessível em http://kvazars.com/littera, e o Bitmap Font Generator que
roda na sua máquina e pode ser baixado em http://www.angelcode.com/products/bmfont
A interface do Littera
Visão geral:
28 Liera
fig 10
Vamos apresentar aqui só os pontos indispensáveis da interface, que lhe permita gerar sua fontes.
Mas você deve brincar um pouco com todas as features do aplicativo.
No lado esquerdo do topo, na seção Project você tem a opção de salvar um projeto de webfonte
ou carregá-lo do seu computador.
Logo abaixo dos botões de Load project e Save project temos uma caixa de texto para
inserirmos algum texto para testar.
fig 11
Descendo mais ainda, temos a seção Included glyphs que é onde inserimos quais caracteres
queremos gerar na nossa webfont. Na caixa de texto podemos digitar os caracteres que queremos
ou utilizar alguns dos presets disponíveis. Mas lembre que quanto mais caracteres você utilizar,
maior será o arquivo PNG gerado.
Temos também o botão Select Font que faz o que o nome indica. Nesta seção temos ainda a
possibilidade de definir o tamanho da fonte e o espaçamento entre os caracteres. Brinque um
pouco com esta seção para descobrir o que pode ser feito.
fig 12
fig 13
Com isso abrirá uma janela com duas caixas de texto. A primeira permite que você coloque
várias escalas que deseja exportar como bitmapFont. No exemplo abaixo nós estamos gerando
três escalas: 0.5, 1 e 2.
fig 14
Ao clicar no botão Start outra janela se abrirá para que você faça download da fonte gerada. O
arquivo baixado será um ZIP com os arquivos png e fnt da sua fonte.
fig 15
fig 16
A primeira coisa a fazer é selecionar o conjunto de caracteres que iremos trabalhar. O primeiro
conjunto localizado na parte de cima do lado direito da figura é o Latin + Latin Supplement.
Selecionando este conjunto você já terá todos os caracteres alfanuméricos do português e outras
línguas latinas.
34 Bitmap Font Generator
Configuração da fonte
fig 17
Criação de jogos HTML5 com Javascript e Phaser - gidenilson@gmail.com
35
Configuração de exportação
exportação
fig 19
Agora vá no menu Options e exporte a bitmap selecionando Save bitmap font as... Pronto, não
tem segredo. Serão gerados os 2 arquivos da bitmap fonte: o arquivo PNG e o FNT (lembrando
que esse fnt é na verdade um XML).
1 function preload ()
2 {
3 this.load.image('logo', 'images/logotipo.png')
4 }
1 function preload()
2 {
3 this.load.image({
4 key: 'logo',
5 url: 'images/logotipo.png'
6 })
7 }
Exemplo:
40 Imagens e sprites
1 function create()
2 {
3 this.add.image(x, y, 'logo')
4 }
1 function create()
2 {
3 this.add.sprite(x, y, 'imagem')
4 }
fig 20
Já num ‘Textura Atlas’ os frames são arranjados dentro da imagem de maneira que ocupem o
menor espaço possível, e esses frames são referenciados pelos seus nomes com o auxílio de uma
arquivo XML ou JSON que contém todos os dados de cada frame.
É bom salientar que alguns artigos e sowares usam o termo ‘Sprite Sheet’ como sinônimo de
‘Texture Atlas’. Então temos que ter cuidado para não confundir as coisas quando trabalhamos
com o Phaser.
Exemplo de texture atlas:
42 TextureAtlas e Spritesheet
fig 21
Nós podemos criar imagens estáticas ou animações utilizando tanto sprite sheet quanto texture
atlas.
Por enquanto vamos ver um exemplo de imagem estática criada com sprite sheet e textura atlas.
1 function preload ()
2 {
3 // sprite sheet
4 this.load.spritesheet('bot', 'images/robot.png', {
5 frameWidth: 32, frameHeight: 38 })
6
7 // texture Atlas
8 this.load.atlas('mainmenu', 'images/MainMenu.png', 'images/MainMenu.json')
9 }
Exemplos de utilização:
1 function create()
2 {
3 // criando sprites
4 this.add.sprite(100, 50, 'bot', 2)
5 this.add.sprite(200, 300, 'mainmenu', 'btnStart')
6
7 // criando imagens
8 this.add.image(200, 50, 'bot', 2)
9 this.add.image(200, 400, 'mainmenu', 'btnStart')
10 }
A princípio não faz diferença se colocamos uma figura na tela com sprite ou com image. Mas,
como já dissemos antes, se queremos uma animação temos que usar os sprites. Então não utilize
sprites para imagens estáticas por motivo de economia de recursos da máquina.
Vamos agora criar uma scene com alguns objetos sprite e image:
index.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Phaser3</title>
6 </head>
7 <body>
8 <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
9 <script src="game1.js"></script>
10 </body>
11 </html>
game1.js
1 var config = {
2 type: Phaser.AUTO,
3 width: 800,
4 height: 600,
5 scene: {
6 preload: preload,
7 create: create
8 }
9 }
10 var game = new Phaser.Game(config);
11
12 function preload ()
13 {
14 // sprite sheet
15 this.load.spritesheet('sheet', 'spritesheet.png', {
16 frameWidth: 54, frameHeight: 59 })
17
18 // texture Atlas
19 this.load.atlas('atlas', 'atlas.png', 'atlas.json')
20 }
21
22 function create ()
23 {
24 // criando sprites
25 this.add.sprite(50, 100, 'sheet', 2)
26 this.add.sprite(300, 150, 'atlas', 'btnJogarOut')
27
28 // criando imagens
29 this.add.image(50, 150, 'sheet', 10)
30 this.add.image(300, 300, 'atlas', 'parabens')
31 }
fig 22
fig 23
Talvez a única configuração que você vai querer fazer é definir a largura máxima do arquivo
PNG que será gerado. Isso pode ser feito na caixa Width que fica bem na metade da seção de
configuração. Neste exemplo estou deixando o tamanho máximo de 512 pixels de largura. Outra
configuração comumente ajustada é a opção de recortar as imagens, tirando as sobras, e a opção
de permitir que o programa rotacione as imagens individualmente para economizar espaço.
No mais, é só você dar uma mexidinha no programa para aprender rapidamente a utilizá-lo.
Você pode baixá-lo em hp://free-tex-paer.com
1. carregar o spritesheet.
2. criar um sprite com uma imagem (textura) qualquer.
3. criar um array com os frames de animação com o método generateFramesNumbers.
4. criar o objeto de configuração da animação
5. criar a animação passando o objeto de configuração
6. executar a animação no sprite criado no passo 2.
A princípio parece algo muito trabalhoso. Na versão 2 do Phaser a configuração de uma animação
era mais simples, porém mais limitada. Vamos ver um exemplo primeiramente bem detalhado,
e logo em seguida o mesmo exemplo com o código mais enxuto.
Para estes exemplos utilizaremos o seguinte spritesheet:
fig 24
Exemplo extenso:
48 Animação de sprites
1 function preload(){
2 this.load.spritesheet('bee', 'bee.png', {
3 frameWidth: 64,
4 frameHeight: 64
5 })
6 }
7 function create() {
8 this.bee = this.add.sprite(100, 100, 'bee', 0)
9 this.frames = this.anims.generateFrameNumbers('bee', {
10 start: 0,
11 end: 7
12 })
13 this.config = {
14 key: 'voando',
15 frames: this.frames,
16 frameRate: 40,
17 repeat: -1
18 }
19 this.anims.create(this.config)
20 this.bee.anims.play('voando')
21 }
Exemplo enxuto:
1 function preload(){
2 this.load.spritesheet('bee', 'bee.png', {
3 frameWidth: 64,
4 frameHeight: 64
5 })
6 }
7 function create() {
8 this.bee = this.add.sprite(100, 100, 'bee', 0)
9 this.anims.create({
10 key: 'voando',
11 frames: this.anims.generateFrameNumbers('bee', {
12 start: 0,
13 end: 7
14 }),
15 frameRate: 40,
16 repeat: -1
17 })
18 this.bee.anims.play('voando')
19 }
Neste segundo exemplo o que fizemos foi amar o método generateFramesNumbers dentro do
método this.anims.create, e já definimos o objeto de configuração também diretamente dentro
do método this.anims.create.
Vamos ao exemplo:
1 function preload ()
2 {
3 this.load.atlas('creatures', 'creatures.png', 'creatures.json')
4 }
5
6 function create() {
7
8 this.ray = this.add.sprite(200, 200, 'creatures')
9 this.anims.create({
10 key: 'stingray',
11 frames: this.anims.generateFrameNames('creatures', {
12 prefix: 'stingray',
13 end: 23,
14 zeroPad: 4
15 }),
16 repeat: -1
17 })
18
19 this.ray.play('stingray')
20 }
No textureAtlas o peixe stingray usa frames com o nome stingray0000 até stingray0023.
O parâmetro zeropad é a quantidade de zero máximo que aparece na numeração dos nomes.
O textureAtlas é indicado para animações mais complexas enquanto que para animações simples
podemos usar facilmente um spritesheet.
Em seguida temos o textureAtlas utilizado nestes exemplos.
fig 25
1 {"frames": [
2
3 {
4 "filename": "blueJellyfish0000",
5 "frame": {"x":484,"y":770,"w":64,"h":64},
6 "rotated": false,
7 "trimmed": true,
8 "spriteSourceSize": {"x":0,"y":0,"w":66,"h":66},
9 "sourceSize": {"w":66,"h":66}
10 }
11 ,{
12 "filename": "blueJellyfish0001",
13 "frame": {"x":484,"y":836,"w":63,"h":65},
14 "rotated": false,
15 "trimmed": true,
16 "spriteSourceSize": {"x":1,"y":0,"w":66,"h":66},
17 "sourceSize": {"w":66,"h":66}
18 }
19 ,{
20 "filename": "blueJellyfish0002",
21 "frame": {"x":322,"y":1621,"w":62,"h":65},
22 "rotated": false,
23 "trimmed": true,
24 "spriteSourceSize": {"x":2,"y":0,"w":66,"h":66},
25 "sourceSize": {"w":66,"h":66}
26 }
tabela 1
Observação: O Chrome não permite o autoplay na página. Isso significa que os sons nesse browser
só serão ouvidos quando o usuário clicar ou fizer qualquer ação na página. Isto não é um bug,
mas uma política de privacidade do Chrome.
O método this.sound.add retorna uma instância de Phaser.Sound.BaseSound que disponibiliza,
entre outros, os seguintes métodos:
54 Sons
tabela 2
1 var config = {
2 type: Phaser.AUTO,
3 width: 300,
4 height: 300,
5 scene: {
6 preload: preload,
7 create: create
8 }
9 }
10 var game = new Phaser.Game(config)
11 function preload() {
12 this.load.audio('trilha', ['trilinha.mp3', 'trilinha.ogg'])
13 }
14 function create() {
15 this.music = this.sound.play('trilha', { loop: true})
16 this.music.play()
17 }
(lembre-se de que para o som começar a tocar no Chrome você precisa clicar na tela do browser)
No site https://opengameart.org você encontrará muitas trilhas e efeitos sonoros livres para uso.
Exemplo:
1 var config = {
2 type: Phaser.WEBGL,
3 width: 400,
4 height: 400,
5 backgroundColor: '#000',
6 scene: {
7 preload: preload,
8 create: create
9 }
10 }
11 var game = new Phaser.Game(config);
12 function preload ()
13 {
14 this.load.image('spark', 'blue.png')
15 }
16 function create ()
17 {
18 this.particles = this.add.particles('spark')
19 this.emitter = this.particles.createEmitter()
20
21 this.emitter.setPosition(200, 200);
22 this.emitter.setSpeed(200);
23 this.emitter.setBlendMode(Phaser.BlendModes.ADD);
24 }
fig 26
explode(count, x, y)
flow(frequency [, count])
Coloca o emissor no modo de fluxo (frequency >= 0) e inicia (ou reinicia) um fluxo de partículas.
forEachAlive(callback, context)
Chama uma função callba para cada partícula ativa neste emissor.
forEachDead(callback, context)
Chama uma função callba para cada partícula inativa neste emissor.
getAliveParticleCount()
getDeadParticleCount()
getParticleCount()
killAll()
onParticleDeath(callback [, context])
Define uma função callba a ser amada para cada partícula ‘morta’.
onParticleEmit(callback [, context])
setAngle( [degrees])
pause()
Pausa a emissão.
resume()
Volta a emitir.
stop()
Para a emissão.
start()
Se o emissor estiver em modo de fluxo (frequency >= 0), o fluxo de partículas ira iniciar. E se
estiver em modo de explosão, nada acontecerá.
Vamos fazer um outro exemplo mais complexo, utilizando um objeto de configuração para o
emissor de partículas.
1 var config = {
2 type: Phaser.WEBGL,
3 width: 400,
4 height: 400,
5 backgroundColor: '#000',
6 scene: {
7 preload: preload,
8 create: create
9 }
10 }
11 var game = new Phaser.Game(config)
12 function preload() {
13 this.load.image('gude', 'gude.png')
14 }
15 function create() {
16 this.particles = this.add.particles('gude')
17 this.particles.createEmitter({
18 x: 200,
19 y: 200,
20 lifespan: 2000,
21 frequency: 300,
22 speed: {
23 min: 100,
24 max: 100
25 },
26 angle: 330,
27 gravityY: 50,
28 scale: {
29 start: 1,
30 end: 0.2
31 }
32 })
33 }
fig 28
Como já dissemos existem ainda muitos outros métodos para controlar a emissão das partículas,
como por exemplo controlar a gravidade, a direção, o ângulo e etc. Veja a documentação do
Phaser para descobrir mais. Não esqueça também de estudar os exemplos que estão disponíveis
no site do Phaser.
Um dos exemplos bem interessantes no site é o editor de configuração de partículas, que gera
automaticamente um json de configuração do emier. http://phaser.io/examples/v3/view/game-
objects/particle-emitter/particle-editor
Teclado
1 var config = {
2 type: Phaser.AUTO,
3 scene: {
4 create: create
5 }
6 }
7 var game = new Phaser.Game(config)
8 function create() {
9 this.input.keyboard.on('keydown', function(event) {
10 console.dir(event.key)
11 })
12 }
Com este código nós capturamos qualquer tecla pressionada e imprimimos o valor no console
de desenvolvimento do browser.
Mas e se quisermos saber se uma tecla específica foi pressionada dentre outras acionadas simul-
taneamente, precisamos fazer de outra forma. Vamos por exemplo querer saber quais as setas
do teclado estão pressionadas. Neste caso precisaremos “escutar” essas teclas individualmente.
Felizmente o Phaser já traz uma funcionalidade específica para trabalhar com as setas do teclado,
que é amada de “cursor”.
Com o código seguinte imprimiremos no console a identificação das setas do teclado. Este será
um recurso que utilizaremos bastante.
62 Teclado e mouse
1 var config = {
2 type: Phaser.AUTO,
3 scene: {
4 create: create,
5 update: update
6 }
7 }
8 var game = new Phaser.Game(config)
9 function create() {
10 cursors = this.input.keyboard.createCursorKeys()
11 }
12 function update() {
13 if (cursors.left.isDown) {
14 console.log('esquerda')
15 } else if (cursors.right.isDown) {
16 console.log('direita')
17 }
18 if (cursors.up.isDown) {
19 console.log('cima')
20 } else if (cursors.down.isDown) {
21 console.log('baixo')
22 }
23 }
Uma outra forma de capturar o teclado é criando um objeto key para uma tecla específica e
testando o acionamento da mesma no método update ou em um callba. No exemplo a seguir
vemos como isso pode ser feito:
1 var config = {
2 type: Phaser.AUTO,
3 scene: {
4 create: create,
5 update: update
6 }
7 }
8 var game = new Phaser.Game(config)
9 function create() {
10 keyA = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A)
11 }
12 function update() {
13 if (keyA.isDown) {
14 console.log('A')
15 }
16 }
Uma coisa interessante nesta forma de capturar o teclado é que as teclas são capturadas
independentemente de estarem com o capslook, shi ou tab pressionados.
1 Phaser.Input.Keyboard.KeyCodes.BACKSPACE
2 Phaser.Input.Keyboard.KeyCodes.TAB
3 Phaser.Input.Keyboard.KeyCodes.ENTER
4 Phaser.Input.Keyboard.KeyCodes.SHIFT
5 Phaser.Input.Keyboard.KeyCodes.CTRL
6 Phaser.Input.Keyboard.KeyCodes.ALT
7 Phaser.Input.Keyboard.KeyCodes.PAUSE
8 Phaser.Input.Keyboard.KeyCodes.CAPS_LOCK
9 Phaser.Input.Keyboard.KeyCodes.ESC
10 Phaser.Input.Keyboard.KeyCodes.SPACE
11 Phaser.Input.Keyboard.KeyCodes.PAGE_UP
12 Phaser.Input.Keyboard.KeyCodes.PAGE_DOWN
13 Phaser.Input.Keyboard.KeyCodes.END
14 Phaser.Input.Keyboard.KeyCodes.HOME
15 Phaser.Input.Keyboard.KeyCodes.LEFT
16 Phaser.Input.Keyboard.KeyCodes.UP
17 Phaser.Input.Keyboard.KeyCodes.RIGHT
18 Phaser.Input.Keyboard.KeyCodes.DOWN
19 Phaser.Input.Keyboard.KeyCodes.PRINT_SCREEN
20 Phaser.Input.Keyboard.KeyCodes.INSERT
21 Phaser.Input.Keyboard.KeyCodes.DELETE
22 Phaser.Input.Keyboard.KeyCodes.ZERO
23 Phaser.Input.Keyboard.KeyCodes.ONE
24 Phaser.Input.Keyboard.KeyCodes.TWO
25 Phaser.Input.Keyboard.KeyCodes.THREE
26 Phaser.Input.Keyboard.KeyCodes.FOUR
27 Phaser.Input.Keyboard.KeyCodes.FIVE
28 Phaser.Input.Keyboard.KeyCodes.SIX
29 Phaser.Input.Keyboard.KeyCodes.SEVEN
30 Phaser.Input.Keyboard.KeyCodes.EIGHT
31 Phaser.Input.Keyboard.KeyCodes.NINE
32 Phaser.Input.Keyboard.KeyCodes.NUMPAD_ZERO
33 Phaser.Input.Keyboard.KeyCodes.NUMPAD_ONE
34 Phaser.Input.Keyboard.KeyCodes.NUMPAD_TWO
35 Phaser.Input.Keyboard.KeyCodes.NUMPAD_THREE
36 Phaser.Input.Keyboard.KeyCodes.NUMPAD_FOUR
37 Phaser.Input.Keyboard.KeyCodes.NUMPAD_FIVE
38 Phaser.Input.Keyboard.KeyCodes.NUMPAD_SIX
39 Phaser.Input.Keyboard.KeyCodes.NUMPAD_SEVEN
40 Phaser.Input.Keyboard.KeyCodes.NUMPAD_EIGHT
41 Phaser.Input.Keyboard.KeyCodes.NUMPAD_NINE
42 Phaser.Input.Keyboard.KeyCodes.A
43 Phaser.Input.Keyboard.KeyCodes.B
44 Phaser.Input.Keyboard.KeyCodes.C
45 Phaser.Input.Keyboard.KeyCodes.D
46 Phaser.Input.Keyboard.KeyCodes.E
47 Phaser.Input.Keyboard.KeyCodes.F
48 Phaser.Input.Keyboard.KeyCodes.G
49 Phaser.Input.Keyboard.KeyCodes.H
50 Phaser.Input.Keyboard.KeyCodes.I
51 Phaser.Input.Keyboard.KeyCodes.J
52 Phaser.Input.Keyboard.KeyCodes.K
53 Phaser.Input.Keyboard.KeyCodes.L
54 Phaser.Input.Keyboard.KeyCodes.M
55 Phaser.Input.Keyboard.KeyCodes.N
56 Phaser.Input.Keyboard.KeyCodes.O
57 Phaser.Input.Keyboard.KeyCodes.P
58 Phaser.Input.Keyboard.KeyCodes.Q
59 Phaser.Input.Keyboard.KeyCodes.R
60 Phaser.Input.Keyboard.KeyCodes.S
61 Phaser.Input.Keyboard.KeyCodes.T
62 Phaser.Input.Keyboard.KeyCodes.U
63 Phaser.Input.Keyboard.KeyCodes.V
64 Phaser.Input.Keyboard.KeyCodes.W
65 Phaser.Input.Keyboard.KeyCodes.X
66 Phaser.Input.Keyboard.KeyCodes.Y
67 Phaser.Input.Keyboard.KeyCodes.Z
68 Phaser.Input.Keyboard.KeyCodes.F1
69 Phaser.Input.Keyboard.KeyCodes.F2
70 Phaser.Input.Keyboard.KeyCodes.F3
71 Phaser.Input.Keyboard.KeyCodes.F4
72 Phaser.Input.Keyboard.KeyCodes.F5
73 Phaser.Input.Keyboard.KeyCodes.F6
74 Phaser.Input.Keyboard.KeyCodes.F7
75 Phaser.Input.Keyboard.KeyCodes.F8
76 Phaser.Input.Keyboard.KeyCodes.F9
77 Phaser.Input.Keyboard.KeyCodes.F10
78 Phaser.Input.Keyboard.KeyCodes.F11
79 Phaser.Input.Keyboard.KeyCodes.F12
80 Phaser.Input.Keyboard.KeyCodes.SEMICOLON
81 Phaser.Input.Keyboard.KeyCodes.PLUS
82 Phaser.Input.Keyboard.KeyCodes.COMMA
83 Phaser.Input.Keyboard.KeyCodes.MINUS
84 Phaser.Input.Keyboard.KeyCodes.PERIOD
85 Phaser.Input.Keyboard.KeyCodes.FORWARD_SLASH
86 Phaser.Input.Keyboard.KeyCodes.BACK_SLASH
87 Phaser.Input.Keyboard.KeyCodes.QUOTES
88 Phaser.Input.Keyboard.KeyCodes.BACKTICK
89 Phaser.Input.Keyboard.KeyCodes.OPEN_BRACKET
90 Phaser.Input.Keyboard.KeyCodes.CLOSED_BRACKET
91 Phaser.Input.Keyboard.KeyCodes.SEMICOLON_FIREFOX
92 Phaser.Input.Keyboard.KeyCodes.COLON
93 Phaser.Input.Keyboard.KeyCodes.COMMA_FIREFOX_WINDOWS
94 Phaser.Input.Keyboard.KeyCodes.COMMA_FIREFOX
95 Phaser.Input.Keyboard.KeyCodes.BRACKET_RIGHT_FIREFOX
96 Phaser.Input.Keyboard.KeyCodes.BRACKET_LEFT_FIREFOX
Mouse
O Phaser tem também muitas funcionalidades para trabalharmos com os eventos do mouse.
Vamos começar com um exemplo bem simples:
1 var config = {
2 scene: {
3 create: create,
4 update: update
5 }
6 }
7 var game = new Phaser.Game(config)
8 function create() {
9 this.pointer = this.input.activePointer
10 this.text1 = this.add.text(10, 10, 'ponto do click:')
11 this.text2 = this.add.text(10, 50, 'distância:')
12 this.text3 = this.add.text(10, 100, 'ângulo em radianos:')
13 this.input.on('pointerdown', (pointer) => {
14 this.text1.setText(`ponto do click: ${pointer.x}, ${pointer.y}`)
15 })
16 }
17 function update() {
18 this.text2.setText(`distância: ${this.pointer.getDistance()}`)
19 this.text3.setText(`ângulo em radianos: ${this.pointer.getAngle()}`)
20 }
Também é possível fazer com que os objetos (sprites, imagens e etc.) fiquem sensíveis a eventos do
mouse. Ou seja, os objetos do jogo podem receber eventos de mouseOver, mouseDonw, mouseUp,
mouseOut e etc.
Vamos criar um exemplo onde colocaremos uma imagem na tela e escutaremos os eventos do
mouse nessa imagem:
1 var config = {
2 scene: {
3 preload: preload,
4 create: create
5 }
6 }
7 var game = new Phaser.Game(config)
8
9 function preload() {
10 this.load.image('rato', 'mouse.png')
11 }
12
13 function create() {
14 this.rato = this.add.image(400, 300, 'rato')
15 this.rato.setInteractive()
16 this.rato.on('pointerover', () => this.rato.setTint(0xff0000))
17 this.rato.on('pointerdown', () => this.rato.setTint(0x00ff00))
18 this.rato.on('pointerup', () => this.rato.setTint(0x0000ff))
19 this.rato.on('pointerout', () => this.rato.clearTint())
20 }
Para tornar um objeto interativo com o mouse devemos executar o método setInteractive(true)
e para desabilitar a interatividade setInteractive(false). O valor padrão do método é true.
Também podemos capturar eventos da “rodinha” do mouse…
1 var config = {
2 scene: {
3 create: create
4 }
5 }
6 var game = new Phaser.Game(config)
7 function create() {
8 this.y = 0
9 this.texto = this.add.text(10, 10, '0', {
10 font: '28px Courier',
11 fill: '#00ff00'
12 })
13 this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
14 this.texto.setText(this.y += deltaY / 100)
15 })
16 }
Primeiro vamos escrever nosso arquivo index.html, porque acrescentaremos um detalhe que não
tínhamos usado até agora, o atributo type="module" na tag script.
index.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Phaser3</title>
6 </head>
7 <body>
8 <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
9 <script type="module" src="game1.js"></script>
10 </body>
11 </html>
Com esse atributo com o uso de type="module" poderemos trabalhar diretamente com módulos
no javascript. Atualmente a grande maioria dos browsers dão suporte a essa forma de trabalho
modular.
Vamos agora ver como fica a classe do nosso componente “ImageBuon”:
ImageBuon.js
70 Botões
Na linha 5 amamos o método setInteractive() para habilitar a escuta dos eventos do mouse
em nosso botão.
Nas linha de 6 a 9 definimos que frame do spritesheet será utilizado em cada estado do mouse.
Iremos padronizar que os spritesheets dos botões terão 3 frames. Um para o estado normal,
outro para o estado pointerover, e outro para o estado pointerdown. Para o estado pointerup
utilizaremos o mesmo frame do estado pointerup.
Na linha 11 disparamos o evento personalizado click para podermos ‘escutá-lo’ no game [todos
os objetos do Phaser podem emitir eventos por meio do método on(event, fn [, context])].
Na última linha exportamos nossa classe como um módulo.
Vamos agora ao código do game1.js
fig 28
fig 29
Acabamos de desenvolver nosso botão personalizável com o Phaser. Fizemos o botão com
imagens, mas é possível fazer outros botões que trabalhem com texto simples e bitmapfontes.
Iremos voltar a falar de botões quando estivermos desenvolvendo os jogos deste curso.
1 var config = {
2 scene: {
3 preload: preload,
4 create: create
5 }
6 }
7 var game = new Phaser.Game(config)
8 function preload() {
9 this.load.spritesheet('peixes', 'spritesheet_peixes.png', {
10 frameWidth: 136,
11 frameHeight: 80
12 })
13 }
14 function create() {
15 var amarelo = this.add.image(0, 80, 'peixes', 0)
16 var verde = this.add.image(250, 80, 'peixes', 1)
17 var rosa = this.add.image(400, 80, 'peixes', 2)
18 //tween do peixe amarelo
19 this.tweens.add({
20 targets: amarelo,
21 props: {
22 x: {
23 value: 700,
24 duration: 4000,
25 flipX: true
26 }
27 },
74 Tween
28 ease: 'Sine.easeInOut',
29 yoyo: true,
30 repeat: -1
31 })
32 //tween do peixe verde
33 this.tweens.add({
34 targets: verde,
35 props: {
36 y: {
37 value: 500,
38 duration: 4000
39 }
40 },
41 ease: 'Sine.easeInOut',
42 yoyo: true,
43 repeat: -1
44 })
45 //tween do peixe rosa
46 this.tweens.add({
47 targets: rosa,
48 props: {
49 x: {
50 value: 700,
51 duration: 4000,
52 flipX: true
53 },
54 y: {
55 value: 500,
56 duration: 4000
57 },
58 scale: {
59 value: 2,
60 duration: 4000
61 }
62 },
63 ease: 'Sine.easeInOut',
64 yoyo: true,
65 repeat: -1
66 })
67 }
1 var config = {
2 scene: {
3 preload: preload,
4 create: create
5 }
6 }
7
8 var game = new Phaser.Game(config)
9
10 function preload() {
11
12 this.load.spritesheet('peixes', 'spritesheet_peixes.png', {
13 frameWidth: 136,
14 frameHeight: 80
15 })
16 }
17
18 function create() {
19 var amarelo = this.add.image(0, 80, 'peixes', 0)
20 var timeline = this.tweens.createTimeline()
21 //tween em x
22 timeline.add({
23 targets: amarelo,
24 props: {x: 600},
25 ease: 'Linear',
26 duration: 3000
27 })
28 //tween em y
29 timeline.add({
30 targets: amarelo,
31 props: {y: 500},
32 ease: 'Linear',
33 duration: 3000
34 })
35 //tween em x
36 timeline.add({
76 Timeline
37 targets: amarelo,
38 props: {x: 100},
39 ease: 'Linear',
40 duration: 3000
41 })
42 //tween em y
43 timeline.add({
44 targets: amarelo,
45 props: {y: 100},
46 ease: 'Linear',
47 duration: 3000
48 })
49
50 timeline.play();
51 }
1 var config = {
2 scene: {
3 preload: preload,
4 create: create
5 }
6 }
7 var game = new Phaser.Game(config)
8 function preload() {
9 this.load.spritesheet('peixes', 'spritesheet_peixes.png', {
10 frameWidth: 136,
11 frameHeight: 80
12 })
13 }
14 function create() {
15 var verde = this.add.image(0, 200, 'peixes', 1)
16 var timeline = this.tweens.createTimeline()
17 //tween em x
18 timeline.add({
19 targets: verde,
20 props: {
21 x: 600
22 },
23 ease: 'Elastic.easeOut',
24 duration: 3000
25 })
26 //tween em scale
27 timeline.add({
28 targets: verde,
29 props: {
30 scale: 2
31 },
32 ease: 'Linear',
33 duration: 3000
34 })
35 //tween em rotation
36 timeline.add({
37 targets: verde,
38 props: {
39 rotation: 3
40 },
41 ease: 'Linear',
42 duration: 3000
43 })
44 timeline.play();
45 }
Vamos ver como fazer com que um sprite follower percorra um caminho?
1 var config = {
2 scene: {
3 preload: preload,
4 create: create
5 }
6 }
7 var game = new Phaser.Game(config)
8 function preload() {
9 //Carrega o spritesheet dos peixes
10 this.load.spritesheet('peixes', 'spritesheet_peixes.png', {
11 frameWidth: 136,
12 frameHeight: 80
13 })
14 }
15 function create() {
16 //cria um path com 4 vértices (cantos)
17 this.path = new Phaser.Curves.Path(50, 50).lineTo(500, 50).lineTo(500, 300).lin\
18 eTo(50, 300)
19 //pinta o path para que fique visível (isto é opcional)
20 this.graphics = this.add.graphics()
21 this.graphics.lineStyle(1, 0xffffff, 1)
22 this.path.draw(this.graphics, 128)
23 //instancia o sprite follower, definindo o path a ser seguido
24 this.peixe = this.add.follower(this.path, 0, 0, 'peixes', 0);
25 //inicializa o movimento do follower no path
26 this.peixe.startFollow({
27 positionOnPath: true,
28 duration: 3000,
29 yoyo: true,
30 repeat: -1,
31 rotateToPath: true,
80 Path e follower
32 verticalAdjust: true
33 })
34 }
Neste exemplos usamos o método lineTo para criar um path formado por linhas retas, mas
também podemos traçar curvas como no próximo exemplo:
1 var config = {
2 scene: {
3 preload: preload,
4 create: create
5 }
6 }
7 var game = new Phaser.Game(config)
8 function preload() {
9 //Carrega o spritesheet dos peixes
10 this.load.spritesheet('peixes', 'spritesheet_peixes.png', {
11 frameWidth: 136,
12 frameHeight: 80
13 })
14 }
15 function create() {
16 //cria um path com curvas
17 this.path = new Phaser.Curves.Path(50, 100).splineTo([ 164, 46, 274, 142, 412, \
18 57, 522, 141, 664, 64 ])
19 //pinta o path para que fique visível (isto é opcional)
20 this.graphics = this.add.graphics()
21 this.graphics.lineStyle(1, 0xffffff, 1)
22 this.path.draw(this.graphics, 128)
23 //instancia o sprite follower, definindo o path a ser seguido
24 this.peixe = this.add.follower(this.path, 0, 0, 'peixes', 0);
25 //inicializa o movimento do follower no path
26 this.peixe.startFollow({
27 positionOnPath: true,
28 duration: 3000,
29 yoyo: true,
30 repeat: -1,
31 rotateToPath: true,
32 verticalAdjust: true
33 })
34 }
O método splineTo é responsável por criar uma curva, a partir dos vértices passados num
array. Este array contém as coordenadas x e y de cada vértice pretendido seguindo a seguinte
convenção:
“spliteTo([x1, y1, x2, y2, x3, y3, … xn, yn])
tabela 3-a
tabela 3-b
A maioria das propriedades desse objeto de configuração são auto-descritivas, mas você pode
consultar a documentação do Phaser para saber mais.
Agora vamos estudar mas detalhadamente a propriedade scale.
O objeto de configuração dessa propriedade tem o seguinte conteúdo:
tabela 4
Neste último objeto temos a propriedade mode que define diretamente o modo como o game será
escalonado. Vejamos os valores possíveis:
tabela 5
Habilitando a física
Para habilitar um sistema de física no Phaser precisamos adicioná-lo no objeto de configuração,
como no exemplo seguinte:
1 var config = {
2 width: 800,
3 height: 600,
4 physics: {
5 default: 'arcade',
6 arcade: {
7 gravity: {
8 y: 100
9 },
10 debug: true
11 }
12 },
13 scene: {
14 preload: preload,
15 create: create
16 }
17 }
Na linha 5 definimos o sistema de física padrão para utilizar o Arcade (O Phaser permite utilizar
mais de um sistema de física ao mesmo tempo).
Nas linhas 7 e 8 definimos um valor para a gravidade do eixo Y.
Na linha 10 acionamos o debug da física, que serve para mostrar na tela os contornos dos corpos
físicos dos objetos.
88 Física no Phaser
Movimento
A primeira coisa que podemos fazer usando a física é mover objetos atribuindo uma velocidade
nos seus eixos X e Y. O sentido do movimento em relação às velocidades aplicadas nos eixos são:
velocidade deslocamento
X<0 esquerda
X=0 sem movimento horizontal
X>0 direita
Y<0 para cima
Y=0 sem movimento vertical
Y>0 para baixo
No próximo exemplo iremos controlar o movimento de uma imagem aplicando uma velocidade
aos eixos X e Y dependendo da seta do teclado pressionada. Vamos lá?
O nosso index.html vai ser o de sempre…
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Phaser</title>
7 </head>
8 <body>
9 <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
10 <script src="game1.js"></script>
11 </body>
12 </html>
Agora o game1.js
1 var config = {
2 physics: {
3 default: 'arcade',
4 arcade: {
5 debug: true
6 }
7 },
8 scene: {
9 preload: preload,
10 create: create,
11 update: update
12 }
13 }
14 var game = new Phaser.Game(config)
15 function preload() {
16 this.load.image('ball', 'ball.png')
17 this.load.image('block', 'block.png')
18 }
19 function create() {
20 this.bola = this.physics.add.image(100, 100, 'ball')
21 this.cursors = this.input.keyboard.createCursorKeys()
22 }
23 function update() {
24 this.bola.setVelocity(0)
25 if (this.cursors.left.isDown) {
26 this.bola.setVelocityX(-100)
27 } else if (this.cursors.right.isDown) {
28 this.bola.setVelocityX(100)
29 }
30 if (this.cursors.up.isDown) {
31 this.bola.setVelocityY(-100)
32 } else if (this.cursors.down.isDown) {
33 this.bola.setVelocityY(100)
34 }
35 }
fig 30a
A caixa vermelha em volta da bola é o corpo da física, e o traço verde indica a direção
da velocidade. Isto está aparecendo porque nós habilitamos o debug da física no objeto de
configuração do Phaser.
Vamos analisar o código em game1.js:
Na linha 19 nós criamos uma imagem com um corpo físico com o método this.physics.add.image.
A imagem criada dessa forma tem as mesmas funcionalidades que uma imagem comum, mas
a diferença é que agora ela tem um corpo físico que pode interagir com outros corpos e com o
‘mundo’ físico.
Na linha 20 nós criamos o objeto cursors para podermos ler o acionamentos das setas do teclado.
Na linha 23, no método update(), primeiramente zeramos as velocidades vertical (Y) e horizontal
(X) da bola.
Na linha 25 definimos a velocidade X como -100, caso a seta esquerda esteja acionada.
Na linha 27 definimos a velocidade X como 100 positivo, caso a seta direita esteja acionada.
Nas próximas linhas fazemos o mesmo para a velocidade no eixo Y.
Detecção de colisões
1 function create() {
2 this.bola = this.physics.add.image(100, 100, 'ball')
3 // habilita colisão da bola com o mundo
4 this.bola.setCollideWorldBounds()
5 this.cursors = this.input.keyboard.createCursorKeys()
6 }
1 function create() {
2 this.bola = this.physics.add.image(100, 100, 'ball')
3 // habilita colisão da bola com o mundo
4 this.bola.setCollideWorldBounds()
5 // habilita o disparo do evento de colisão da bola com o mundo
6 this.bola.body.onWorldBounds = true
7 // checa se houve colisão de algum objeto com o mundo
8 this.physics.world.on('worldbounds', (body) => console.log(body))
9 this.cursors = this.input.keyboard.createCursorKeys()
10 }
1 function create() {
2 this.bola = this.physics.add.image(100, 100, 'ball')
3 this.bola2 = this.physics.add.image(200, 200, 'ball')
4 // habilita colisão da bola com o mundo
5 this.bola.setCollideWorldBounds()
6 this.bola2.setCollideWorldBounds()
7 // habilita o disparo do evento de colisão da cola com o mundo
8 this.bola.body.onWorldBounds = true
9 this.bola2.body.onWorldBounds = true
10 // define que a bola2 vai `recochetear`
11 this.bola2.body.setBounce(1, 1)
12 // habilita colisão entre as bolas e checa quando colidem
13 this.physics.add.collider(this.bola, this.bola2, (a, b) => console.log(`${a} co\
14 lide com ${b}`))
15 this.cursors = this.input.keyboard.createCursorKeys()
16 }
A próxima tarefa é criar um grupo de imagens e fazer com que colida com a bola.
A seguir temos o método create() atualizado com essa tarefa:
1 function create() {
2 // cria bola
3 this.bola = this.physics.add.image(100, 100, 'ball')
4 // habilita colisão da bola com o mundo
5 this.bola.setCollideWorldBounds()
6 // cria grupo de física passando um objeto de configuração.
7 this.group = this.physics.add.group({
8 bounceX: 1,
9 bounceY: 1,
10 collideWorldBounds: true
11 })
12 // cria 6 caixas e adiciona ao grupo de física.
13 for (let i = 1; i < 7; i++) {
14 let block = this.add.image(i * 150, 250, 'block')
15 this.group.add(block)
16 }
17 // habilita colisão entre a bola e o grupo.
18 this.physics.add.collider(this.bola, this.group, (a, b) => console.log(`${a} co\
19 lisão com ${b}`))
20 this.cursors = this.input.keyboard.createCursorKeys()
21 }
Se você prestar atenção, vai perceber que a bola colide com as caixas mas as caixas não colidem
entre si.
Para habilitar a colisão entre as caixas precisamos habilitar a colisão do grupo com ele mesmo:
1 function create() {
2 // cria bola
3 this.bola = this.physics.add.image(100, 100, 'ball')
4 // habilita colisão da bola com o mundo
5 this.bola.setCollideWorldBounds()
6 // cria grupo de física passando um objeto de configuração.
7 this.group = this.physics.add.group({
8 bounceX: 1,
9 bounceY: 1,
10 collideWorldBounds: true
11 })
12 // cria 6 caixas e adiciona ao grupo de física.
13 for (let i = 1; i < 7; i++) {
14 let block = this.add.image(i * 150, 250, 'block')
15 this.group.add(block)
16 }
17 // habilita colisão entre a bola e o grupo.
18 this.physics.add.collider(this.bola, this.group, (a, b) => console.log(`${a} co\
19 lisão com ${b}`))
20 this.physics.add.collider(this.group, this.group, (a, b) => console.log(`${a} c\
21 olisão com ${b}`))
22 this.cursors = this.input.keyboard.createCursorKeys()
23 }
fig 30b
tileset
tilemap - mundo
É importante observar que esta imagem final é construída dentro do Phaser em tempo de
execução.
Para montar o tilemap o Phaser precisa de 2 arquivos: uma imagem (geralmente um png com
fundo transparente) e um JSON com o mapeamento do tilemap.
Tiled
Para gerar o mapa JSON a partir de um tileset, utilizaremos um aplicativo open source muito
conhecido entre os desenvolvedores de games que é o Tiled, disponível para Windows, Mac e
Linux.
Tiled
Construção de um autódromo
Como nosso primeiro trabalho com o Tiled vamos construir um autódromo bem simples a partir
do seguinte tileset:
fig 32
O resultado será um tilemap como a figura a seguir. Você não precisa fazer exatamente igual, use
a sua criatividade.
fig 33
Temos aqui um passo a passo para a criação do tilemap, mas você pode também acessar esse
mesmo tutorial em vídeo no Youtube em
https://youtu.be/yh-7Xrnlf2k.
Passo a Passo
Abrir o Tiled e criar um novo mapa:
fig 34
fig 35
fig 36
Clicar no botão “Novo Tileset…” e abrir a imagem tiles-rua-02.png (esta imagem está na pasta
assets no código fonte baixado).
fig 37
No primeiro parâmetro Nome: devemos colocar um nome pelo qual o tileset vai ser identificado
no Phaser. No nosso caso vamos deixar como tiles-rua-02‘ mesmo.
Em seguida configurar o tileset: Largura e altura = 100px, Margem e Espaçamento = 0px.
fig 38
fig 39
fig 40
fig 41
fig 42
Exportar o map como autodromo.json (este é o JSON que vamos importar no Phaser)
fig 43
fig 44
fig 45
Para ver o resultado abra no seu editor de texto o arquivo autodromo.json que acabamos de criar.
Usando o Tilemap
Vamos agora aprender como importar o tilemap para dentro do Phaser.
1 var config = {
2 width: 800,
3 height: 600,
4 scene: {
5 preload: preload,
6 create: create
7 }
8 }
9 var game = new Phaser.Game(config)
10 function preload() {
11 // carrega o JSON
12 this.load.tilemapTiledJSON('map', 'autodromo.json')
13 // carrega o tileset
14 this.load.image('tiles', 'tiles-rua-02.png')
15 }
16 function create() {
17 // cria o tilemap
18 var map = this.make.tilemap({key: 'map'})
19 // cria o tileset onde o primeiro parâmetro é o nome do
20 // mapa definido no Tiled.
21 var tileset = map.addTilesetImage('tiles-rua-02', 'tiles')
22 // cria o layer do terreno
23 var terreno = map.createStaticLayer('terreno', tileset, 0, 0)
24 // cria o layer do arbusto
25 var arbusto = map.createStaticLayer('arbusto', tileset, 0, 0)
26 // cria o layer da pista
27 var pista = map.createStaticLayer('pista', tileset, 0, 0)
28 // reduz escala dos layers para o tamanho da tela
29 terreno.setScale(0.5)
30 arbusto.setScale(0.5)
31 pista.setScale(0.5)
32 }
Layer de objetos
Podemos ter no tilemap um layer para marcar posição de objetos no mapa. No próximo exemplo
iremos criar um layer de objetos no Tiled e exportar novamente o JSON. Então usaremos a
camada de objetos do novo mapa para posicionar algumas joias no mundo. Você pode também
assistir o vídeo sobre como criar layers de objetos em https://youtu.be/NgoEUH_FWvA
Primeiro criamos uma camada de objetos:
fig 46
Chamamos essa camada de joia e a arrastamos para o topo das camadas, acima de pista. Para
o Phaser, não importa em que nível esteja a camada de objetos, mas para nossa visualização no
Tiled, deixamos acima das outras.
fig 47
fig 48
Selecionamos a camada de objetos na lista de camadas e já podemos marcar pontos onde ficarão
os objetos.
fig 49
Crie vários pontos de objetos sobre o terreno. Em cada ponto marcado iremos colocar um sprite
de moeda no Phaser.
Faça algo do tipo…
fig 50
1 var config = {
2 width: 800,
3 height: 600,
4 scene: {
5 preload: preload,
6 create: create
7 }
8 }
9 var game = new Phaser.Game(config)
10 function preload() {
11 // carrega o JSON
12 this.load.tilemapTiledJSON('map', 'autodromo.json')
13 // carrega o tileset
14 this.load.image('tiles', 'tiles-rua-02.png')
15 //carrega spritesheet das moedas (coins)
16 this.load.spritesheet('moedas', 'moedas.png', {
17 frameWidth: 16,
18 frameHeight: 16
19 })
20 }
21 function create() {
22 // cria o tilemap
23 var map = this.make.tilemap({
24 key: 'map'
25 })
26 // cria animação da moeda girando
27 this.anims.create({
28 key: 'moeda-girando',
29 frames: this.anims.generateFrameNumbers('moedas', {
30 start: 0,
31 end: 7
32 }),
33 frameRate: 10,
34 repeat: -1
35 })
36 // cria o tileset onde o primeiro parâmetro é o nome do
37 // mapa definido no Tiled.
38 var tileset = map.addTilesetImage('tiles-rua-02', 'tiles')
39 // cria o layer do terreno
40 var terreno = map.createStaticLayer('terreno', tileset, 0, 0)
41 // cria o layer do arbusto
42 var arbusto = map.createStaticLayer('arbusto', tileset, 0, 0)
43 // cria o layer da pista
44 var pista = map.createStaticLayer('pista', tileset, 0, 0)
45 /* cria moedas nos pontos da camada 'joia' */
46 // pega array de objetos da camada 'joia'
fig 51
Tiles de colisão
Num mapa de jogo sempre temos elementos de colisão, tais como paredes, ão, objetos e etc.
Para fazer isso no Phaser usaremos uma técnica de marcar tiles de colisão no mapa.
A ideia é criar uma propriedade booleana personalizada no tileset (por exemplo “colisao”) e
marcar alguns tiles específicos com o valor de colisão = true. Feito isso podemos, dentro do
Phaser, fazer com que objetos do jogo colidam com esses tiles marcados.
Vamos ao passo a passo:
[Um vídeo tutorial está disponível em https://youtu.be/jiVNp0V-m5Y]
Clique no botão Editar Tileset.
fig 52
fig 53
fig 54
fig 55
Agora selecione só os tiles de grama e de terra. E marque a propriedade “colisão” como true no
ebox da propriedade.
fig 56
Salve o arquivo.
fig 57
E novamente o autodromo.json.
fig 58
1 var config = {
2 width: 800,
3 height: 600,
4 pixelArt: true,
5 physics: {
6 default: 'arcade',
7 arcade: {
8 // desabilita debug da física
9 debug: false
10 }
11 },
12 scene: {
13 preload: preload,
14 create: create,
15 update: update
16 }
17 }
18 var game = new Phaser.Game(config)
19 function preload() {
20 // carrega o JSON
21 this.load.tilemapTiledJSON('map', 'autodromo.json')
22 // carrega o tileset
23 this.load.image('tiles', 'tiles-rua-02.png')
24 // carrega o carro
25 this.load.image('carro', 'circle.png')
26 }
27 function create() {
28 // cria o tilemap
29 var map = this.make.tilemap({
30 key: 'map'
31 })
32 // cria o tileset onde o primeiro parâmetro é o nome do
33 // mapa definido no Tiled.
34 var tileset = map.addTilesetImage('tiles-rua-02', 'tiles')
35 // cria o layer do terreno
36 var terreno = map.createStaticLayer('terreno', tileset, 0, 0)
37 // cria o layer do arbusto
38 var arbusto = map.createStaticLayer('arbusto', tileset, 0, 0)
39 // cria o layer da pista
40 var pista = map.createStaticLayer('pista', tileset, 0, 0)
41 // cria o carro com imagem pois não precisamos de animação
42 this.carro = this.physics.add.image(150, 150, 'carro')
43 // habilita tiles de colisão do terreno
44 terreno.setCollisionByProperty({
45 colisao: true
46 })
47 // habilita colisão entre o carro e o terreno
48 this.physics.add.collider(this.carro, terreno)
49 //câmera segue o carro
50 this.camera = this.cameras.main
51 this.camera.startFollow(this.carro)
52 this.camera.setBounds(0, 0, map.widthInPixels, map.heightInPixels)
53 // cria o cursos para detectar as setas do teclado
54 this.cursors = this.input.keyboard.createCursorKeys()
55 }
56 function update() {
57 // as próximas linhas definem a velocidade do carro
58 // com base nas setas pressionadas no teclado
59 this.carro.setVelocity(0)
60 if (this.cursors.left.isDown) {
61 this.carro.setVelocityX(-200)
62 } else if (this.cursors.right.isDown) {
63 this.carro.setVelocityX(200)
64 }
65 if (this.cursors.up.isDown) {
66 this.carro.setVelocityY(-200)
67 } else if (this.cursors.down.isDown) {
68 this.carro.setVelocityY(200)
69 }
70 }
• Linha 9: desabilitamos o debug da física, para não aparecer as bordas físicas do objeto e
nem o indicador da velocidade.
• Linhas de 19 a 25: carregamos o mapa json e as imagens do jogo.
• Linha 29: criação do tilemap.
• Linha 34: criação do tileset.
• Linhas 36 a 40: criação dos layer (camadas).
• Linha 42: criação do carro com um corpo físico.
• Linha 44: habilitamos os tiles de colisão do terreno. Apenas os tiles com a propriedade
colisao marcada como true.
• Linha 48: habilita colisão do carro com o terreno.
• Linhas 50 a 52: Faz a câmera seguir o carro.
• Linha 54: cria o cursos para controlar o carro pelas setas do teclado.
• Linhas 59 a 68: controla o carro com as setas do teclado.
fig 59
fig 60
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Phaser</title>
6 </head>
7 <body>
8 <script src="//cdn.jsdelivr.net/npm/phaser@3.20.0/dist/phaser.js"></script>
9 <script src="game.js" type="module"></script>
10 </body>
11 </html>
Na linha 9 temos o parâmetro type="module". Com esse parâmetro estamos dizendo ao browser
11 // texto
12 this.texto = this.add.text(200, 150, 'Boot', {
13 fontSize: 40,
14 fontFamily: "Arial"
15 })
16 this.texto.setOrigin(0.5)
17 // chama a próxima cena após 3 segundos
18 this.time.delayedCall(3000, () => this.scene.start('Preloader'), [], this)
19 }
20 }
29 })
30 }
31 }
fig 61
fig 62
index.html
fig 63
Criação de jogos HTML5 com Javascript e Phaser - gidenilson@gmail.com
137
Já vimos em exemplos anteriores a estrutura do nosso index.html para carregar o Phaser. Mas
vale à pena ressaltar algumas linhas.
Nas linhas 5 e 6 definimos a meta viewport para que nossa página seja responsiva, se adaptando
ao tamanho da tela do dispositivo.
Nas linhas de 8 a 17 definimos a margin e padding do body como 0, definimos a cor de fundo
como preto, carregamos a webfonte PressStart2P para ser utilizada no game.
Nas linha 18 a 22 estilizamos a classe .texto para posicionar um elemento div fora da área visível
do dispositivo. Como já vimos em capítulos anteriores, a função dessa div é carregar a webfonte
no documento para garantir que estará disponível para ser utilizada pelo Phaser.
Na linha 28 carregamos a biblioteca do Phaser na sua versão minificada.
Na linha 29 amamos o arquivo game.js que é responsável por dar início ao game. Repare que
estamos definindo um parâmetro type="module", para que o browser saiba que iremos trabalhar
com módulos no javascript.
game.js
fig 64
Este código é responsável por preparar tudo para instanciar o Phaser e iniciá-lo.
Primeiro importamos o objeto de configuração que está definido no arquivo ./config/phaser.js.
Em seguida, nas linhas de 2 a 6 carregamos as scenes do game.
Nas linhas de 8 a 16 criamos a classe Game que herda de Phaser.Game. Dentro do bloco de criação
da classe passamos o objeto de configuração para a classe pai amando o método super, e
em seguida registramos todas as scenes do game para que possamos amá-las no momento
oportuno.
Na linha 20 instanciamos a classe Game e iniciamos o game.
Nas linhas de 21 a 24 adicionamos a game um objeto de dados o qual iremos utilizar em algumas
scenes do game.
scenes/BootScene.js
fig 65
Essa é a primeira cena a ser amada, e é nela que carregamos alguns assets mínimos que iremos
utilizar na PreloaderScene.
Criamos a classe herdando de Phaser.Scene. No constructor Passamos para a classe pai o nome
da scene pelo método super.
Nas linhas de 6 a 9 carregamos a imagem do logotipo e a barra de progresso.
No método create definimos valores para o objeto de dados que foi criado em game.js. Observe
o caminho para se egar a esse objeto a partir de uma scene: this.sys.game.model, onde this
faz referência à própria scene.
scenes/PreloaderScene.js
fig 66
método preload()
No método preload() adicionamos todos os assets do game à fila de carregamento. Nesse método
podemos também monitorar esse carregamento sendo possível assim criar barras de progresso e
mostrar o carregamento dos assets.
Na linha 12 carregamos a imagem do tileset do tilemap. Lembrando que o primeiro parâmetro é
sempre a key pela qual iremos amar o assets quando necessário.
tuxmon.png
Na linha 15 carregamos o map.json que exportamos do Tiled, atribuindo uma key “map”. Perceba
que para criar o tilemap os 2 arquivos, o tileset e o json, são carregados separadamente.
Na linha 17 carregamos o spritesheet da “beth”. Nos parâmetros frameWidth e frameHeight
passamos as dimensões de cada sprite desse spritesheet. É a partir dessas dimensões que o Phaser
irá “separar” cada frame do spritesheet.
beth.png
Na linha 24 caregamos uma imagem que será utilizada na scene dos créditos.
creditos.png
moedas.png
fig 67
fig 68
Nas linhas 61 a 62 declaramos as constantes width e height com a largura e altura do canvas do
game respectivamente.
Na linha 65 colocamos o logo na tela, lembrando que a imagem do logo foi carregada previamente
em SceneBoot, assim como a barra de progresso.
Na linha 68 colocamos na tela a barra de progresso utilizando como referência as constantes
width e height.
Na linha 70 definimos o ponto de origem na barra de progresso para o topo do canto esquerdo. Isso
é necessário para que o redimensionamento desta barra que será feito durante o carregamento
dos assets tenha como origim o início da barra de progresso.
Nas linhas 73 criamos um texto para mostrar a porcentagem do carregamento. Este texto também
é posicionado na tela tomando como referência as constantes width e height. Perceba que
estamos utilizando a webfonte “PressStart2P”, que foi carregada lá no nosso index.html. Na
linha 82 definimos a origem do texto como sendo o centro do mesmo (essa linha é desnecessária
já que por padrão o pivô dos objetos é o centro mesmo).
fig 69
Nas linhas 85 a 93 colocamos na tela outro texto para mostrar o nome do asset que está sendo
carregado.
Nas linhas 96 a 98 criamos um evento do tip progress para “escutar” o carregamento dos assets.
A função callba vai atualizar a escala da barra de progresso e a porcentagem de carregamento
dos assets.
Nas linha 102 e 103 criamos outro evento, do tipo fileprogress. A função callba vai atualizar
o texto com o nome dos assets conforme vão sendo carregados.
E nas linhas de 107 a 110 criamos mais um evento agora do tipo complete cuja função callba
destrói a barra de progresso e os textos de porcentagem e nome do asset.
Método create()
fig 70
scenes/MenuScene.js
fig 71
fig 72
Depois de carregar todos os assets paramos nesta scene onde temos 2 botões: [iniciar] e
[créditos].
Nas 2 primeiras linhas importamos as classes Botao e Audio (essas classes serão estudadas
posteriormente).
Nas linhas 3 a 4, constructor(), criamos a classe MenuScene que herda de Phaser.Scene, e
passamos o nome da scene amando o método super.
Nas linhas de 8 a 11 temos o método init() que por padrão é o primeiro a ser executado
automaticamente quando a scene iniciar. Dentro deste método instanciamos a classe Audio e
anexamos ao objeto de dados do jogo (aquele objeto criado no game.js). Criamos também uma
variável da scene, this.audio e colocamos o objeto de áudio criado.
Nas linhas 13 e 14 criamos as constantes width e height com as dimensões da tela.
Nas linhas 18 e 19 criamos a variável this.btnIniciar instanciando a classe Botao, que será vista
adiante. Posicionamos o botao na tela tendo como referência as constantes width e height. Na
linha 20 definimos o valor Game para a propriedade target do botão criado. Isso fará com que ao
ser pressionado o game passe para a scene com o nome Game.
Nas linhas 23 a 25 criamos outro botão, para navegar até a scene Credits.
Nas linhas 28 a 30 tocamos a música de espera com o volume em 30% e em loop.
E por fim, na linha 35 exportamos a classe.
scenes/CreditsScene.js
fig 73
Esta scene é nossa tela de créditos, nela colocamos a imagem com os créditos e um botão para
acessar o menu. O código dispensa comentários.
scenes/GameScene.js
Esta é a scene onde o game acontece.
fig 74
Até a linha 12 carregamos as classes e estendemos a classe Phaser.Scene da mesma forma como
nas outras scenes.
Método create()
fig 75
fig 76
Nas linhas de 28 a 40 instanciamos os objetos do game. Cada um desses objetos está implementado
num arquivo separado por questões de organização do código do nosso game. Veremos cada
classe mais adiante.
fig 77
Nas linhas 43 e 44 posicionamos a personagem na posição inicial. Esta posição vem de um objeto
específico no JSON do tilemap. Este código ficará claro quando estudarmos a classe Mundo.
Nas linhas 46 a 48 habilitamos as colisões entre a personagem e as camadas (layers) do tilemap.
Nas linhas de 51 a 54 habilitamos a câmera do game a acompanhar a personagem.
fig 78
Método update()
fig 79
O método update() é executado a cada frame do game a 60fps (60 frames por segundo). Neste
método atualizamos os objetos beth e inimigo caso o estado do jogo seja “jogando”.
Na linha 81 exportamos a scene.
Podemos perceber que o código da SceneGame é relativamente pequeno para um jogo. Isso é
porque a lógica do game está distribuída nas classes dos objetos do jogo que iremos estudar em
seguida. Mas antes vamos olhar rapidamente o código do script config/phaser.js.
config/phaser.js
fig 80
type
Phaser.AUTO
Deixa o Phaser escolher automaticamente o modo de renderização.
backgroundColor
#fcf8e8
pixelArt
true
Esta propriedade sendo true define que o Phaser vai deixar o game com aspecto pixelado quando
as imagens são renderizadas com escalas aplicadas.
physics
Aqui configuramos a física do jogo para “arcade”, que é um dos motores de física suportados
disponíveis no Phaser.
scale
mode: Phaser.Scale.FIT
Define que o canvas do game será redimensionado para o preenar o tamanho da tela do
dispositivo.
autoCenter: Phaser.Scale.CENTER_BOTH
width: 800
Largura do canvas.
classes/Beth.js
Vamos agora analisar uma das classes mais importantes do jogo, que é a classe Beth que define
o player do jogo.
fig 81
Método update()
fig 82
fig 83
fig 84
fig 85
classes/Audio.js
Nesta classe colocamos todos os objetos de sons do game, para serem amados no mo-
mento oportuno. Lembrando que todos os assets de sons já foram previamente carregados na
PreloaderScene.
fig 86
Esta classe dispensa muito comentários pois creio que a implementação está bastante clara.
Veremos mais adiante como executar os sons a partir dessa classe.
classes/Botao.js
Esta classe é responsável por criar os botões que temos no jogo. São botões de texto que quando
clicados amam uma scene.
fig 87
Na linha 3 a 7 amamos a classe pai passando para o método super() a scene, a posição x e y,
e um objeto de configuração do texto.
Na linha 10 definimos uma propriedade “privada” this._target, que irá conter o nome da scene
a ser amada quando o botão for clicado. Nós setamos o valor dessa propriedade amando o
método seer target definido na linha 17.
Na linha 11 definimos a origem do botão para o centro do mesmo (isso já era padrão e não
precisaria ser definido aqui, mas fica como exemplo).
Na linha 12 adicionamos o botão na scene.
Na linha 13 tornamos o botão clicável.
Na linha 14 definimos o evento “pointerdown”, que mudará o tamanho da fonte quando
pressionarmos o botão.
Na linha 15 definimos o evento “pointerup” que amará a scene alvo quando soltarmos o botão.
classes/Colisoes.js
Esta classe não é um objeto de exibição, ela é responsável por tratar as colisões entre a personagem
e as moedas, e a personagem e o inimigo.
O código é bem simples. Temos 2 métodos que serão utilizados como callba no evento de
colisões que veremos mais adiante.
fig 88
Método bethMoedas(moeda)
Na linha 7 destruimos a moeda que colidiu com a personagem. Nas linhas 8 e 9 atualizamos a
contagem de pontos e as moedas colhidas no objeto de dados que criamos lá no nosso arquivo
game.js.
Na linha 10 atualizamos a exibição de pontos e moedas no objeto da classe Gui que ve-
remos mais a frente. Este objeto está anexado à scene, por isso podemos acessá-lo com
this.scene.gui.update().
Método bethInimigo()
Nas linhas 18 e 19 temos o método bethInimigo que será usado como callba da colisão entre
a personagem e o inimigo. Este método apenas emite o evento “pegou”, que será escutado em
GameScene, linha 62.
fig 89
A vantagem de montar esse padrão é que poderíamos ter muitas outras ações e só precisaríamos
para isso criar uma classe para cada nova ação, sem precisar mexer nas outras classes.
Vamos agora analisar as 3 classes dessa corrente.
classes/Responsabilidades.js
fig 90
Nas linhas nas primeiras 2 linhas importamos as 2 classes auxiliares correspondentes as ações
“vitoria” e “morreu”.
Na linha 6 colocamos o objeto this.escene.sys.game.audio na variável this.audio (), lembrando
que o objeto this.scene.sys.game.audio foi criado na linha 9 em SceneMenu.js.
No método executa(acao) primeiramente paramos a música do jogo e iniciamos tocamos a música
de espera.
Na linha 16 instanciamos a classe ResponsabilidadeMorreu, passando como parâmetro this.scene.
Na linha 17 instanciamos a classe ResponsabilidadeVitoria, passando como parâmetro this.scene
e a this.morreu.
Na linha 18 amamos this.vitoria.executa(acao), que dará início à corrente de responsabilidades.
classes/ResponsabilidadeVitoria.js
fig 91
fig 92
fig 93
Nas linha 17 a 27 criamos um objeto para mostrar o texto “VITÓRIA ‼!” na tela.
Nas linhas 29 e 30 definimos o fator de scroll para 0 fixando o texto na tela e definimos a
profundidade com 100 para, da mesma forma, garantir que o texto não seja sobreposto por outros
objetos.
fig 94
classes/ResponsabilidadeMorreu.js
fig 95
fig 96
fig 97
Nas linhas de 14 a 28 colocamos na tela o texto “MORREU :-(“, com o fator de scroll 0 e
profundidade 100 como nos textos anteriores.
fig 98
fig 99
classes/Inimigo.js
Aqui criamos a classe Inimigo.
fig 100
Método update().
fig 101
Este método rodará a cada frame pois será amado pelo update() da GameScene.
Na linha 14 movemos o inimigo na direção da personagem a cada frame, com uma velocidade
de 100 pixels por segundo com o método moveToObjects() que é fornecido pelo motor de física
arcade do Phaser.
Na linha 19 carregamos a animação atual na variável animação.
Na linha 20 calculamos o ângulo de movimento do inimigo por meio do método this.body.velocity.angle().
Este método é fornecido pelo corpo físico do objeto. Na documentação do Phaser você encontrará
outros métodos do corpo físico de um objeto.
Nas linhas 21 a 30 definimos qual será a animação aplicada ao inimigo correspondente ao ângulo
do movimento.
Nas linhas 32 a 34 aplicamos a nova animação caso a animação atual seja diferente da nova
animação.
Método criaAnimacoes().
fig 102
Das linhas 37 a 82 criamos todas as animações que serão utilizadas pelo inimigo.
classes/Moeda.js
Nesta classe definimos as moedas do game.
fig 103
classes/GrupoMoedas.js
A função dessa classe é criar as moedas nas posições de objetos do layer de objetos do tilemap
que foi carregado na linha 31 da classe Mundo.js (this.map.getObjectLayer("Moedas")).
fig 104
Nas linhas 6 e 7 iteramos no array moedas e criamos no grupo 1 moedas para cada objeto do
array.
classes/Gui.js
Essa classe é um container responsável por mostrar na tela do game o score e as moedas colhidas.
Criamos esta classe herdando de Phaser.GameObjects.Container, que é uma classe do Phaser
que serve de container para objetos de exibição.
fig 105
Nas linhas de 5 a 13 criamos o objeto de texto que mostrará o score do game. Na linha 16
adicionamos esse texto à própria classe.
fig 106
Da mesma forma nas linhas de 17 a 28 criamos o objeto de texto para mostrar as moedas colhidas
e adicionamos na própria classe.
fig 107
Método update()
fig 108
No método update atualizamos os textos. Esse roda a cada frame do game, pois será amado a
partir do método update() na classe GameScene.
Todo o código do jogo está nos arquivos fontes do curso. Estudar o código do jogo é uma excelente
forma de aprender e fixar os conhecimentos.
ien
fig 109
189
assets
Como sempre, nesta pasta encontram-se todos as imagens mapas sons e todos os assets do game.
assets/atlas
classes
config
scenes
index.html
fig 110
Este arquivo é semelhante ao index.html do game Beth, mas vamos analisar as linhas mais
importantes.
Na tag style nós definimos magin e padding para que não fique nenhuma borda no can-
vas do game. Nesta tag também configuramos e carregamos a fonte a partir do arquivo
assets/font/PressStart2P-Regular.tff.
Configuramos também uma classe .texto que fará com que o texto que utilizaremos somente
para carregar efetivamente a fonte e deixá-la disponível para o Phaser fique fora da tela.
Na linha 27 temos a div com a classe texto.
Na linha 28 carregamos a biblioteca do Phaser e na linha 29 carregamos o nosso arquivo inicial
game.js. Acrescentamos o atributo type="module" para podermos importar nossas classes para
o game de forma modular. Sem esse atributo o browser não saberá que estamos trabalhando com
módulos javascript.
game.js
Aqui está o arquivo inicial responsável por carregar as configuração e iniciar o game.
fig 111
fig 112
fig 113
Na linha 20 criamos a variável game e iniciamos o jogo. Nas linha 22 a 27 anexamos ao game um
objeto de dados game.model. Este objeto de dados poderá ser acessado a partis das scenes pelo
atalho this.sys.game.model.
fig 114
scenes/BootScene.js
fig 115
Está é a a primeira scene do game. A função dessa classe é carregar previamente os assets que
serão utilizados na PreloadesScene. No método preload() estamos carregando apenas o logotipo
e a barra de progresso. No método create() inicializamos o score do game e passamos o controle
para a próxima scene.
scenes/PreloaderScene.js
Aqui carregamos todos os assets do game enquanto mostramos na tela uma barra de progresso,
um texto mostrando a porcentagem do carregamento e um texto com o nome do asset que está
sendo carregado.
Na primeira linha importamos a classe Animação que será utilizada mais adiante para criar todas
as animações do game.
método preload()
fig 116
fig 117
fig 118a
fig 118b
fig 118c
fig 119
A partir da linha 33 começamos a carregas os sons do game. Como vimos antes cada som carrega
2 arquivos de áudo nos formatos ogg e mp3, para garantir que o browser execute o som.
fig 120
fig 121
Nas linhas 75 a 84 criamos o objeto texto da porcentagem e definimos sua origem para o centro
do objeto.
fig 122
Nas linhas de 86 a 95 criamos o objeto texto dos assets e definimos sua origem para o centro do
objeto.
fig 123
Nas linhas 98 a 100 escutamos o evento ‘progress’ onde atualizaremos a escala da barra de
progresso e o texto da porcentagem.
fig 124
fig 124
Nas linhas 104 a 105 escutamos o evento ‘fileprogress’ para atualizar o texto dos assets enquanto
são carregados.
Nas linhas 109 a 111 escutamos o evento ‘complete’ para destruir a barra de progresso, o texto
dos assets e o texto da porcentagem.
método create()
fig 125
fig 125
Na linha 118 invocamos a classe responsável por criar toda as animações do game. E depois de
meio segundo, na linha 120, passamos o controle para MenuScene.
scenes/MenuScene.js
fig 126
fig 126
Nas primeiras linha importamos as classes Buon e Audio que serão utilizadas adiante para criar
os botões do menu e tocar a música de espera.
### método init()
fig 127
fig 127
fig 128
fig 129
Nas linhas 22 e 23 pegamos as dimensões da tela e salvamos nas variáveis constantes width e
height.
fig 130
fig 130
Nas linhas 26 e 27 criamos um botão para iniciar o game instanciando a classe Buon
(estudaremos essa classe detalhadamente mais adiante). Na linha 28 definimos a scene alvo desse
botão.
fig 131
fig 131
Nas linhas 31 e 32 criamos um botão para acessar a scene dos créditos do game.
scenes/CreditsScene.js
Nesta cena apenas apresentamos os créditos do game. E colocamos um botão para voltar ao
menu.
fig 132
fig 133
fig 134
fig 135
Nas linhas 30 a 32 colocamos e posicionamos um botão que quando acionado retorna o controle
para MenuScene.
Nesta scene podemos colocar outras informações sobre o game, conforme sua vontade. No lugar
do texto poderíamos colocar uma imagem ou até mesmo um sprite animado. Isto que colocamos
é apenas um exemplo.
scenes/GameScene.js
fig 136
fig 137
fig 138
fig 139
fig 140
Na linha 46 escutamos o evento “egou”, emitido pela classe Chien, para executarmos
this.acao.egou(). Examinaremos a classe Acao posteriormente.
fig 141
fig 142
Na linha 59 executamos o método update() da classe Chien a cada frame do game. A classe
ien também será estudada posteriormente.
classes/Mundo.js
fig 143
Esta classe tem a responsabilidade de criar o nosso mundo, instanciando o tilemap, os layers e
layers de objetos.
fig 145
fig 146
Na linha 15 a 23 criamos métodos get para fornecer acesso às variáveis this._player, this._-
arrival e this._eggs.
classes/Chicken.js
fig 147
Esta é a classe onde criamos a personagem. Aqui criamos o sprite, capturamos as setas do teclado
para alterar o movimento e as animações correspondentes.
Na linha 3 passamos para a classe herdada a posição x e y do player, a textura (spritesheet
carregado em PreloaderScene) e o número do frame com o qual iremos iniciar a exibição da
personagem.
fig 148
Na linhas 14 a 16 tocamos o som dos passos em loop e logo em seguida, na linha 18, colocamos
os sons dos passos em pausa.
fig 149
método reset()
fig 149
Nas linhas 20 a 23 criamos o método reset() que servirá para posicionar o player na posição
inicial. Os valores divididos por 2 se devem a que, como já vimos, o tamanha original no nosso
tilemap é o dobro do tamanho da tela do game.
fig 150
fig 151
Nas linha 32 e 33 emitimos o evento “egou” caso a personagem tenha alcançado a posição de
egada.
fig 152
fig 153
Se nenhuma seta está pressionada definimos a velocidade com 0, pausamos o som dos passos
e ajustamos a animação da personagem conforme o valor de this.getData('direcao') que
definimos no bloco anterior com a ajuda do método this.setData('direcao', direcao)
classes/Vehicle.js
fig 154
método setMoviment()
fig 155
helper do Phaser. Procure saber mais sobre ele na documentação do Phaser procurando por
Phaser.Math.RND.
fig 156
Nas linhas 20 a 27 ajustamos a posição horizontal do veículo de acordo com a direção do mesmo.
fig 157
Método update()
No método update() redefinimos o veículo caso tenha ultrapassado o lado esquerdo ou direito
da tela, executando novamente o método this.setMoviment().
fig 158
classes/VehiclesGroup.js
Nesta classe instanciamos 9 veículos e adicionamos os mesmos dentro do grupo.
fig 159
fig 160
Voltando ao código. Na linha 4 amamos a classe herdada passando como parâmetros o mundo
físico e a scene.
Na linha 5 adicionamos o grupo da scene.
Na linha 6 habilitamos que o método this.update() desta classe rode a cada frame do game.
Nas linhas 7 e 8 criamos e adicionamos ao grupo 9 veículos com posições y variando de 100 a
500px.
Na linha 10 amamos o método this.setDepth(200, 1) este método definirá a profundidade
dos veículos deste grupo a partir de 200, incrementando 1 para cada veículo. Então a profundidade
dos veículos será: 200, 201, 202, 203, 204, 205, 206, 207, 208.
classes/Egg.js
Nesta classe definimos a imagem do ovo.
fig 161
classes/EggsGroup.js
fig 162
Na linha 13 nós carregamos na variável ovos um array com a posição dos ovos embaralhados
(com o helper Phaser.Math.RND.suffle). O array this.mundo.eggs vem do objectlayer “posicao”
no tilemap.
Na linha 14 definimos que colocaremos na tela apenas 20 ovos, ignorando o resto. Como o array
foi embaralhado aleatoriamente, esses 20 ovos ocuparam posições aleatórias.
Na linha 15 instanciamos a classe Egg dentro do grupo.
classes/Gui.js
fig 163
fig 164
fig 165
fig 166
fig 167
Na linha 13 instanciamos a classe GameOver que é responsável por colocar na tela alguns objetos
de exibição como textos e um botão para reiniciar e outro para voltar ao MenuScene.
fig 168
fig 168
fig 169
O método pegouTudo() ainda não tem nenhuma lógica implementada. Você pode aproveitar e
criar alguma coisa aqui.
fig 170
fig 170
No método chegou() tocamos a música de vitória e implementamos a seguinda lógica para somar
pontos:
classes/Colisoes.js
Esta classe, como o nome sugere, trata as colisões entre os objetos do game.
fig 171
fig 172
fig 173
Método ovoChicken()
Este método trata a sobreposição da personagem com o ovo.
fig 174
Método veiculoChicken().
fig 175
classes/Animacoes.js
Nesta classe criamos todas as animações do game e não necessita comentários. Abra o arquivo
para ver mais.
classes/Audio.js
Nesta classe criamos todos os objetos de áudio do game.
fig 176
classes/Button.js
Nesta classe criamos o botão que será instanciado em algumas scenes.
fig 177
fig 178
A base de implementação desse game são as funcionalidades Path e Follower do Phaser, que já
estudamos anteriormente.
Basicamente fazemos o seguinte:
Se você for rodar esse jogo como mobile eu aconselho a diminuir o tempo para 5 segundos pois,
como já dissemos, a jogabilidade com o tou é bem melhor.
Passemos então ao código.
fig 179
index.html
fig 180
Para este game utlizaremos a fonte BigShouldersDisplay-Medium.tff, que pode ser encontrada no
GoogleFonts. Mas no código fonte já temos disponível. Neste treo de código definimos também
padding e margin para 0 e deixamos um baground preto.
fig 181
Nas linhas 19 e 20 adicionamos uma div que não será visível na tela, só com a função de forçar
o carregamento da fonte.
A partir da linha 21 temos um caso diferente que nos games anteriores. Aqui não vamos
importar as scenes e as classes como módulos, mas como arquivos simples de Javascript. Então
precisaremos importar cada scene e classe como um script no body do html.
Nas linhas 21 e 22 carregamos a biblioteca do Phaser e o script de configuração.
fig 182
fig 183
game.js
fig 184
Aqui registramos as scenes e na linha 13 instanciamos e damos início ao game. Observe que não
precisamos utilizar de import porque já carregamos todos os scripts no index.html.
scene/BootScene.js
fig 185
scene/CreditsScene.js
Esta classe não está implementada, mas o código roda sem ela.
fig 186
scene/PreloaderScene.js
Aqui são carregados os assets do game.
fig 187
fig 188
fig 189
fig 190
fig 191
fig 192
fig 193
fig 194
fig 195
fig 196
scene/MenuScene.js
fig 197
fig 198
Classes do game
Antes de continuarmos a análise da GameScene, vamos estudar as classes dos objetos do jogo.
class/Gui.js
Esta classe é responsável por mostrar o score e o level em que o jogo se encontra. A implementa-
ção herda de Phaser.GameObjects.Container, sendo portanto um container onde estão os textos
que mostram as informações.
fig 199
fig 200
fig 201
fig 202
class/Timer.js
Esta classe é responsável por executar a contagem regressiva e emitir o evento ‘endtime’ se a
contagem egar a zero.
fig 203
fig 204
fig 205
fig 206
fig 207
fig 208
class/Bee.js
Esta é a classe que define a abelha. Ela é responsável por criar a curva a ser seguida pela abelha,
criar as animações, escutar os eventos de cli na abelha, entre outras coisas.
fig 209
fig 210
fig 211
fig 212
fig 213
fig 214
fig 215
fig 216
fig 217
fig 218
class/Death.js
Esta classe herda de Phaser.GameObjects.Sprite e é responsável por mostrar a abelha morrendo.
A lógica consiste em substituir a abelha por esse sprite adicionado exatamente da posição da
abelha. Vamos entender melhor quando estudarmos a GameScene.
fig 219
class/Message.js
Esta classe é bem simple. A função dela é mostrar no centro da tela uma mensagem de texto. Ela
será utilizada apenas para colocar a mensagem de “Game Over” na tela. Não seria tão necessário
criar uma classe só para isso ao invés de criar o objeto texto diretamente na GameScene, no
entanto optamos por fazer dessa forma por questões de não quebrar a padrão de desenvolvimento
que estamos utilizando no game.
fig 220
class/GameSound.js
Aqui definimos a música e os sons do game, como já vimos nos games anteriores.
fig 221
fig 222
No método play() tocamos em loop a música e o som da abelha. E no método stop() paramos
esses sons.
fig 223
No método pause() pausamos a música e o som da abelha, e no método splash() tocamos o som
da morte.
scene/GameScene.js
Aqui temos a alma do game. Dentro desta classe instanciamos todas as outras classes já vistas
até aqui.
fig 224
fig 225
fig 226
fig 227
fig 228
fig 229
fig 230