Um Novo Território — Por que Jogos, o Game Loop e as Três Bibliotecas

Um Novo Território — Por que Jogos, o Game Loop e as Três Bibliotecas

Um jogo não espera o usuário: o laço roda dezenas de vezes por segundo mesmo quando ninguém toca em nada, e isso muda a forma do programa. O artigo apresenta o game loop, explica por que mover objetos por frame amarra o jogo à velocidade da máquina, e compara raylib, SFML e SDL.
Linguagem C++

12 min de leitura

Encerramos a série completa no artigo O Destino e o Novo Começo — Encerrando a Jornada Completa, mas você pediu um território novo, e ele merece a visita: os jogos. C++ é a língua franca do desenvolvimento de jogos sérios — de motores como Unreal a títulos AAA, de indies a emuladores, o desempenho e o controle que você dominou ao longo do ano são exatamente o que jogos exigem. Esta segunda trilha-extensão, de sete artigos (Um Novo Território a O Capstone dos Jogos), vai levá-lo do zero a jogos completos — Pong, Snake e Breakout —, reaproveitando tudo o que você aprendeu: RAII para recursos gráficos, STL para entidades, a composição sobre herança da Fase 3, os ponteiros inteligentes da Fase 2. Hoje abrimos com o conceito que torna um jogo fundamentalmente diferente de qualquer programa que você escreveu até aqui — o game loop — e com um comparativo honesto das três bibliotecas que usaremos como base: raylib, SFML e SDL.

Um aviso honesto antes de começar

Prometo a franqueza de sempre, e ela começa aqui. Ao contrário de todo o resto do curso, os exemplos de jogos precisam de uma janela gráfica — eles abrem uma tela, desenham, capturam teclado e mouse. Isso significa que eles não rodam num ambiente sem tela (headless); você precisa de uma máquina desktop (Windows, macOS ou Linux com ambiente gráfico) e da biblioteca instalada. Vou sempre indicar a linha de compilação e como obter a biblioteca (via vcpkg, da extensão anterior, ou pelos pacotes do sistema). Não se assuste se um exemplo não compilar no mesmo ambiente onde você rodou o resto do curso — jogos vivem noutro habitat. Dito isso, o código é real e roda de verdade numa máquina apropriada.

O que torna um jogo diferente: o game loop

Todo programa que você escreveu até aqui seguia um fluxo linear: começa, faz seu trabalho, termina. Mesmo o servidor web do Extensão, embora fique ativo, reage a requisições — ele dorme até algo chegar. Um jogo é fundamentalmente diferente: ele roda um laço contínuo e infinito que, dezenas de vezes por segundo, lê a entrada do jogador, atualiza o estado do mundo, e desenha tudo na tela — mesmo que nada aconteça. Esse laço é o game loop, o coração pulsante de qualquer jogo:

// O ESQUELETO conceitual de qualquer jogo. Este é o coração de tudo.
int main() {
    inicializa();                 // cria a janela, carrega recursos

    while (!jogo_terminou()) {     // o LOOP: roda ~60 vezes por segundo
        processa_input();          // 1. lê teclado, mouse, controle
        atualiza_estado();         // 2. move objetos, checa colisões, aplica regras
        desenha();                 // 3. renderiza tudo na tela
    }

    finaliza();                    // libera recursos, fecha a janela
    return 0;
}

Essas três fases — input, update, render — repetidas continuamente, são o que cria a ilusão de um mundo vivo. Cada volta do laço é um frame. A 60 frames por segundo (o padrão), o laço roda 60 vezes a cada segundo, e cada pequena mudança de posição, somada frame a frame, vira movimento suave. É uma mudança de mentalidade profunda: você não descreve mais "o que o programa faz" de forma linear, mas "o que acontece a cada instante" de forma cíclica. Todo o resto — física, animação, IA, som — pendura-se nesse laço.

Delta time: o tempo entre frames

Há uma sutileza crucial no game loop, e ignorá-la é um erro clássico de iniciante. Se você mover um objeto "5 pixels por frame", a velocidade do jogo dependerá da velocidade da máquina — num computador rápido que roda 120 frames por segundo, o objeto anda o dobro do que num que roda 60. Isso é inaceitável. A solução é o delta time: o tempo decorrido desde o frame anterior. Você move os objetos proporcionalmente a esse tempo, tornando o movimento independente da taxa de frames:

