Fundamentos: Game estilo Frogger com Phaser 3 - Parte 3

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

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 2 nesse repositório.

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

Movimentando os Inimigos

No momento já temos o nosso jogador e os inimigos no jogo, mas de forma estática. Chegou a hora de dar movimento aos nossos inimigos, inicialmente precisamos de variáveis para deixar a movimentação mais randômica, dificultando um pouco o jogo.

Dentro do método init adicionaremos 2 variáveis responsáveis por isso, ficando dessa forma:

init () {
  // largura da tela
  this.gameWidth = this.sys.game.config.width;
  // altura da tela
  this.gameHeight = this.sys.game.config.height;
  
  // min e max velocidade de movimento do inimigo
  this.enemyMinSpeed = 1;
  this.enemyMaxSpeed = 4;
}

Com as variáveis inicializadas, precisamos criar um método responsável por setar a velocidade dos nossos inimigos de forma aleatória. Abaixo do método update criaremos o método setSpeed, ficando dessa forma:

/*
  Responsavel por criar de forma aleatoria a velocidade do inimigo  
*/
setSpeed () {
  const dir = this.setDirection(); // seta a direção do inimigo
  // seta a velocidade de acordo com o min e max
  const speed = this.enemyMinSpeed + Math.random() * (this.enemyMaxSpeed - this.enemyMinSpeed);
  return dir * speed; // retorna a velocida (negativa ou positiva)
}

Perceba que nosso método chama o método setDirection, ele será responsável por dizer em qual direção o inimigo irá iniciar o jogo, já podemos criar ele abaixo do método setSpeed, ficando da seguinte forma:

/* Responsavel por gerar uma direção aleatoria  */
setDirection () {
  // chance de 50%
  return Math.random() < 0.5 ? 1 : -1;
}

É extremamente simples a implementação, mas que pode gerar dúvidas em iniciantes, primeiramente eu verifico se o Math.random() e menor que 0.5, o Math.random() me retorna um número de forma aleatória entre 0 ou 1, por exemplo 0.21060819921803065.

Ou seja, verificando se e menor que 0.5 eu crio uma probabilidade de 50% de chance, você pode alterar para o valor que quiser, como por exemplo 0.8 (80%), mas eu considero 50% de chance ideal já que ele pode ir ou para esquerda ou para direita.

O retorno pode ser 1 ou -1, ou seja caso seja passado 1 ele irá converter o valor para positivo, e caso seja passado -1 ele irá converter o valor para negativo, exemplo:

A velocidade do inimigo e 4, e a direção dele e -1 então: 4 * -1 = -4 o inimigo irá iniciar movendo-se para esquerda (eixo X negativo).

Movimentando o Inimigo Carro

Com os métodos auxiliares implementados, abaixo da criação do inimigo carro no método create, basta adicionarmos a propriedade speed ao nosso carro ficando dessa forma:

this.car = this.add.image(
  48,
  this.gameHeight - 140,
  'car',
);
// mudando a escala da imagem para 0.8
this.car.setScale(0.8);
// adicionando propriedade speed ao car
this.car.speed = this.setSpeed();

Lembre-se que o método setSpeed, também gera uma direção inicial ao inimigo.

NOTA: A propriedade speed não existia “nativamente” no objeto this.car, você pode adicionar qualquer propriedade que deseja para criar algum tipo de lógica no jogo, como por exemplo hp para criar uma barra de vida para o jogador, existem formas mais eficientes utilizando um padrão chamado prefabs, isso deixarei para ensinar em tópicos mais avançados.

Até o momento, nosso carro continua parado, vamos fazer ele funcionar! Abaixo do método setDirection, adicionaremos o método responsável por movimentar o carro, ficando assim:

/* Movimentação do carro */
carMovement () {
  // adiciona movimento ao carro (pode ser negativo ou positivo)
  this.car.x += this.car.speed;

  // caso o carro esteja saindo da tela pela esquerda
  const conditionLeft = this.car.speed < 0 && this.car.x <= -this.car.width;
  // casso o carro esteja saindo da tela pela direita
  const conditionRight = this.car.speed > 0 && this.car.x >= this.gameWidth + this.car.width;

  if (conditionLeft) {
    // muda a direção do carro  
    this.car.speed *= -1;
    // deixa a imagem do carro no eixo X na sua forma padrão
    this.car.flipX = false;

    // muda o carro de pista
    if (!this.car.moveToDown) {
      this.car.setPosition(this.car.x, this.car.y + this.car.height);
      this.car.moveToDown = true;
    }

  } else if (conditionRight) {
    // muda a direção do carro
    this.car.speed *= -1;
    // inverte a imagem do carro no eixo X
    this.car.flipX = true;
    
    // muda o carro de pista
    if (this.car.moveToDown) {
      this.car.setPosition(this.car.x, this.car.y - this.car.height);
      this.car.moveToDown = false;
    }
  }
}

