Pular para o conteúdo principal

Game Loop: A Anatomia Matemática de um Jogo

Publicado em 7 de janeiro de 202630 min de leitura
Imagem de tecnologia relacionada ao artigo game-loop-arquitetura-delta-time-interpolacao

Game Loop: A Anatomia Matemática de um Jogo

Imagem de tecnologia relacionada ao artigo game-loop-arquitetura-delta-time-interpolacao
O código que dá vida aos mundos virtuais: o infinito Game Loop.

Se você olhar o código fonte de um site (React/Vue), verá que ele é orientado a eventos. O código fica parado esperando o usuário clicar. Se ninguém clicar, a CPU dorme.

Um jogo é o oposto. Ele é um loop infinito que roda o mais rápido possível, redesenhando a tela 60, 144 ou 240 vezes por segundo, mesmo que o jogador tenha ido ao banheiro.

while (true) { input(); update(); render(); }

Parece simples, certo? Mas se você implementar um loop "ingênuo", seu jogo vai rodar na velocidade da luz num PC gamer e em câmera lenta num laptop antigo. A física vai quebrar, personagens vão atravessar paredes e o multiplayer será um caos. Bem-vindo à engenharia do Game Loop, onde cada milissegundo é uma batalha matemática pela fluidez.

1. O Problema da Velocidade (FPS vs Física)

Imagine que seu personagem anda 5 pixels por frame.

  • No PC A (30 FPS): Ele anda 150 pixels por segundo.
  • No PC B (120 FPS): Ele anda 600 pixels por segundo.

O jogador com PC melhor ganha. Isso é inaceitável. Para corrigir, introduzimos o Delta Time (dt): o tempo que passou desde o último frame.

posicao += velocidade * dt

Se dt for pequeno (PC rápido), o incremento é pequeno. Se dt for grande (PC lento), o incremento é grande. O resultado final é o mesmo deslocamento por segundo.

2. Fixed Timestep: O Segredo da Física Determinística

Game Loop: A Anatomia Matemática de um Jogo

Usar dt variável resolve o movimento, mas quebra a física. Motores de física (Box2D, PhysX) odeiam dt variável. Se o dt variar muito, erros de arredondamento se acumulam. Num frame, a colisão é detectada. No outro (com dt maior), a bala atravessa a parede antes da detecção (Tunneling).

A solução é separar o tempo do jogo em dois:

  1. Renderização (Variable Timestep): Desenha o mais rápido possível.
  2. Física (Fixed Timestep): Roda em intervalos fixos (ex: a cada 16ms ou 50Hz).
javascript

// Exemplo de Loop com Fixed Timestep (Padrão de Ouro)
double t = 0.0;
double dt = 0.01; // 10ms (Física roda a 100Hz)

double currentTime = hires_time_in_seconds();
double accumulator = 0.0;

while (!quit) {
  double newTime = hires_time_in_seconds();
  double frameTime = newTime - currentTime;
  currentTime = newTime;

  accumulator += frameTime;

  // A física alcança o tempo real em passos fixos
  while (accumulator >= dt) {
      integrate(state, t, dt); // Roda a física
      accumulator -= dt;
      t += dt;
  }

  render(state); // Desenha a tela
}

Este padrão garante que a física seja Determinística. O pulo do Mario será idêntico num PC da NASA e num laptop velho.

Imagem de tecnologia relacionada ao artigo game-loop-arquitetura-delta-time-interpolacao
Determinismo: o segredo da estabilidade em simulações complexas.

3. O "Spiral of Death" (Espiral da Morte)

O código acima tem um perigo. O loop while (accumulator >= dt) tenta rodar a física várias vezes se o PC estiver lento (lag), para "alcançar" o tempo real.

Se o computador for muito lento, rodar a física demora mais que o próprio dt.

  1. O jogo atrasa.
  2. O accumulator cresce.
  3. O loop roda mais vezes para compensar.
  4. O jogo atrasa mais ainda.
  5. O jogo trava (Freeze).

Solução: Limitar o número máximo de updates de física por frame (Clamp). Se o PC não aguenta, o jogo roda em "slow motion", mas não trava.

4. Interpolação: A Mágica Visual

Se a física roda a 20Hz (para economizar CPU) e a tela roda a 144Hz, o movimento vai parecer "travado" (stuttering), pois a posição do objeto só muda 20 vezes por segundo.

Para corrigir, usamos Interpolação. O renderizador não desenha onde o objeto está. Ele desenha onde o objeto estaria entre o frame atual e o anterior, baseado no quanto sobrou no accumulator.

render_pos = state.pos * alpha + prev_state.pos * (1.0 - alpha)

Isso cria um movimento "manteiga" a 144 FPS mesmo com uma simulação física rodando a 20 FPS.

5. Double Buffering e V-Sync

Como a tela desenha os pixels? De cima para baixo, linha por linha (Scanline). Se você atualizar a memória de vídeo enquanto o monitor está desenhando, você verá metade do frame novo e metade do frame antigo. Isso cria um corte horizontal na tela (Screen Tearing).

Double Buffering:

  1. Back Buffer: Onde a GPU desenha o frame atual (invisível).
  2. Front Buffer: O que o monitor está mostrando.

Quando a GPU termina de desenhar, ela inverte os buffers (Swap). O V-Sync (Vertical Sync) força a GPU a esperar o monitor terminar de desenhar antes de fazer o Swap, eliminando o Tearing, mas introduzindo input lag.

Conclusão

Desenvolver jogos é lutar contra o tempo. Cada milissegundo conta. O Game Loop é a fundação dessa batalha. Entender como desacoplar o "tempo de simulação" do "tempo de visualização" é o que permite criar mundos virtuais complexos que parecem reais e fluidos.


Glossário Técnico

  • FPS (Frames Per Second): Quantas vezes o render() roda por segundo.
  • Tick Rate: Quantas vezes o update() (física) roda por segundo. Servidores de CS:GO rodam a 64 ou 128 ticks.
  • Delta Time (dt): A diferença de tempo entre dois frames.
  • Determinismo: A garantia de que, dada a mesma entrada, a simulação produzirá exatamente a mesma saída.
  • Interpolation (Interpolação): Adivinhar valores intermediários entre dois pontos conhecidos.
  • Extrapolation (Extrapolação): Adivinhar valores futuros baseado na velocidade atual (usado em Netcode para esconder lag).

Referências

  1. Glenn Fiedler. Fix Your Timestep!. O artigo mais famoso sobre o assunto.
  2. Robert Nystrom. Game Programming Patterns: Game Loop. Livro essencial.
  3. Unity Documentation. Execution Order of Event Functions.
Imagem de tecnologia relacionada ao artigo game-loop-arquitetura-delta-time-interpolacao