// ERRADO: velocidade depende da máquina.
posicao += 5;                    // 5 pixels por FRAME — mais rápido em máquina veloz

// CERTO: velocidade em pixels por SEGUNDO, independente da taxa de frames.
float velocidade = 300.0f;       // 300 pixels por segundo
posicao += velocidade * dt;      // dt = tempo do último frame (ex.: 0.016s a 60fps)
// Em 60fps, dt≈0.016, então move 300*0.016≈5 pixels/frame.
// Em 120fps, dt≈0.008, então move 300*0.008≈2.5 pixels/frame — MESMA velocidade real.

Multiplicar tudo por dt garante que o jogo se comporte igual em qualquer máquina. Guarde essa ideia — posicao += velocidade * dt — porque ela vai aparecer em todo movimento que fizermos daqui para frente. É a diferença entre um jogo que se comporta consistentemente e um que acelera ou trava conforme o hardware.

As três bibliotecas, lado a lado

Para desenhar na tela, capturar input e tocar som, precisamos de uma biblioteca — escrever isso do zero (falando direto com o sistema operacional e a placa de vídeo) é trabalho de meses. Há três escolhas populares em C++, e serei honesto sobre cada uma para você entender o terreno. Reunindo os pontos num quadro comparativo:

A raylib é uma biblioteca em C, minimalista e feita para ensinar. Sua API é diretíssima — InitWindow(800, 600, "Jogo"), DrawRectangle(...), IsKeyDown(KEY_SPACE) —, tudo incluído (gráficos 2D e 3D, áudio, input, fontes), e é a mais fácil de integrar e a que menos atrapalha o foco no conceito de jogo. Sua contrapartida honesta: por ser C, ela não gerencia recursos automaticamente — nós aplicamos RAII por cima, o que é ótimo pedagogicamente (repete a lição do SQLite no Extensão). Será nosso fio condutor.

A SFML (Simple and Fast Multimedia Library) é uma biblioteca em C++ nativo, orientada a objetos, com tipos como sf::RenderWindow, sf::Texture, sf::Sprite. Ela já vem com RAII embutido — seus objetos se limpam sozinhos —, sendo a mais idiomática das três. É um degrau acima de raylib em cerimônia, mas ainda amigável, e ótima para quem quer C++ moderno de cara.

A SDL (Simple DirectMedia Layer) é a mais baixo nível e a mais usada na indústria — muitos jogos comerciais e engines a usam por baixo. API em C, dá controle total sobre janela, renderização e áudio, ao custo de mais código e mais gerenciamento manual. É a que mais revela "o que acontece por baixo", ao preço de mais trabalho.

Nossa estratégia: usaremos raylib como fio condutor dos exemplos (pela clareza), mas em pontos-chave mostrarei o equivalente em SFML e SDL, para você ver o padrão por trás e não ficar preso a uma só. O "Olá, mundo" das três — abrir uma janela — ilustra a diferença de estilo:

// raylib — direto ao ponto (C, minimalista):
#include "raylib.h"
int main() {
    InitWindow(800, 600, "raylib");
    while (!WindowShouldClose()) {
        BeginDrawing();
        ClearBackground(RAYWHITE);
        DrawText("Olá, jogos!", 300, 280, 20, BLACK);
        EndDrawing();
    }
    CloseWindow();
    return 0;
}
// SFML — orientado a objetos (C++ nativo, RAII embutido):
#include <SFML/Graphics.hpp>
int main() {
    sf::RenderWindow janela(sf::VideoMode(800, 600), "SFML");
    while (janela.isOpen()) {
        sf::Event ev;
        while (janela.pollEvent(ev))
            if (ev.type == sf::Event::Closed) janela.close();
        janela.clear(sf::Color::White);
        janela.display();
    }
    return 0;   // a janela se fecha sozinha — RAII da SFML
}

