Este guia constrói um jogo pequeno e verificável, em vez de apresentar apenas uma lista de recursos: você controla uma peça azul com as setas do teclado, coleta dez moedas e acompanha a pontuação. O exemplo usa formas desenhadas pelo próprio Phaser, então não depende de imagens ou arquivos de terceiros.
Usaremos Phaser 3.90.0, TypeScript 5.9.3 e Vite 8.3.5. O artigo inclui o código da cena, configuração do projeto, comandos de validação de tipos e compilação. As versões foram escolhidas para tornar o exemplo reproduzível; verifique os requisitos de Node.js do Vite antes de começar.
1. O que é Phaser?
Phaser é um framework gratuito e de código aberto voltado para o desenvolvimento de jogos 2D executados em navegadores. Ele oferece recursos para renderização gráfica, animações, entrada do teclado e do mouse, física, áudio, carregamento de imagens e gerenciamento de cenas.
Entre suas principais características estão:
- Renderização utilizando WebGL ou Canvas.
- Sistema de física para movimentação e colisões.
- Suporte a teclado, mouse e controles por toque.
- Gerenciamento de cenas, como menus, fases e telas de encerramento.
- Carregamento de imagens, sprites, áudio e outros recursos.
- Integração com JavaScript, TypeScript, Vite e outros ambientes web.
O Phaser é especialmente interessante para jogos educacionais, jogos casuais, quebra-cabeças, plataformas, aventuras 2D e experiências interativas.
2. Por que utilizar TypeScript?
TypeScript é uma linguagem baseada em JavaScript que adiciona recursos de tipagem estática. Isso permite identificar diversos erros durante o desenvolvimento, melhorar o preenchimento automático do editor e organizar melhor projetos que possuem muitas classes e funcionalidades.
Veja um exemplo simples:
let pontuacao: number = 0;
function adicionarPontos(valor: number): void {
pontuacao += valor;
}
adicionarPontos(10);
console.log(pontuacao);
Nesse exemplo, a variável pontuacao armazena um número, enquanto a função adicionarPontos() recebe um argumento numérico. Se tentarmos passar um texto para essa função, o TypeScript poderá identificar o problema durante a verificação do código.
Em jogos maiores, essa organização ajuda a manter o projeto mais confiável e facilita a manutenção.
3. Preparando o ambiente de desenvolvimento
Para acompanhar este tutorial, você precisará de:
- Node.js em uma versão compatível com o Vite utilizado.
- npm, que normalmente acompanha o Node.js.
- Visual Studio Code ou outro editor de código.
- Um navegador moderno, como Google Chrome ou Microsoft Edge.
O Vite será responsável por iniciar o servidor de desenvolvimento e preparar os arquivos para publicação.
Criando o projeto
No terminal, crie um projeto inicial com o template TypeScript do Vite:
npm create vite@latest meu-jogo -- --template vanilla-ts
Entre na pasta criada:
cd meu-jogo
Instale as dependências:
npm install
Instale o Phaser e fixe as versões usadas neste exemplo. O Vite 8 requer uma versão de Node.js compatível com os requisitos publicados pelo próprio projeto.
npm install --save-exact phaser@3.90.0
npm install --save-dev --save-exact vite@8.3.5 typescript@5.9.3
Antes de continuar, confirme que o package.json tem comandos para desenvolvimento, verificação de tipos e compilação. Se necessário, deixe os scripts assim:
"scripts": {
"dev": "vite",
"typecheck": "tsc --noEmit",
"build": "tsc --noEmit && vite build",
"preview": "vite preview"
}
Agora o projeto possui a estrutura básica para trabalhar com TypeScript.
Organizando os arquivos
Para este exemplo, utilizaremos a seguinte organização:
meu-jogo/
├── public/
├── src/
│ ├── main.ts
│ └── style.css
├── index.html
├── package.json
└── tsconfig.json
O arquivo main.ts conterá a lógica principal do jogo. O arquivo style.css será responsável pela apresentação da página, enquanto o index.html será o ponto de entrada da aplicação.
4. Entendendo a estrutura de um jogo Phaser
Antes de implementar o jogo, é importante conhecer três métodos fundamentais de uma cena Phaser:
preload() — utilizado para carregar imagens, áudios, sprites e outros recursos necessários.
create() — executado quando a cena é criada. É nele que normalmente configuramos personagens, textos, objetos e eventos.
update() — executado continuamente durante o jogo. É utilizado para atualizar movimentos, verificar condições e processar comportamentos que dependem do tempo.
Uma cena pode ser representada por uma classe que herda de Phaser.Scene.
class MinhaCena extends Phaser.Scene {
constructor() {
super("MinhaCena");
}
preload(): void {
// Carregamento de recursos.
}
create(): void {
// Criação dos elementos do jogo.
}
update(): void {
// Atualização contínua.
}
}
Essa estrutura separa as responsabilidades e facilita a evolução do projeto.
5. Criando nosso primeiro jogo: Caça às Moedas
Vamos desenvolver um jogo simples com as seguintes regras:
- O personagem aparece na tela.
- O jogador utiliza as setas do teclado para movimentá-lo.
- Dez moedas ficam distribuídas pelo cenário.
- Cada moeda coletada aumenta a pontuação.
- Ao coletar todas as moedas, o jogo exibe uma mensagem de vitória.
Para facilitar o aprendizado, criaremos os elementos gráficos diretamente pelo código. Assim, não precisaremos baixar imagens ou configurar arquivos de sprites.
5.1 Código completo do jogo Caça às Moedas
Vamos ao código prático. Abra o arquivo src/main.ts, substitua seu conteúdo pelo código abaixo e salve.
import Phaser from "phaser";
type ArcadeOverlapObject =
| Phaser.Types.Physics.Arcade.GameObjectWithBody
| Phaser.Physics.Arcade.Body
| Phaser.Physics.Arcade.StaticBody
| Phaser.Tilemaps.Tile;
class JogoCena extends Phaser.Scene {
private jogador!: Phaser.Physics.Arcade.Sprite;
private moedas!: Phaser.Physics.Arcade.StaticGroup;
private teclas!: Phaser.Types.Input.Keyboard.CursorKeys;
private textoPontuacao!: Phaser.GameObjects.Text;
private pontuacao: number = 0;
private readonly totalMoedas: number = 10;
private jogoFinalizado: boolean = false;
constructor() {
super("JogoCena");
}
create(): void {
const largura = this.scale.width;
const altura = this.scale.height;
// Cor de fundo e título.
this.cameras.main.setBackgroundColor("#14213d");
this.add
.text(largura / 2, 24, "CAÇA ÀS MOEDAS", {
fontFamily: "Arial",
fontSize: "28px",
color: "#ffffff",
fontStyle: "bold"
})
.setOrigin(0.5);
this.add
.text(
largura / 2,
58,
"Use as setas para coletar todas as moedas!",
{
fontFamily: "Arial",
fontSize: "15px",
color: "#dbeafe"
}
)
.setOrigin(0.5);
// Criando a textura do jogador.
const graficoJogador = this.make.graphics({ x: 0, y: 0 });
graficoJogador.fillStyle(0x38bdf8, 1);
graficoJogador.fillCircle(16, 16, 15);
graficoJogador.generateTexture(
"texturaJogador",
32,
32
);
graficoJogador.destroy();
// Criando a textura das moedas.
const graficoMoeda = this.make.graphics({ x: 0, y: 0 });
graficoMoeda.fillStyle(0xfacc15, 1);
graficoMoeda.fillCircle(12, 12, 11);
graficoMoeda.lineStyle(2, 0xfef3c7, 1);
graficoMoeda.strokeCircle(12, 12, 8);
graficoMoeda.generateTexture(
"texturaMoeda",
24,
24
);
graficoMoeda.destroy();
// Criando o jogador com física Arcade.
this.jogador = this.physics.add.sprite(
largura / 2,
altura / 2,
"texturaJogador"
);
this.jogador.setCollideWorldBounds(true);
this.jogador.setBounce(0);
// Configurando os controles do teclado.
this.teclas = this.input.keyboard!.createCursorKeys();
// Criando o grupo de moedas.
this.moedas = this.physics.add.staticGroup();
for (let i = 0; i < this.totalMoedas; i++) {
let x = 40 + Math.random() * (largura - 80);
let y = 110 + Math.random() * (altura - 150);
// Evita colocar uma moeda muito perto do jogador.
const distancia = Phaser.Math.Distance.Between(
x,
y,
this.jogador.x,
this.jogador.y
);
if (distancia < 65) {
x = x < largura / 2
? largura - 50
: 50;
y = y < altura / 2
? altura - 55
: 110;
}
this.moedas.create(x, y, "texturaMoeda");
}
// Exibindo a pontuação.
this.textoPontuacao = this.add.text(
16,
altura - 34,
`Moedas: ${this.pontuacao}/${this.totalMoedas}`,
{
fontFamily: "Arial",
fontSize: "18px",
color: "#ffffff"
}
);
// Detectando a coleta das moedas.
this.physics.add.overlap(
this.jogador,
this.moedas,
this.coletarMoeda,
undefined,
this
);
}
update(): void {
if (this.jogoFinalizado) {
return;
}
const velocidade = 220;
const corpo = this.jogador.body as
Phaser.Physics.Arcade.Body;
corpo.setVelocity(0, 0);
if (this.teclas.left.isDown) {
corpo.setVelocityX(-velocidade);
} else if (this.teclas.right.isDown) {
corpo.setVelocityX(velocidade);
}
if (this.teclas.up.isDown) {
corpo.setVelocityY(-velocidade);
} else if (this.teclas.down.isDown) {
corpo.setVelocityY(velocidade);
}
// Evita que o movimento diagonal seja mais rápido.
corpo.velocity.normalize().scale(velocidade);
}
private coletarMoeda(
_objetoJogador: ArcadeOverlapObject,
objetoMoeda: ArcadeOverlapObject
): void {
if (!(objetoMoeda instanceof Phaser.Physics.Arcade.Image)) {
return;
}
objetoMoeda.disableBody(true, true);
this.pontuacao++;
this.textoPontuacao.setText(
`Moedas: ${this.pontuacao}/${this.totalMoedas}`
);
if (this.pontuacao >= this.totalMoedas) {
this.finalizarJogo();
}
}
private finalizarJogo(): void {
this.jogoFinalizado = true;
this.jogador.setVelocity(0, 0);
const largura = this.scale.width;
const altura = this.scale.height;
this.add
.rectangle(
largura / 2,
altura / 2,
largura - 40,
100,
0x000000,
0.8
);
this.add
.text(
largura / 2,
altura / 2,
"PARABÉNS! VOCÊ VENCEU!",
{
fontFamily: "Arial",
fontSize: "24px",
color: "#facc15",
fontStyle: "bold",
align: "center"
}
)
.setOrigin(0.5);
}
}
const configuracao: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: "app",
backgroundColor: "#14213d",
physics: {
default: "arcade",
arcade: {
debug: false
}
},
scene: [JogoCena],
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
}
};
new Phaser.Game(configuracao);
5.2 Preparando a página HTML
Abra o arquivo index.html na raiz do projeto e verifique se ele contém a estrutura abaixo:
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Jogo de coleta de moedas criado com Phaser e TypeScript." />
<title>Caça às Moedas | Phaser e TypeScript</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
O elemento app é o contêiner no qual o Phaser inserirá a área de renderização do jogo.
5.3 Ajustando o CSS
Abra src/style.css e substitua seu conteúdo por:
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
min-width: 320px;
min-height: 100%;
background: #0b1120;
color: #ffffff;
font-family: Arial, sans-serif;
}
body {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
}
#app {
width: 100%;
max-width: 800px;
}
canvas {
display: block;
max-width: 100%;
height: auto;
border-radius: 8px;
}
Depois, confira se o arquivo src/main.ts importa o CSS. Adicione esta linha no início do arquivo, caso ela ainda não exista:
import "./style.css";
5.4 Executando o jogo
Com os arquivos salvos, abra o terminal na pasta do projeto e execute:
npm run dev
O Vite exibirá um endereço local, normalmente semelhante a:
http://localhost:5173/
Abra o endereço no navegador. O jogo deverá aparecer com um personagem azul, dez moedas amarelas e o contador de pontuação.
Utilize as setas do teclado para movimentar o personagem. Ao tocar nas moedas, elas desaparecerão e a pontuação aumentará. Depois de coletar as dez moedas, será exibida a mensagem de vitória.
Para interromper o servidor, utilize Ctrl + C no terminal.
5.5 Gerando a versão para publicação
Quando o jogo estiver funcionando corretamente, execute:
npm run build
O script indicado executa primeiro tsc --noEmit, que verifica os tipos sem gerar arquivos JavaScript, e depois vite build, que prepara a versão de produção na pasta dist.
Antes de publicar, confira também:
- Se todos os recursos utilizados estão disponíveis.
- Se os caminhos dos arquivos funcionam no endereço de destino.
- Se o jogo funciona em navegadores móveis.
- Se o console do navegador não apresenta erros.
- Se a aplicação funciona em um caminho de publicação diferente da raiz do domínio, quando aplicável.
Em hospedagens tradicionais, os arquivos gerados podem ser enviados ao servidor web. Entretanto, é importante respeitar a configuração de caminhos do Vite, principalmente quando o jogo ficará dentro de uma subpasta de um site.
6. Problemas comuns durante o desenvolvimento
O jogo não aparece na tela
Confira o console do navegador, verifique se o arquivo src/main.ts foi carregado e confirme se o elemento app existe no HTML. Erros de caminho no script também podem impedir que o Vite carregue a cena.
O TypeScript apresenta erros
Execute npm run typecheck para localizar a linha indicada pelo compilador. Confira os tipos dos callbacks do Arcade Physics e a versão de Phaser instalada; não remova a verificação de tipos para ocultar um erro.
O personagem não coleta as moedas
Confirme que o jogo usa a física Arcade, que as moedas pertencem ao grupo estático e que a chamada a this.physics.add.overlap() está dentro da cena ativa.
O jogo fica lento em dispositivos móveis
Este exemplo usa controles de teclado e foi pensado para ser acompanhado em um computador. Para suportar toque, implemente controles na tela e teste em aparelhos reais; não presuma que a escala visual do canvas, por si só, torna o jogo jogável por toque.
7. Como evoluir o exemplo
O exemplo apresentado é simples, mas pode ser expandido de diversas maneiras.
Adicionar imagens e animações
O Phaser permite carregar spritesheets e criar animações para o personagem. Em vez de utilizar um círculo, você pode criar um personagem com movimentos de caminhada, corrida e comemoração.
Criar obstáculos
É possível adicionar paredes, inimigos e outros objetos usando os recursos de física. Assim, o jogador precisará desviar dos obstáculos para alcançar as moedas.
Implementar níveis
Você pode criar várias cenas, cada uma com um mapa e uma dificuldade diferente. Ao terminar uma fase, o jogo pode carregar a próxima.
Adicionar efeitos sonoros
O Phaser oferece suporte a áudio. Sons de coleta, vitória e colisão ajudam a tornar a experiência mais envolvente.
Criar controles para celulares
Além das setas do teclado, é possível implementar botões virtuais, joystick na tela ou controles por toque, tornando o jogo mais adequado para smartphones e tablets.
Salvar a pontuação
O localStorage do navegador pode ser utilizado para manter um recorde local entre sessões. Para placares compartilhados entre jogadores, pode ser necessário um servidor com banco de dados e mecanismos de validação.
8. Organização para projetos maiores
À medida que o jogo crescer, evite concentrar toda a lógica em uma única classe. Uma organização possível é:
src/ ├── scenes/ │ ├── MenuScene.ts │ ├── GameScene.ts │ └── GameOverScene.ts ├── objects/ │ ├── Player.ts │ └── Coin.ts ├── systems/ │ └── ScoreSystem.ts ├── main.ts └── style.css
Essa estrutura é apenas uma sugestão: projetos pequenos podem utilizar menos arquivos, enquanto jogos maiores podem exigir sistemas adicionais.
Separar cenas, personagens e regras de negócio facilita a leitura, a manutenção e a implementação de novos recursos. Também é recomendável manter nomes consistentes, utilizar os tipos do TypeScript e testar o jogo depois de cada mudança importante.
Conclusão
Desenvolver jogos com Phaser e TypeScript é uma forma prática de combinar programação web com conceitos tradicionais de desenvolvimento de jogos.
Neste tutorial, criamos um jogo completo de coleta de moedas, configuramos o ambiente com Vite, implementamos controles por teclado, utilizamos a física Arcade e preparamos o projeto para publicação.
O próximo passo é experimentar novas mecânicas e transformar o protótipo em um jogo mais elaborado, com personagens animados, níveis, obstáculos, efeitos sonoros e interface adaptada a diferentes dispositivos.
No Estúdio Multimídia, acreditamos que aprender programação fica mais interessante quando os conceitos são aplicados em projetos reais. Por isso, continuaremos explorando técnicas, ferramentas e soluções para o desenvolvimento de jogos HTML5 e aplicações interativas.
Gostou deste tutorial? Continue acompanhando o blog do Estúdio Multimídia para conhecer novos conteúdos sobre Phaser, TypeScript, JavaScript e desenvolvimento de jogos.
Referências técnicas
- Documentação oficial do Phaser
- Tutorial oficial: criando seu primeiro jogo com Phaser
- Guia oficial do Vite
- Template oficial Phaser + TypeScript + Vite
- Documentação oficial do TypeScript
Ambiente usado na validação local deste exemplo: Node.js 24.11.0, Phaser 3.90.0, Vite 8.3.5 e TypeScript 5.9.3. As versões e os comandos podem mudar; consulte as documentações oficiais antes de iniciar um projeto novo.
Leia também
- Como a inteligência artificial está mudando a criação de jogos
- Criação de jogos com rapidez e sem programar: conheça o Construct 3
Quer um jogo profissional para o seu projeto?
O Estúdio Multimídia desenvolve games em HTML5 sob medida para web, aplicativos e campanhas, do conceito à publicação.
Fale com a nossa equipe