Chegamos ao fim de tudo. No artigo Menu, Jogo, Fim — Estados de Jogo e o Snake Completo completamos o Snake e dominamos os estados de jogo; agora construímos o terceiro e mais completo jogo — o Breakout, o quebra-tijolos —, o capstone que integra cada peça da trilha: a raquete e a bola vêm do Pong (Quando as Coisas se Tocam), as dezenas de tijolos se organizam com o ECS (Entidades de Verdade), a colisão AABB (Quando as Coisas se Tocam) faz tudo interagir, os estados de jogo (Menu, Jogo, Fim) envolvem as telas, e o RAII (O Primeiro Pixel, Imagem e Som) gerencia os recursos. É a coroação da trilha de jogos e, mais que isso, o ponto final de um ano inteiro de C++. Depois do Breakout, faremos a retrospectiva desta segunda extensão e, então, o balanço da jornada completa — das nove fases ao capstone, das duas trilhas-extensão a este adeus. Vamos construir um último jogo, e fechar o ciclo.
A anatomia do Breakout
O Breakout reúne, num só jogo, tudo o que construímos separadamente. Há uma raquete que o jogador move na horizontal (como no Pong, mas só num eixo). Há uma bola que quica pelas paredes, pela raquete e pelos tijolos (a física de colisão do Quando as Coisas se Tocam). E há uma parede de tijolos — dezenas deles, dispostos em linhas —, cada um destruído quando a bola o atinge. O jogador tem vidas (perde uma quando a bola cai) e um placar (sobe a cada tijolo quebrado). O objetivo: destruir todos os tijolos sem perder todas as vidas. Modelamos os tijolos como entidades do ECS (Entidades de Verdade) — muitos objetos do mesmo tipo, o cenário perfeito para o padrão:
#include <raylib.h>
#include <vector>
// Um tijolo é uma entidade simples: posição, tamanho, cor e se está vivo.
struct Tijolo {
float x, y, largura, altura;
Color cor;
bool vivo; // false quando destruído
};
// Cria a parede de tijolos: várias linhas e colunas.
std::vector<Tijolo> cria_tijolos() {
std::vector<Tijolo> tijolos;
const int linhas = 6, colunas = 10;
const float lt = 70, at = 25, esp = 5; // largura, altura, espaçamento
const Color cores[] = { RED, ORANGE, YELLOW, GREEN, SKYBLUE, PURPLE };
for (int lin = 0; lin < linhas; ++lin)
for (int col = 0; col < colunas; ++col)
tijolos.push_back({
col * (lt + esp) + 35, // x, com margem
lin * (at + esp) + 50, // y
lt, at,
cores[lin], // cor por linha
true // começa vivo
});
return tijolos; // 60 tijolos, gerados por composição (Fase 4 + ECS)
}
Sessenta tijolos, gerados por dois laços e guardados num std::vector (Fase 4) — o ECS do Entidades de Verdade em ação: cada tijolo é uma entidade composta de dados (posição, cor, estado), sem hierarquia. Adicionar um tijolo especial (que resiste a dois toques, digamos) seria só mais um campo no struct, não uma nova classe. A parede inteira é uma coleção uniforme que os sistemas de colisão e render percorrem.
A colisão da bola com os tijolos
O coração do Breakout é a bola atingindo os tijolos. A cada frame, verificamos a colisão AABB (Quando as Coisas se Tocam) entre a bola e cada tijolo vivo; ao acertar, o tijolo "morre", a bola quica, e o placar sobe:
// Verifica colisão da bola com cada tijolo vivo; destrói e quica.
void colisao_tijolos(float bx, float by, float btam,
float& bvy, std::vector<Tijolo>& tijolos, int& placar) {
for (auto& t : tijolos) {
if (!t.vivo) continue; // ignora os já destruídos
if (colide(bx, by, btam, btam, t.x, t.y, t.largura, t.altura)) {
t.vivo = false; // destrói o tijolo
bvy = -bvy; // a bola quica na vertical
placar += 10; // pontos
break; // trata um tijolo por frame (evita quicar múltiplo)
}
}
}
Percorremos os tijolos (a STL da Fase 4), pulando os mortos, e ao detectar colisão com um vivo, marcamos vivo = false, invertemos a velocidade vertical da bola (o quique do Quando as Coisas se Tocam), e somamos pontos. O break trata só um tijolo por frame, evitando que a bola quique de forma errática ao tocar dois de uma vez. É a colisão AABB do Pong, agora aplicada a dezenas de alvos — a mesma técnica, escalada. Os tijolos destruídos permanecem no vetor (apenas marcados como mortos), o que é simples e eficiente; alternativamente, poderíamos removê-los com o idioma erase-remove do artigo Pare de Escrever Laços — a Biblioteca <algorithm>.
O Breakout completo
Reunindo tudo — raquete, bola, tijolos, vidas, placar e estados —, eis o jogo inteiro, a integração final de toda a trilha:
#include <raylib.h>
#include <vector>
int main() {
Janela janela(800, 600, "Breakout"); // RAII (O Primeiro Pixel)
Estado estado = Estado::Menu; // máquina de estados (Menu, Jogo, Fim)
std::vector<Tijolo> tijolos;
float raq_x = 350, bola_x = 400, bola_y = 400, bola_vx = 250, bola_vy = -250;
const float raq_l = 100, raq_a = 20, raq_y = 550, bola_tam = 12;
int placar = 0, vidas = 3;
auto reinicia = [&]() {
tijolos = cria_tijolos();
raq_x = 350; bola_x = 400; bola_y = 400; bola_vx = 250; bola_vy = -250;
placar = 0; vidas = 3;
};
while (!janela.deve_fechar()) {
float dt = GetFrameTime();
if (estado == Estado::Menu) {
if (IsKeyPressed(KEY_ENTER)) { reinicia(); estado = Estado::Jogando; }
}
else if (estado == Estado::Jogando) {
// --- Raquete: move na horizontal com as setas ---
if (IsKeyDown(KEY_LEFT) && raq_x > 0) raq_x -= 500 * dt;
if (IsKeyDown(KEY_RIGHT) && raq_x < 800 - raq_l) raq_x += 500 * dt;
// --- Bola: move ---
bola_x += bola_vx * dt;
bola_y += bola_vy * dt;
// Quique nas paredes laterais e no teto:
if (bola_x <= 0 || bola_x + bola_tam >= 800) bola_vx = -bola_vx;
if (bola_y <= 0) bola_vy = -bola_vy;
// Quique na raquete:
if (colide(bola_x, bola_y, bola_tam, bola_tam, raq_x, raq_y, raq_l, raq_a))
bola_vy = -fabsf(bola_vy); // sempre para cima (evita grudar, Quando as Coisas se Tocam)
// Colisão com os tijolos:
colisao_tijolos(bola_x, bola_y, bola_tam, bola_vy, tijolos, placar);
// Bola caiu: perde uma vida.
if (bola_y > 600) {
vidas--;
bola_x = 400; bola_y = 400; bola_vx = 250; bola_vy = -250;
if (vidas <= 0) estado = Estado::GameOver;
}
// Vitória: todos os tijolos destruídos.
bool venceu = true;
for (const auto& t : tijolos) if (t.vivo) { venceu = false; break; }
if (venceu) estado = Estado::GameOver;
}
else if (estado == Estado::GameOver) {
if (IsKeyPressed(KEY_ENTER)) estado = Estado::Menu;
}
// --- RENDER por estado ---
BeginDrawing();
ClearBackground(BLACK);
if (estado == Estado::Menu) {
DrawText("BREAKOUT", 260, 200, 60, WHITE);
DrawText("Enter para jogar", 290, 320, 20, GRAY);
}
else if (estado == Estado::Jogando) {
for (const auto& t : tijolos) // desenha tijolos vivos
if (t.vivo)
DrawRectangle((int)t.x, (int)t.y, (int)t.largura, (int)t.altura, t.cor);
DrawRectangle((int)raq_x, (int)raq_y, (int)raq_l, (int)raq_a, WHITE); // raquete
DrawRectangle((int)bola_x, (int)bola_y, (int)bola_tam, (int)bola_tam, WHITE); // bola
DrawText(TextFormat("Placar: %d", placar), 10, 10, 20, WHITE);
DrawText(TextFormat("Vidas: %d", vidas), 680, 10, 20, WHITE);
}
else if (estado == Estado::GameOver) {
DrawText("FIM", 350, 220, 50, WHITE);
DrawText(TextFormat("Placar final: %d", placar), 290, 300, 25, WHITE);
DrawText("Enter para o menu", 280, 360, 20, GRAY);
}
EndDrawing();
}
return 0;
}
Rode e jogue — mova a raquete, rebata a bola, destrua os sessenta tijolos coloridos, cuide das suas três vidas. É o jogo mais completo da trilha, e ele é a soma de tudo: o game loop (Um Novo Território) orquestra; a janela RAII (O Primeiro Pixel) gerencia a tela; a raquete e a bola reúnem o movimento e a colisão do Pong (Quando as Coisas se Tocam); os tijolos são entidades do ECS (Entidades de Verdade) num std::vector (Fase 4); a máquina de estados (Menu, Jogo, Fim) governa menu, jogo e fim; o delta time torna tudo consistente; e a lambda reinicia (Fase 5) prepara cada partida. Da janela vazia do O Primeiro Pixel a este quebra-tijolos completo, a trilha inteira convergiu num jogo — assim como o mini banco de dados foi a convergência do curso principal. Você construiu três jogos, do zero, com o C++ que dominou.
O Breakout reúne o que a trilha construiu: janela e recursos em RAII, entidades compostas, colisão AABB, máquina de estados e movimento por delta time. Nenhuma dessas peças é específica de jogos — são as mesmas ideias das fases anteriores, encontrando um domínio em que a consequência de errar aparece na tela, imediatamente.
Três jogos depois, o que fica não é a raylib, que pode ser trocada por outra biblioteca sem alterar o raciocínio. É a arquitetura: separar dados de comportamento, dar dono a cada recurso, tornar estados impossíveis irrepresentáveis e manter a simulação independente da máquina onde roda.
A palavra final
Um ano. Cinquenta e nove artigos. Nove fases, um capstone, e duas trilhas-extensão que levaram o C++ a bancos de dados, à web e aos jogos. Você começou como um programador C, curioso e talvez intimidado, e termina dominando C++ moderno em toda a sua amplitude — capaz de projetar sistemas, persistir dados, servir na web, e criar jogos, tudo com a segurança do RAII, a expressividade da STL, a clareza dos tipos modernos, e a honestidade sobre custos e trade-offs que buscamos cultivar em cada aula. O caminho foi longo, mas foi de degraus firmes, cada um apoiado no anterior, cada promessa plantada e paga. E se o C++ deixou de ser uma montanha intimidante para virar uma ferramenta poderosa a serviço das suas ideias — em sistemas, na web ou em jogos —, então esta jornada inteira cumpriu tudo o que prometeu no primeiro "Olá, C++!". Obrigado por percorrê-la até o último pixel. O domínio que você construiu é seu, para sempre. Agora vá construir mundos — de dados, de páginas, ou de jogos. A jornada de aprender terminou; a de criar, essa não tem fim. Boa sorte, e divirta-se — afinal, você acabou de aprender a fazer jogos.
Fontes e leituras recomendadas
- raylib.com e seus exemplos: continue com a biblioteca que usamos; há exemplos de Breakout, plataformas, e muito mais para estudar e expandir.
- Robert Nystrom, Game Programming Patterns (gameprogrammingpatterns.com): releia-o agora, do início — os padrões farão pleno sentido com os três jogos que você construiu.
- "Handmade Hero" (handmadehero.org): uma série que constrói um jogo completo em C/C++ do absoluto zero, para quem quer ir fundo.
- learnopengl.com: se os gráficos o fascinaram, o caminho para o 3D e o controle total da renderização começa aqui.
- A comunidade de game dev (r/gamedev, itch.io, game jams): construa, publique, participe — é assim que game developers nascem.
Exercícios
Exercício 1
Compile e jogue o Breakout completo. Descreva a experiência: mover a raquete, rebater a bola, destruir tijolos, perder vidas. O que acontece ao destruir todos os tijolos, e ao perder as três vidas?
Ver resposta
✓ Resposta: Ao jogar, você move a raquete branca na base da tela com as setas esquerda/direita, rebatendo a bola para cima. A bola quica nas paredes laterais, no teto e na raquete, e ao atingir um tijolo, o destrói (o tijolo some), quica de volta, e o placar sobe 10 pontos. Se a bola passar pela raquete e cair pela base, você perde uma vida (das três iniciais), e a bola reinicia. Ao destruir todos os sessenta tijolos, o jogo vai para a tela de fim (vitória) mostrando o placar final. Ao perder as três vidas, o jogo também vai para a tela de fim (derrota). Em ambos os casos, Enter volta ao menu para jogar de novo. A progressão de cores dos tijolos (vermelho no topo, roxo embaixo) dá um visual agradável, e o desafio está em não deixar a bola cair enquanto se limpa a parede.
Exercício 2
Identifique, no código do Breakout, um lugar concreto onde cada uma das seguintes peças aparece: (a) RAII; (b) o ECS/composição; (c) colisão AABB; (d) a máquina de estados; (e) delta time. Para cada uma, cite o artigo (da trilha ou do curso) que a ensinou.
Ver resposta
✓ Resposta: As peças no Breakout: (a) RAII — na criação Janela janela(800, 600, "Breakout"), que abre a janela no construtor e a fecha no destrutor (ensinado no O Primeiro Pixel, com raiz na Fase 2). (b) ECS/composição — os tijolos como std::vector<Tijolo>, cada Tijolo uma entidade composta de dados (posição, cor, vivo) sem herança (ensinado no Entidades de Verdade, com raiz na Fase 3). (c) Colisão AABB — nas chamadas a colide(...) entre a bola e a raquete, e na colisao_tijolos entre a bola e cada tijolo (ensinado no Quando as Coisas se Tocam). (d) Máquina de estados — na variável Estado estado e nos ramos if (estado == ...) que tratam Menu, Jogando e GameOver (ensinado no Menu, Jogo, Fim, com o enum class da Fase 1). (e) Delta time — em todo movimento multiplicado por dt, como bola_x += bola_vx * dt e raq_x -= 500 * dt (ensinado no Um Novo Território). Cada peça vem de um artigo específico, e juntas mostram a integração completa da trilha e do curso.
Exercício 3
Adicione ao Breakout um efeito de "quique melhorado": faça o ângulo de rebatida da bola na raquete depender de onde ela bate (mais para a borda = ângulo mais acentuado). Descreva conceitualmente como você calcularia a nova velocidade horizontal com base na posição do impacto relativa ao centro da raquete.
Ver resposta
✓ Resposta: Para o quique melhorado, a ideia é que a bola não apenas inverta a direção vertical ao bater na raquete, mas ganhe uma velocidade horizontal proporcional a quão longe do centro da raquete ela bateu — batendo no centro, sobe reto; batendo perto das bordas, sai em ângulo. Conceitualmente: calcula-se a posição do impacto relativa ao centro da raquete, normalizada para o intervalo [-1, +1]. Se o centro da bola está no centro exato da raquete, o valor é 0; na borda esquerda, -1; na direita, +1. A fórmula seria algo como float rel = (bola_centro_x - (raq_x + raq_l/2)) / (raq_l/2); — isso dá o quão descentrado foi o impacto, de -1 a +1. Então define-se a nova velocidade horizontal como esse valor vezes uma velocidade máxima: bola_vx = rel * VELOCIDADE_MAX;, e mantém-se a bola indo para cima (bola_vy = -fabsf(bola_vy)), possivelmente ajustando a magnitude total para manter a velocidade constante. O efeito: bater com a pontinha da raquete manda a bola em diagonal acentuada; bater no meio, sobe quase reto. Isso dá ao jogador controle sobre a direção da bola — uma mecânica clássica do Breakout que transforma a raquete de um simples anteparo numa ferramenta de mira, aumentando muito a profundidade do jogo. (Usa a matemática de vetores e proporção, e a lógica de colisão do Quando as Coisas se Tocam.)
Exercício 4
O Breakout mantém os tijolos destruídos no vetor (marcados com vivo = false). Discuta o trade-off entre essa abordagem e removê-los do vetor com o idioma erase-remove (artigo Pare de Escrever Laços — a Biblioteca <algorithm>). Em que situação cada escolha seria preferível?
Ver resposta
✓ Resposta: Manter os tijolos destruídos no vetor (marcados vivo = false) versus removê-los com erase-remove:
- Manter (marcar como morto): vantagens — simplicidade (só mudar um booleano), estabilidade de índices e posições (os tijolos vivos não mudam de lugar no vetor, nem de posição na tela), e nenhum custo de realocação. Desvantagem — o laço de colisão e render continua percorrendo os tijolos mortos (pulando-os com if (!vivo) continue), um pequeno desperdício que cresce se houver muitíssimos objetos mortos. Para o Breakout, com 60 tijolos, isso é irrelevante.
- Remover (erase-remove, Pare de Escrever Laços): vantagem — o vetor encolhe, e os laços só percorrem tijolos vivos, sem desperdício. Desvantagens — o erase desloca elementos (custo O(n)), invalida índices/iteradores (cuidado se algo referencia tijolos por posição), e a remoção no meio de um frame de jogo pode complicar a lógica se outras partes assumem posições fixas.
Situações: manter é preferível quando o número de objetos é modesto (como aqui) e a simplicidade e estabilidade importam mais que o micro-desempenho — o caso do Breakout. Remover é preferível quando há um número enorme de objetos e muitos são destruídos, tornando o desperdício de percorrer mortos significativo, ou quando a memória importa e você quer liberar o espaço dos destruídos. A escolha segue a mesma disciplina da Fase 4: para poucos objetos, prefira a simplicidade; para muitos, meça e otimize se o percurso dos mortos virar gargalo. No nosso caso, marcar como morto é a escolha certa — simples e suficiente.
Exercício 5
Escolha um dos três jogos (Pong, Snake ou Breakout) e projete três melhorias que você gostaria de implementar, indicando, para cada uma, quais conceitos do curso (fases principais ou trilhas) você usaria. Por exemplo: "adicionar som ao quebrar tijolos usaria a classe Som RAII do Imagem e Som".
Ver resposta
✓ Resposta: Resposta pessoal de projeto — uma boa resposta escolhe um jogo e propõe melhorias com os conceitos certos. Exemplo (Breakout):
- Melhoria 1 — som ao quebrar tijolos e rebater: usaria a classe Som RAII do Imagem e Som (LoadSound/PlaySound envolvidos em RAII), tocando um efeito na colisão com tijolo e na raquete. Conceitos: RAII (Fase 2, Imagem e Som), gerenciamento de recursos.
- Melhoria 2 — power-ups que caem de alguns tijolos (raquete maior, bola extra): quando um tijolo especial é destruído, cria-se uma entidade "power-up" que cai; se a raquete a pega (colisão AABB, Quando as Coisas se Tocam), aplica-se o efeito. Conceitos: ECS/composição (Entidades de Verdade) para os power-ups como entidades, colisão AABB (Quando as Coisas se Tocam), enum/variant (Fase 6) para os tipos de power-up.
- Melhoria 3 — múltiplos níveis com layouts diferentes de tijolos: carregar cada nível de um arquivo (a persistência da extensão anterior, Extensão/Extensão com streams) ou de dados embutidos, e avançar de nível ao limpar a parede, adicionando um estado ProximoNivel à máquina de estados (Menu, Jogo, Fim). Conceitos: máquina de estados (Menu, Jogo, Fim), streams/arquivos (Fase 1, extensão), std::vector (Fase 4) para os layouts.
O valor do exercício é o aluno perceber que cada melhoria se apoia em conceitos que ele já domina — que o curso inteiro, das fases às trilhas, é a caixa de ferramentas para construir o que quiser. E esse é o fecho perfeito: você não terminou de aprender para parar, mas para começar a criar.
Fim da série completa — obrigado por percorrer toda a jornada, do primeiro "Olá, C++!" ao último pixel. 🎮🎯