Fundamentos: Game estilo Frogger com Phaser 3 - Parte 5 (final)

Cover image

Esse artigo faz parte de uma série de tutoriais referente a criação de um game estilo frogger, você está na parte 5 (final).

Se você não viu o primeiro post dessa série. Você pode continuar a partir daqui, basta iniciar a partir do commit referente a parte 4 nesse repositório.

Essa e uma série finalizada, você pode ver todos os posts aqui:

Ações em Caso de Vitória ou Derrota

No momento concluímos a maior parte do jogo, inclusive a de colisões. Está na hora de trocar os dois console.log dentro do método checkCollisions.

Abaixo do método checkCollisions, adicione:

/* quando o jogador vencer */
playerWin () {
  this.player.setPosition(this.gameWidth / 2, this.gameHeight - 20);
}

Agora dentro do método checkCollisions, substitua o console.log('venci!') por:

this.playerWin();

No momento esse método está apenas fazendo com que o jogador volte a sua posição inicial, em breve adicionaremos mais funcionalidades.

Abaixo do método playerWin adicione:

/* quando o jogador perder */
gameOver () {
  this.gamePause = true; // pausa o jogo

  // adiciona um efeito de camera "shake" na tela principal do jogo
  this.cameras.main.shake(500);

  /* 
    espera a o efeito de camera shake acabar e executa uma callback, que irá
    executar outro efeito de camera: "fade"
  */
  this.cameras.main.on('camerashakecomplete', () => this.cameras.main.fade(500));

  /* 
    espera a o efeito de camera fade acabar e executa uma callback, que irá
    reiniciar a scene atual
  */
  this.cameras.main.on('camerafadeoutcomplete', () => this.scene.restart());
}

É um método simples mas que introduz a uma das funcionalidades mais legais do Phaser, os efeitos de câmera! você pode ver mais exemplos aqui.

E uma funcionalidade extremamente usada, e em diversos cenários. Não tem muito mistério a ser explicado e detalhado, caso você tenha alguma dúvida no código pode mandar mensagem no meu e-mail ou twitter.

No final do método eu chamo this.scene.restart() referente a scene atual do jogo, como o nome já diz, reinicia a cena atual do jogo.

Perceba que no início do método eu seto true para this.gamePause, é um detalhe sutil, mas no momento se você for para o jogo e o frog colidir com os inimigos, o efeito de câmera ocorre, mas você continua podendo mover o jogador e os inimigos continuam se movendo, eu particularmente não acho isso legal para experiência do usuário.

Dentro do método init abaixo de this.spaceKey adicione:

// controla o fluxo do jogo
this.gamePause = false;

e no método update:

update () {
  // verifica se é para pausar o jogo
  if (this.gamePause) return;

  // a cada loop o método e executado novamente
  this.carMovement();
  this.truckMovement();
  this.playerMovement();
  this.checkCollisions();
}

Agora perceba que o jogador ao perder, o jogo pausa até que a scene seja reiniciada.

Adicionando Áudios

Um jogo sem efeito sonoro perde toda a magia, e adicionar sons com o Phaser é extremamente simples. Dentro do método preload adicionaremos os arquivos de áudio ficando dessa forma:

preload () {
  // carregando arquivo do tipo imagem
  this.load.image('background', './src/assets/background.png');
  this.load.image('car', './src/assets/car.png');
  this.load.image('truck', './src/assets/truck.png');

  // carregando spritesheet
  this.load.spritesheet('frog', './src/assets/frog.png', {
    frameWidth: 54, // largura do quadro
    frameHeight: 42, // altura do quadro
  });

  // carregando sons
  this.load.audio('victory', ['./src/assets/victory.wav']);
  this.load.audio('gameOver', ['./src/assets/gameOver.mp3']);
}

Não há muita diferença de adicionar um tipo image por exemplo, o grande detalhe é que eu passo no segundo parâmetro um array de strings, o ideal e passarmos vários formatos do mesmo áudio assim dependendo do dispositivo que o jogo estiver rodando, ele não terá problemas de reprodução.