As três fazem a mesma coisa; a raylib com menos cerimônia, a SFML com mais estrutura de C++. Conhecer as diferenças ajuda você a escolher a certa para seu projeto — e a entender que o conceito (abrir janela, laço, desenhar) é o mesmo em todas.

O game loop é o que separa um jogo de um programa comum: ele roda continuamente, mesmo quando ninguém toca em nada, refazendo entrada, atualização e desenho dezenas de vezes por segundo. Daí vem a necessidade do delta time — mover um objeto uma quantidade fixa por frame amarra a velocidade do jogo à velocidade da máquina, e o mesmo código fica lento num computador e injogável em outro. Multiplicar pelo tempo decorrido é o que torna o movimento independente da taxa de quadros.

Fontes e leituras recomendadas

  • raylib.com: o site da raylib, com exemplos, a referência da API (cheatsheet) e instruções de instalação — nosso fio condutor.
  • sfml-dev.org: o site da SFML, com tutoriais e documentação da biblioteca C++ orientada a objetos.
  • libsdl.org: o site da SDL, com a documentação da biblioteca de mais baixo nível usada na indústria.
  • "Fix Your Timestep!" (gafferongames.com/post/fix_your_timestep): o artigo clássico de Glenn Fiedler sobre game loops e delta time — leitura essencial sobre o tema de hoje.
  • Robert Nystrom, Game Programming Patterns (gameprogrammingpatterns.com, gratuito online): o livro de referência sobre padrões de design em jogos, que citaremos ao longo da trilha.

Exercícios

Exercício 1

Explique, com suas palavras, por que o game loop é fundamentalmente diferente do fluxo de um programa comum (como os que você escreveu nas fases anteriores). O que o laço faz mesmo quando o jogador não faz nada?

Ver resposta

✓ Resposta: Um programa comum segue um fluxo linear ou reativo: ele executa suas instruções e termina, ou (como um servidor) dorme até um evento chegar e então reage. Um game loop é um laço contínuo e ativo que roda dezenas de vezes por segundo independentemente de haver ação do jogador — a cada volta (frame), ele lê a entrada, atualiza o estado do mundo e redesenha a tela. A diferença central é que o jogo está sempre "vivo": mesmo quando o jogador não faz nada, o laço continua rodando, e o mundo pode continuar evoluindo — inimigos se movem, o tempo passa, animações avançam, a física age. O laço não espera por eventos como um servidor; ele impulsiona o mundo a cada frame. É a diferença entre um programa que reage e um que simula continuamente.

Exercício 2

Explique o conceito de delta time e por que mover objetos "N pixels por frame" é um erro. O que aconteceria com um jogo assim ao rodar numa máquina duas vezes mais rápida?

Ver resposta

✓ Resposta: Delta time é o tempo decorrido desde o frame anterior (tipicamente uma fração de segundo, como 0.016s a 60fps). Mover objetos "N pixels por frame" é um erro porque vincula a velocidade do jogo à taxa de frames, que varia com o hardware: como o número de frames por segundo depende de quão rápida é a máquina, um movimento fixo por frame resulta em velocidades reais diferentes em máquinas diferentes. Numa máquina duas vezes mais rápida (que roda o dobro de frames por segundo), um objeto que anda "5 pixels por frame" se moveria duas vezes mais rápido na tela — o jogo inteiro aceleraria, tornando-se injogável ou inconsistente. Multiplicando o movimento por delta time (velocidade * dt), você expressa a velocidade em pixels por segundo (uma unidade real), e o movimento por frame se ajusta automaticamente à taxa de frames, mantendo a velocidade real idêntica em qualquer máquina.

Exercício 3

Dado que um jogo roda a 60 frames por segundo e um objeto deve se mover a 240 pixels por segundo, calcule quantos pixels ele se move por frame usando velocidade * dt. Depois recalcule para 30 fps e confirme que a velocidade real (pixels por segundo) é a mesma.

Ver resposta

