No Pac-Man original, os fantasmas são controlados pelo computador. No GG Pacman, eles podem ser outras pessoas. Cada jogador entra na sala como um Pac-Man ou como um dos quatro fantasmas, e a partida acontece ao vivo, com todo mundo vendo o mesmo labirinto.
Isso cria um problema que todo jogo online enfrenta: as informações chegam pela internet com atraso e em intervalos irregulares, mas o jogador espera ver os personagens deslizando suavemente pela tela. Este artigo explica como o GG Pacman resolve isso, e mostra as outras decisões que tornam o jogo possível.
Como se joga
- O labirinto tem 28 colunas por 31 linhas, as mesmas proporções do clássico.
- Você se move com as setas ou W A S D. No celular, um joystick virtual aparece onde você toca.
- Quem é Pac-Man come pastilhas e foge. Quem é fantasma, com o nome e a cor de Blinky, Pinky, Inky ou Clyde, persegue.
- Comer uma superpastilha deixa os fantasmas assustados por um tempo, e aí é o Pac-Man quem caça.
- As vidas aparecem como corações no placar. A partida termina quando todos os Pac-Mans perdem suas vidas, e um placar final mostra quem foi melhor.
Para entrar, você digita o código de uma sala, entra numa sala aleatória ou cria a sua. O código também pode vir no próprio link, o que facilita chamar um amigo.
Quem manda é o servidor
Num jogo com vários jogadores, alguém precisa ser a fonte da verdade. Se cada navegador calculasse a partida por conta própria, em segundos cada um teria uma versão diferente: num, o fantasma pegou o Pac-Man; no outro, ele escapou.
No GG Pacman, quem decide é um servidor, hospedado no Google Cloud Run. Ele guarda o labirinto, move todos os personagens, conta pontos e vidas e decide quando a fase acaba. Os navegadores fazem só duas coisas:
- Mandam a direção que o jogador escolheu. Não a posição, só "quero ir para cima".
- Mostram o que o servidor diz que está acontecendo.
// O que o navegador envia
socket.emit('direction', { dir: UP });
// O que o navegador recebe, várias vezes por segundo
socket.on('update', (state) => buffer.push(state));A comunicação usa Socket.IO, uma biblioteca que mantém uma conexão aberta entre o navegador e o servidor e reconecta sozinha se a internet cair. O GG Pacman tenta até 10 reconexões, esperando de 1 a 5 segundos entre elas, antes de avisar que não conseguiu.
Mandar só a direção tem duas vantagens. A primeira é que ninguém trapaceia dizendo ao servidor que está numa posição impossível. A segunda é que o servidor aplica as mesmas regras para todos, inclusive as colisões com as paredes.
O problema: atualizações chegam aos pulos
O servidor manda o estado da partida em intervalos, e a internet não entrega essas mensagens em ritmo perfeito. Uma pode chegar em 30 ms, a próxima em 80 ms, outra em 45 ms.
Se o jogo simplesmente desenhasse cada personagem na última posição recebida, o resultado seria um movimento aos trancos: o Pac-Man ficaria parado, depois saltaria para a frente, pararia de novo. Num jogo de labirinto, em que você precisa ver o fantasma se aproximando, isso estraga a experiência.
A solução: mostrar o passado, suavemente
A técnica que o GG Pacman usa se chama interpolação de entidades. A ideia parece estranha à primeira vista: o jogo mostra a partida um pouco atrasada de propósito.
O buffer de estados
Cada atualização que chega do servidor vai para uma fila, o buffer, que guarda as 20 mais recentes. Quando chega a 21ª, a mais antiga sai.
const STATE_BUFFER_MAX = 20;
const INTERP_DELAY_MS = 100;
function onUpdate(state) {
buffer.push(state);
if (buffer.length > STATE_BUFFER_MAX) buffer.shift();
}Desenhar 100 ms no passado
Na hora de desenhar a tela, o jogo não usa "agora". Ele calcula qual era o momento de 100 milissegundos atrás e procura, no buffer, as duas atualizações que ficam uma antes e uma depois desse momento. Então desenha cada personagem no meio do caminho entre as duas posições, na proporção certa.
function render(now) {
const renderTime = now - INTERP_DELAY_MS;
const [before, after] = findStatesAround(buffer, renderTime);
const t = (renderTime - before.t) / (after.t - before.t); // de 0 a 1
for (const p of players) {
p.x = lerp(before.pos[p.id].x, after.pos[p.id].x, t);
p.z = lerp(before.pos[p.id].z, after.pos[p.id].z, t);
}
}Como existe sempre uma atualização "depois" já recebida, o movimento fica contínuo, mesmo que as mensagens tenham chegado com intervalos irregulares. Os 100 ms são a folga: enquanto as mensagens não atrasarem mais que isso, o jogador nunca percebe o tranco.
Há também um ajuste de relógio. Na primeira atualização, o navegador anota a diferença entre o seu relógio e o horário que o servidor marcou na mensagem. A partir daí, todas as contas usam o tempo do servidor, e um relógio adiantado no computador do jogador não atrapalha.
O preço
O custo é que todo mundo vê a partida 100 ms atrasada. Num jogo de labirinto, em que as decisões são "virar ou não virar nesta esquina", esse atraso é pequeno perto do ganho de ver os personagens se movendo de verdade. Jogos de tiro rápido costumam precisar de técnicas extras, como prever o próprio movimento antes de o servidor confirmar, mas para o GG Pacman a interpolação sozinha resolve.
O labirinto em 3D
Apesar do visual de arcade, o GG Pacman é desenhado em 3D com Three.js. O labirinto chega do servidor como uma grade de números, e o jogo transforma cada número em peça:
| Valor | Peça |
|---|---|
| 0 | caminho vazio |
| 1 | parede |
| 3 | pastilha |
| 4 | superpastilha |
| 6 | porta da casa dos fantasmas |
Cada célula ocupa 24 unidades no mundo 3D. Como o labirinto vem do servidor, ele pode mudar de uma fase para outra sem que o jogo no navegador precise ser atualizado.
Duas câmeras
A tecla V (ou o botão de câmera) alterna entre duas visões:
- De cima, com uma câmera ortográfica, que não tem perspectiva. É a visão clássica: o labirinto inteiro, plano, sem distorção. Ótima para planejar a rota.
- De perseguição, com uma câmera em perspectiva posicionada atrás e acima do seu personagem. Você vê o labirinto do ponto de vista de quem está dentro dele, o que deixa a fuga bem mais tensa.
A câmera ortográfica é a que mantém a leitura clara: numa câmera em perspectiva vista de cima, as paredes perto das bordas ficariam inclinadas, e seria mais difícil julgar distâncias.
Detalhes que mudam a partida
Algumas escolhas pequenas fazem diferença no jogo:
- Quem é Pac-Man vê todos os fantasmas em branco. Só quem joga como fantasma vê as cores de Blinky, Pinky, Inky e Clyde. Assim o Pac-Man não sabe qual fantasma é qual jogador, e não dá para fugir só de quem joga melhor.
- Comer fantasmas em sequência vale cada vez mais. Dentro de uma mesma superpastilha, o número que flutua na tela dobra: 200, 400, 800 e 1.600.
- Sons do clássico: o "waka" ao comer pastilhas, a sirene, o som da superpastilha, o de comer um fantasma e o de perder uma vida. Dá para silenciar, e o jogo lembra da escolha.
- No fim, o placar mostra estatísticas da partida, como quantas vezes cada jogador foi comido e quantos Pac-Mans cada fantasma pegou.
E o editor de fases?
O labirinto é só uma grade de números, então criar uma fase nova é, na prática, editar essa grade. Existe uma ferramenta interna para isso, usada no desenvolvimento. Mandem sugestoes de fases.
Resumo
- Labirinto 28×31 desenhado em 3D com Three.js, com câmera de cima e de perseguição
- Servidor no Cloud Run decide tudo; o navegador só envia a direção
- Buffer com as 20 últimas atualizações e desenho 100 ms no passado, com interpolação
- Fantasmas controlados por jogadores, com cores visíveis só para eles