Ao final do método create, adicionaremos esses áudios a scene do nosso jogo, ficando dessa forma:

this.soundVictory = this.sound.add('victory', { loop: false, volume: 0.3 });
this.soundGameOver = this.sound.add('gameOver', { loop: false, volume: 0.3 });

perceba que além da key, temos como segundo parâmetro um objeto de configurações, no meu caso coloquei loop: false, ou seja, o som só será tocado 1 vez, e não ficará repetindo quando for dado o play. E o outro e o volume que coloquei em 30% do normal.

Agora no método playerWin daremos play no áudio victory ficando assim:

playerWin () {
  this.soundVictory.play();
  this.player.setPosition(this.gameWidth / 2, this.gameHeight - 20);
}

E no método gameOver faremos o mesmo, ficando assim:

gameOver () {
  this.gamePause = true; // pausa o jogo
  this.soundGameOver.play(); 

  // adiciona um efeito de camera "shake" na tela principal do jogo
  this.cameras.main.shake(500);

  /* 
    espera a o efeito de camera shake acabar e executa uma callback, que irá
    executar outro efeito de camera: "fade"
  */
  this.cameras.main.on('camerashakecomplete', () => this.cameras.main.fade(500));

  /* 
    espera a o efeito de camera fade acabar e executa uma callback, que irá
    reiniciar a scene atual
  */
  this.cameras.main.on('camerafadeoutcomplete', () => this.scene.restart());
}

Pronto, agora só conferir eles na tela do jogo 🎶

Adicionando Pontuação

Um jogo sem objetivo não é nada concorda? O nosso jogo é simples, mas podemos adicionar ao menos uma pontuação. E de quebra você irá aprender a adicionar textos na tela.

Dentro do método init, abaixo do this.gamePause = false adicione:

// pontuação
this.score = 0;

Agora ao final do método create, adicione:

// adicionando texto (X, Y, Texto, Configs)
this.textScore = this.add.text(20, 20, `Score: ${this.score}`, {
  font: '20px Arial',
  fill: '#fff',
});

Os parâmetros são simples, os 2 primeiros são as posições que o texto irá ficar em relação a scene. O terceiro e o texto em si. E o último um objeto de configurações com várias opções.

Perceba que o texto já é renderizado na tela, mas mesmo o jogador vencendo ele não muda. Então o método playerWin ficará assim:

playerWin () {
  this.score += 1;
  this.soundVictory.play();
  this.player.setPosition(this.gameWidth / 2, this.gameHeight - 20);
}

Perceba que mesmo assim o texto não atualiza na tela, ou seja, não importa se estamos adicionando +1 ao score. Lembre-se que precisamos do método update para esse caso, então ele ficará assim:

update () {
  // verifica se é para pausar o jogo
  if (this.gamePause) return;

  // a cada loop o método e executado novamente
  this.carMovement();
  this.truckMovement();
  this.playerMovement();
  this.checkCollisions();

  // atualiza o texto
  this.textScore.setText(`Score: ${this.score}`);
}

Pronto, perceba que agora o texto está sendo atualizado.

Conclusão

Chegamos ao fim dessa série de tutoriais a respeito dos fundamentos do Phaser 3, espero que tenham gostado e qualquer dúvida ou ideia de tutorial sobre o Phaser estou a disposição pelo meu e-mail ou twitter, agradeço a todos que me acompanharam até aqui, e nos vemos em breve com outros tutoriais sobre o Phaser 3!

Desafios

Caso você tenha interesse em praticar mais sobre o phaser, tente melhorar o jogo, aqui vai uma lista de desafios que você pode fazer com os conhecimentos adquiridos nessa série:

  • Diminuir a pontuação por -1 caso o jogador perca
  • Adicionar mais inimigos ao jogo
  • Adicionar áudio quando o frog se mover

Bônus

Deixo aqui a demo do game que iremos fazer na próxima série de tutoriais: Game estilo Battle City.