2. Introdução ao PhaserJS
PhaserJS é um framework poderoso para a criação de jogos em HTML5. Ele fornece uma estrutura simples e eficiente para desenvolver jogos interativos diretamente no navegador.
Neste guia, abordaremos as funções básicas do PhaserJS, incluindo as etapas principais para carregar recursos, criar cenas e atualizar elementos no jogo.
1. Estrutura Básica de um Jogo no PhaserJS
Um jogo no Phaser segue um ciclo padrão que envolve três funções principais:
- preload(): Carrega os recursos (imagens, áudios, sprites, etc.).
- create(): Configura os objetos na cena.
- update(): Atualiza o jogo em tempo real.
A estrutura básica de um jogo no PhaserJS é a seguinte:
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
scene: {
preload: preload,
create: create,
update: update
}
};
const game = new Phaser.Game(config);
function preload() {
// Carregar imagens e outros recursos aqui
}
function create() {
// Criar elementos na tela
}
function update() {
// Atualizar elementos a cada frame
}
2. Adicionando Imagens na Tela
Para adicionar imagens ao jogo, primeiro carregamos os recursos na função preload() e depois os exibimos na função create().
Exemplo:
function preload() {
this.load.image('player', 'assets/player.png');
}
function create() {
this.add.image(400, 300, 'player');
}
No exemplo acima, carregamos uma imagem chamada player.png e depois a exibimos no centro da tela.
3. Mecânica de Movimentação
Para movimentar um objeto, podemos usar o teclado e atualizar sua posição na função update().
Exemplo de movimentação com setas do teclado:
let player;
let cursors;
function preload() {
this.load.image('player', 'assets/player.png');
}
function create() {
player = this.physics.add.sprite(400, 300, 'player');
cursors = this.input.keyboard.createCursorKeys();
}
function update() {
if (cursors.left.isDown) {
player.setVelocityX(-160);
} else if (cursors.right.isDown) {
player.setVelocityX(160);
} else {
player.setVelocityX(0);
}
if (cursors.up.isDown) {
player.setVelocityY(-160);
} else if (cursors.down.isDown) {
player.setVelocityY(160);
} else {
player.setVelocityY(0);
}
}
Neste exemplo:
- Criamos um sprite
playere ativamos física nele. - Definimos
cursorspara capturar entradas do teclado. - No
update(), ajustamos a velocidade do player conforme as teclas pressionadas.
Ponto importante!!!
Quando se trata de movimentação, é possível criar diferentes lógicas na programação de acordo com as suas necessidades. O código acima permite que você mova o "player" em diagonalmente, mas caso você não queira que isso aconteça, um exemplo abaixo seria uma das lógicas que poderiam ser implementadas:
let lastKey = null; // Armazena a última tecla pressionada
function update() {
let velocityX = 0;
let velocityY = 0;
if (cursors.left.isDown || cursors.right.isDown || cursors.up.isDown || cursors.down.isDown) {
// Define a última tecla pressionada
if (cursors.left.isDown) {
lastKey = 'left';
} else if (cursors.right.isDown) {
lastKey = 'right';
} else if (cursors.up.isDown) {
lastKey = 'up';
} else if (cursors.down.isDown) {
lastKey = 'down';
}
}
// Move apenas no eixo correspondente à última tecla pressionada
if (lastKey === 'left') {
velocityX = -160;
} else if (lastKey === 'right') {
velocityX = 160;
} else if (lastKey === 'up') {
velocityY = -160;
} else if (lastKey === 'down') {
velocityY = 160;
}
// Se nenhuma tecla estiver pressionada, reseta o lastKey
if (!cursors.left.isDown && !cursors.right.isDown && !cursors.up.isDown && !cursors.down.isDown) {
lastKey = null;
}
player.setVelocityX(velocityX);
player.setVelocityY(velocityY);
}
4. Definindo Hitbox no PhaserJS
A hitbox define a área de colisão do objeto, sendo essencial para detectar interações no jogo.
Para modificar a hitbox de um sprite, podemos usar setSize() e setOffset():
function create() {
player = this.physics.add.sprite(400, 300, 'player');
player.setSize(32, 48); // Define uma hitbox menor
player.setOffset(16, 24); // Ajusta a posição da hitbox dentro do sprite
}
Isso é útil quando a imagem do sprite tem espaços vazios ao redor do personagem ou quando queremos uma área de colisão mais precisa.
5. Modo Debug da Hitbox
Para visualizar a hitbox e depurar colisões, podemos ativar o modo debug do sistema de física.
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: {
debug: true // Habilita a exibição das hitboxes
}
},
scene: {
preload: preload,
create: create,
update: update
}
};
Com essa configuração, todas as hitboxes dos objetos com física serão desenhadas na tela, facilitando ajustes e correções.
Se quiser habilitar o debug apenas para um objeto específico, você pode adicionar:
this.physics.world.createDebugGraphic();
Isso irá mostrar apenas as hitboxes dos elementos ativos no sistema de física, tornando a depuração mais precisa.
6. Mecânica de colisão
Dento do Phaser existem alguns tipos de colisão, sendo eles: O Colider e o Overlap
6.1. O que é Colisão em um Jogo?
Colisão em um jogo é o evento que ocorre quando dois ou mais objetos dentro do ambiente virtual se tocam ou se sobrepõem. Esse conceito é fundamental em jogos digitais, pois define como os elementos interagem entre si, como personagens, inimigos, itens coletáveis e cenários.
6.2. Tipos de Colisão
Existem diferentes formas de tratar colisões em jogos, dependendo da mecânica desejada:
-
Colisão Física (Realista)
- Usa motores de física para calcular impactos, força e direção após a colisão.
- Exemplo: Em um jogo de sinuca, quando uma bola bate em outra e muda sua trajetória.
-
Colisão Simples (Arcade)
- Apenas impede que objetos atravessem uns aos outros, sem simular física realista.
- Exemplo: Um personagem que não pode atravessar paredes em um jogo de plataforma.
-
Sobreposição (Overlap)
- Detecta se dois objetos estão no mesmo espaço, mas sem impedir a passagem.
- Exemplo: Coletar uma moeda sem que o personagem precise colidir fisicamente com ela.
6.3. Como a Colisão é Detectada?
Os jogos usam diferentes técnicas para identificar colisões, como:
- Hitbox: Área invisível ao redor do objeto que define quando a colisão ocorre. Pode ser um retângulo, círculo ou formato mais complexo.
- Pixel Perfect: Verifica se os pixels de dois objetos realmente se sobrepõem. É mais preciso, mas consome mais recursos.
- Sistemas de Física: Motores como Box2D e Matter.js simulam colisões realistas usando cálculos de massa, velocidade e força.
6.4. Exemplos de Colisão em Jogos
- Jogos de plataforma: Um personagem colide com o chão e não atravessa ele.
- Jogos de corrida: Carros colidem uns com os outros e são empurrados.
- Jogos de tiro: Balas colidem com inimigos e causam dano.
7. Colider:
O Collider verifica as hitboxes dos dois objetos mencionados na função e impede que eles ocupem o mesmo espaço. No entanto, em alguns casos, seu uso pode não ser recomendado, pois a colisão pode fazer com que os objetos troquem inércia, o que pode gerar interações indesejadas.
Isso é especialmente problemático em objetos como itens coletáveis, que podem ser empurrados involuntariamente pelo jogador, ou em situações onde queremos que a colisão ocorra sem afetar a física do objeto, como ao atravessar uma porta ou ativar um interruptor.
this.physics.add.collider(X, Y, Z);
O método this.physics.add.collider(X, Y, Z) em Phaser é usado para adicionar uma colisão entre dois objetos, X e Y. Quando esses dois objetos se tocarem ou se sobrepuserem, a física do jogo vai gerenciar a colisão automaticamente.
Aqui está o que cada parte faz:
-
XeY: São os dois objetos (ou grupos de objetos) que você deseja que colidam. Eles podem ser sprites, corpos físicos (como objetos comarcade.physics), ou grupos de objetos. -
Colisão automática: Diferente do método
overlap, que verifica sobreposição sem interações físicas diretas,colliderlida com interações físicas reais. Por exemplo, se os objetos forem sprites com corpos de física, a colisão pode causar um empurrão ou impacto, com base nas configurações dos corpos físicos (massa, elasticidade, etc.). -
Z: É a função que vai ser chamada quando ocorrer uma colisão entreXeY. Essa função pode ser utilizada para executar ações específicas após a colisão, como emitir um som, trocar a animação dos objetos, aplicar dano, alterar o estado do jogo (por exemplo, diminuir a vida do jogador ou destruir um inimigo), ou até mesmo iniciar um efeito visual.
Observação: É possivel criar uma colisão sem definir uma função a ser chamada, sendo possivel realizar essa colisão dessa forma:
this.physics.add.collider(X, Y)
7.1. Bounce e o efeito da colisão
Quando dois objetos colidem, eles normalmente param, mas podemos configurar um comportamento mais dinâmico usando a propriedade bounce. O bounce define o quanto um objeto "quica" ao colidir com outro.
No Arcade Physics, podemos ajustar esse efeito com setBounce(value), onde value pode variar de 0 (sem quique) até 1 (quique total, como uma bola de pingue-pongue).
7.2. Exemplo de uso do bounce:
player.setBounce(0.2); // O jogador terá um leve quique ao tocar no chão
bola.setBounce(1); // A bola quicará indefinidamente ao bater nas paredes
Se quisermos que um grupo inteiro tenha esse efeito:
let bolas = this.physics.add.group({
key: 'ball',
repeat: 5,
setXY: { x: 100, y: 100, stepX: 150 }
});
// Aplicando bounce para todas as bolas do grupo
bolas.children.iterate(ball => {
ball.setBounce(1);
});
7.3. Bounce e o efeito da colisão
Quando dois objetos colidem, o comportamento deles depende do tipo de corpo físico que possuem:
- Se um dos objetos for estático (
setStatic(true)), o outro objeto será impedido de atravessá-lo e parará, a menos que tenha bounce configurado.
Dica: Se estiver criando um chão, plataformas ou paredes, lembre-se de definir esses objetos como estáticos (
setStatic(true)), para evitar que saiam se movendo ao colidir com outros elementos.
- Se ambos forem dinâmicos, a colisão pode fazer com que troquem inércia e se movimentem conforme suas propriedades físicas, como massa e velocidade.
7.4. Quando usar o Collider?
O Collider é útil em diversos cenários, como:
- Evitar que objetos atravesse o chão e plataformas:
this.physics.add.collider(object, platforms); - Criar colisões entre o jogador e obstáculos que devem bloqueá-lo:
this.physics.add.collider(player, wall); - Fazer com que caixas empilhadas interajam fisicamente entre si:
this.physics.add.collider(boxes, boxes);
7.5. Quando não usar o Collider?
- Em itens coletáveis, que devem ser sobrepostos e não bloqueados:
this.physics.add.overlap(player, coins, collectCoin, null, this); - Em portas ou botões, onde basta detectar a interação sem impedir o movimento:
this.physics.add.overlap(player, door, enterDoor, null, this);
8. Overlap:
Diferente do Collider, que impede a sobreposição de objetos, o overlap apenas detecta quando dois objetos se encontram, sem aplicar nenhuma física sobre eles. Isso é útil quando queremos que um objeto reaja ao contato sem bloqueá-lo.
A função Overlap, possue algumas entradas
this.physics.add.overlap(X,Y,Z, null, this);
-
XeY: São os dois objetos que você está verificando a sobreposição. Esses podem ser sprites, grupos de sprites ou qualquer outro objeto físico que você tenha adicionado à física do jogo. -
Z: Esse é o callback (função) que será chamado quando a colisão entreXeYocorrer. Ou seja, quando houver sobreposição entre os dois objetos, a funçãoZserá executada. -
null: Aqui, você tem um valor que pode ser um critério de filtragem adicional. Pode ser um valor que ajude a determinar se a colisão deve ser considerada ou não. Quando você passanull, significa que você não está usando nenhum filtro extra e que a sobreposição será verificada sem condições adicionais. -
this: Este é o contexto (this) da função de callback, ou seja, o escopo em que a função será executada. Geralmente,thisé o objeto da cena do Phaser (como a instância da cena que está sendo carregada), garantindo que você tenha acesso a propriedades e métodos dessa cena dentro da função de callback.
8.1. Exemplos de uso do Overlap
- Coletar itens sem que o jogador os empurre:
this.physics.add.overlap(player, coins, collectCoin, null, this);
function collectCoin(player, coin) {
coin.disableBody(true, true); // Faz a moeda desaparecer
} - Ativar um botão ou interruptor ao tocar nele:
this.physics.add.overlap(player, button, activateSwitch, null, this);
function activateSwitch(player, button) {
console.log("Interruptor ativado!");
} - Detectar quando o jogador entra em uma área específica, como uma porta ou checkpoint:
this.physics.add.overlap(player, door, enterDoor, null, this);
function enterDoor(player, door) {
console.log("Jogador entrou na porta!");
}
Curiosidade: O overlap é mais leve computacionalmente do que o Collider, pois não precisa calcular a separação dos objetos ou lidar com forças físicas.
9 Destruir Objetos
Em alguns casos, queremos que um objeto desapareça do jogo após uma determinada interação, como quando um inimigo é derrotado, um item coletável é recolhido ou um projétil atinge um alvo. No Phaser, podemos fazer isso com o método destroy().
objeto.destroy();
Esse método remove o objeto da cena e libera a memória associada a ele, garantindo que ele não continue processando eventos ou colisões.
9.1. Exemplos de uso de destroy()
Coletar itens e removê-los do jogo:
this.physics.add.overlap(player, coin, collectCoin, null, this);
function collectCoin(player, coin) {
coin.destroy(); // Remove a moeda da cena ao ser coletada
}
Remover um inimigo ao ser derrotado:
this.physics.add.collider(player, enemy, defeatEnemy, null, this);
function defeatEnemy(player, enemy) {
enemy.destroy(); // Remove o inimigo ao colidir com o jogador
}
Fazer um projétil desaparecer ao colidir com uma parede:
this.physics.add.collider(bullet, wall, destroyBullet, null, this);
function destroyBullet(bullet, wall) {
bullet.destroy(); // Remove a bala ao atingir a parede
}
Atenção: Quando um objeto é destruído, qualquer referência a ele se torna inválida. Se tentar acessá-lo depois de destruído, o jogo pode apresentar erros.
10. Explicação do this
No Phaser, a palavra-chave this é usada para se referir ao contexto atual da cena. Isso é importante porque cada cena do jogo é uma classe que contém seus próprios métodos e propriedades.
Quando usamos this dentro de uma função, estamos acessando elementos da cena, como os objetos do jogo, grupos de física, animações e sons.
10.1. Exemplo de uso do this
10.1.1. Criando e acessando objetos na cena:
this.player = this.physics.add.sprite(100, 200, 'player');
Aqui, this.player armazena o sprite do jogador na cena atual, permitindo que seja referenciado depois.
10.1.2. Usando this dentro de funções:
Ao passar this como último argumento em collider ou overlap, garantimos que a função de callback tenha acesso às propriedades da cena.
this.physics.add.overlap(this.player, this.coins, this.collectCoin, null, this);
Isso permite que a função collectCoin acesse outros elementos da cena sem perder o contexto.
10.1.3. Sem this, o código pode falhar!
Se tentarmos usar uma função sem passar this corretamente, podemos perder o acesso aos elementos da cena:
this.physics.add.collider(this.player, this.enemy, defeatEnemy);
function defeatEnemy(player, enemy) {
this.enemy.destroy(); // ERRO: `this` não está definido aqui!
}
Para corrigir isso, basta passar
thiscomo último argumento:
this.physics.add.collider(this.player, this.enemy, defeatEnemy, null, this);
Dessa forma, this.enemy funcionará corretamente dentro da função defeatEnemy.
10.2. Resumo sobre this
thisrefere-se ao contexto da cena atual.thisé necessário para acessar objetos e métodos da cena.- Sempre passe
thiscomo último argumento em funções decollidereoverlap. - Se esquecer de usar
this, pode acabar com erros ao tentar acessar elementos da cena!
Dica: Para evitar confusão, use funções de seta (
=>) dentro de eventos sempre que possível, pois elas mantêm automaticamente o contexto dethis:
this.physics.add.collider(this.player, this.enemy, (player, enemy) => {
enemy.destroy(); // Funciona sem precisar passar `this` manualmente
});