É um método um pouco mais complexo, mas se parar um pouco para entender não levará muito tempo. Basicamente o carro anda de um lado para o outro trocando de faixa na pista.

Alguns pontos que quero detalhar mais (além dos comentários no código) são:

  • this.car.x = algum valor : podemos usar a propriedade X do objeto carro para movimentá-lo na tela, como já expliquei na parte 2, adicionando um valor positivo, o carro irá mais para direita, e negativo mais para esquerda, como o método carMovement irá ser executado a todo momento dentro do método update, mudando de forma constante esse valor, irá dar a sensação que o carro está se movendo. Existe formas mais fluidas de fazer isso, mas deixarei para tutoriais de um nível mais avançado. Obs: a propriedade Y , também existe e você pode usar para movimentar para cima e para baixo.
  • this.car.flipX = true ou false : a propriedade flipX existe no this.car pois ele e um objeto do tipo image, basicamente o flipX inverte a imagem no eixo X, por padrão o carro está virado para direita, você pode ver em src/assets/car.png, quando eu passo o valor true eu inverto a imagem então o carro vai apontar para direita, isso da a sensação de “troca de sprite”, mas na verdade só estamos girando a imagem no eixo X. Obs: existe o mesmo para o eixo Y (flipY).
  • this.car.setPosition(X, Y) : O método setPosition também já existe no this.car, ele (como o nome já diz) seta uma posição para o objeto de acordo com as coordenadas X e Y, uso esse método para fazer com que o carro mude de faixa na pista.

Caso você tenha dúvidas, pode me contactar por email ou criando uma issue no repositório do projeto.

Se você olhar para tela do jogo, o carro ainda não está se movendo, isso e devido a não colocarmos o método carMovement para ser executado, para isso vamos no método update, ficando da seguinte forma:

update () {
  // a cada loop o método e executado novamente
  this.carMovement()
}

Voltando para a tela do jogo, finalmente conseguimos ver o carro andar!

Movimentando o Inimigo Caminhão

Agora que já vimos os conceitos principais da movimentação, será mais fácil implementar o movimento do caminhão, no método create abaixo da criação do inimigo caminhão, adicionaremos a ele a propriedade speed ficando dessa forma:

this.truck = this.add.image(
  48,
  this.gameHeight - 306,
  'truck',
);
// mudando a escala da imagem para 0.8
this.car.setScale(0.8);
// adicionando propriedade speed ao truck
this.truck.speed = this.setSpeed();

Abaixo do método carMovement iremos criar o método truckMovement, como o nome já diz, será responsável por movimentar o caminhão. Ficando da seguinte forma:

truckMovement () {
  // adiciona movimento ao caminhão (pode ser negativo ou positivo)
  this.truck.x += this.truck.speed;

  // gira o caminhão no eixo X (pode ser esquerda ou direita)
  if (this.truck.speed < 0) {
    this.truck.flipX = true;
  } else {
    this.truck.flipX = false;
  }

  const conditionLeft = this.truck.speed < 0 && this.truck.x <= -this.truck.width;
  const conditionRight = this.truck.speed > 0 && this.truck.x >= this.gameWidth + this.truck.width;

  // move o caminhão de faixa com uma chance de 3%
  if (Math.random() < 0.003) {
    if (!this.truck.moveToDown) {
      this.truck.setPosition(this.truck.x, this.truck.y + this.truck.height);
      this.truck.moveToDown = true;
    } else {
      this.truck.setPosition(this.truck.x, this.truck.y - this.truck.height);
      this.truck.moveToDown = false;
    }
  }

  // muda o caminhão de direção
  if (conditionLeft) {
    this.truck.speed *= -1;
  } else if (conditionRight) {
    this.truck.speed *= -1;
  }
}

Para deixar um pouco mais dinâmico o jogo, o caminhão além de ser maior (mais fácil de colidir com o frog), ele também irá mudar de faixa de forma aleatória (o carro mudava toda vez que saía da tela). Por fim colocamos o método para executar dentro do update, ficando assim:

update () {
  // a cada loop o método e executado novamente
  this.carMovement()
  this.truckMovement()
}

Conclusão

Nessa terceira parte vimos como movimentar os inimigos, nós vemos na parte 4!