✓ Resposta: A 60 fps, cada frame dura dt = 1/60 ≈ 0.0167 segundos. Movimento por frame = 240 * 0.0167 ≈ 4 pixels. A 30 fps, cada frame dura dt = 1/30 ≈ 0.0333 segundos. Movimento por frame = 240 * 0.0333 ≈ 8 pixels. Confirmando a velocidade real: a 60 fps, 4 pixels/frame × 60 frames/s = 240 pixels/s; a 30 fps, 8 pixels/frame × 30 frames/s = 240 pixels/s. Idêntica. Embora o objeto se mova mais pixels por frame na máquina mais lenta (para compensar os menos frames), a velocidade real na tela — pixels por segundo — é a mesma nos dois casos. É exatamente isso que o delta time garante.

Exercício 4

Compare as três bibliotecas (raylib, SFML, SDL) quanto a: linguagem base, nível de abstração, e gestão de recursos (RAII embutido ou manual). Para um iniciante que quer focar em aprender conceitos de jogo, qual você recomendaria e por quê?

Ver resposta

✓ Resposta: Comparação: - raylib: linguagem base C; nível de abstração alto e minimalista (API diretíssima); gestão de recursos manual (por ser C, exige RAII aplicado por nós). - SFML: linguagem base C++ nativo; nível de abstração médio-alto, orientado a objetos; gestão de recursos RAII embutido (os objetos se limpam sozinhos). - SDL: linguagem base C; nível de abstração baixo (controle detalhado); gestão de recursos manual (mais gerenciamento explícito). Para um iniciante focado em aprender conceitos de jogo, eu recomendaria a raylib: sua API minimalista e direta tira do caminho a cerimônia e a infraestrutura, deixando o foco no que importa (o game loop, movimento, colisão, estados), e sua natureza em C ainda nos dá a oportunidade pedagógica de aplicar RAII por cima — reforçando uma lição central do curso. A SFML seria uma segunda escolha excelente (C++ idiomático de cara); a SDL, por ser mais baixo nível e trabalhosa, é melhor deixar para quando os conceitos já estiverem sólidos e você quiser mais controle.

Exercício 5

Escreva (em pseudocódigo ou C++ conceitual) o game loop de um jogo simples onde uma bola cai pela tela: descreva o que iria em cada uma das três fases (input, update, render), usando delta time no movimento.

Ver resposta

✓ Resposta: Game loop da bola que cai:

// Estado: posição e velocidade vertical da bola.
float bola_y = 0.0f;           // começa no topo
float velocidade_y = 200.0f;   // 200 pixels por segundo, para baixo

while (!jogo_terminou()) {
    float dt = tempo_do_ultimo_frame();

    // 1. INPUT: ler entrada do jogador.
    //    (ex.: se apertar espaço, reinicia a bola no topo)
    if (tecla_pressionada(ESPACO))
        bola_y = 0.0f;

    // 2. UPDATE: atualizar o estado usando delta time.
    bola_y += velocidade_y * dt;        // move para baixo, proporcional ao tempo
    if (bola_y > altura_tela)           // se saiu por baixo...
        bola_y = 0.0f;                  // ...volta ao topo (regra do jogo)

    // 3. RENDER: desenhar tudo na tela.
    limpa_tela();
    desenha_circulo(largura_tela / 2, bola_y, 10);   // a bola na posição atual
    apresenta();
}

Na fase de input, lê-se o teclado (aqui, espaço reinicia a bola). Na fase de update, a posição da bola é atualizada com velocidade_y * dt (movimento consistente por delta time) e aplica-se a regra de voltar ao topo ao sair da tela. Na fase de render, limpa-se a tela e desenha-se a bola na sua posição atual. As três fases, repetidas a cada frame, criam a animação da bola caindo suavemente — e, graças ao dt, à mesma velocidade em qualquer máquina.

Comentários

Mais em Linguagem C++

Buscas Instantâneas — map, set e suas Versões Hash
Buscas Instantâneas — map, set e suas Versões Hash

Nem todo problema é sobre sequências; muitos são sobre associações, e em C…

O Primeiro Pixel — Janela, Desenho e Input com raylib
O Primeiro Pixel — Janela, Desenho e Input com raylib

A primeira janela traz de volta uma lição antiga em contexto novo: ela é um…

Escreva uma Vez, Use com Todo Tipo — Templates de Função
Escreva uma Vez, Use com Todo Tipo — Templates de Função

Escrever a mesma função para int, double e string é o tipo de repetição que o…