CURSOS / TECNOLOGIA / PYTHON NA PRÁTICA (ETAPA 4 DE 5)
← Voltar aos cursos
CURSO PRÁTICO · ETAPA 4 DE 5 · CAPÍTULOS 12–14

Projeto de Consolidação Lógica: Alien Invasion

Hora de colocar classes, módulos e refatoração à prova construindo um jogo 2D completo a 60 FPS com Pygame — da janela principal e disparo de projéteis até frotas coordenadas, detecção de colisões, escalonamento dinâmico de velocidade e placar com recorde.

Ilustração retro-futurista da nave espacial e frota alienígena em um gabinete de desenvolvimento Python
6 Oficinas PráticasCapítulos 12–14 com Pygame e sprites
Podcast IntegradoEpisódio complementar direto na página
Simulador 60 FPSPreview de velocidade e pontuação por onda

Trilha do Curso: 5 Etapas Progressivas

Você está na Etapa 4: Projeto de Consolidação Lógica.

PODCAST DE APOIO · REPRODUÇÃO DIRETA NA PÁGINA

A Engenharia por Trás do Alien Invasion

Ouça o episódio complementar direto por aqui antes ou depois das oficinas para conectar o game loop de 60 FPS, a composição de sprites em grupos, o gerenciamento de memória de projéteis e a evolução de dificuldade do jogo.

Abrir episódio no RSS.com ↗

Oficinas Práticas — Capítulos 12 a 14

Pygame · 60 FPS
Diagrama de fluxo do game loop de 60 FPS em Pygame: captura de eventos, atualização de posições, detecção de colisões e renderização
Fluxo contínuo do game loop: captura de eventos, física, colisões e renderização.
OFICINA 01 · CAPÍTULO 12

1. Loop Pygame, Clock e Classe Settings

Todo jogo em Pygame gira em torno de um laço infinito (while True:) que captura entradas do usuário, atualiza posições e redesenha a tela. Antes de escrever código, planeje os módulos separados (alien_invasion.py, settings.py, ship.py, bullet.py) e instale o Pygame com python -m pip install --user pygame.

Em vez de espalhar números mágicos pelo código, centralize dimensões, cores e velocidades na classe Settings. No arquivo principal, métodos auxiliares prefixados com sublinhado (como _check_events() e _update_screen()) mantêm run_game() legível e fácil de evoluir, enquanto self.clock.tick(60) trava a taxa em 60 quadros por segundo.

  • settings.py: centraliza parâmetros estáticos da janela e velocidades iniciais para ajuste rápido em um único lugar.
  • Métodos auxiliares (_metodo): a convenção com underscore inicial indica métodos internos da classe AlienInvasion usados para refatorar o loop.
Python · settings.py e alien_invasion.py (base)
import sys
import pygame

class Settings:
    def __init__(self):
        self.screen_width = 1200
        self.screen_height = 800
        self.bg_color = (230, 230, 230)
        self.ship_speed = 1.5
        self.bullet_speed = 2.5
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color = (60, 60, 60)
        self.bullets_allowed = 3

class AlienInvasion:
    def __init__(self):
        pygame.init()
        self.clock = pygame.time.Clock()
        self.settings = Settings()
        self.screen = pygame.display.set_mode(
            (self.settings.screen_width, self.settings.screen_height)
        )
        pygame.display.set_caption("Alien Invasion")

    def run_game(self):
        while True:
            self._check_events()
            self._update_screen()
            self.clock.tick(60)
Salvo localmente.
OFICINA 03 · CAPÍTULO 13

3. Frota Alienígena, Laços Aninhados e Bordas

A classe Alien também herda de Sprite e expõe check_edges(), retornando True quando o alien toca a borda direita (self.rect.right >= screen_rect.right) ou esquerda (self.rect.left <= 0).

Para montar a frota em _create_fleet(), criamos um alien modelo para ler alien.rect.size (alien_width, alien_height) e usamos dois laços while aninhados: o externo avança current_y até screen_height - 3 * alien_height, enquanto o interno preenche current_x até screen_width - 2 * alien_width. A cada frame, _check_fleet_edges() verifica se algum membro encostou na borda; se sim, _change_fleet_direction() desce toda a frota em fleet_drop_speed e inverte fleet_direction *= -1.

Python · _create_fleet() e _change_fleet_direction()
def _create_fleet(self):
    alien = Alien(self)
    alien_width, alien_height = alien.rect.size
    current_x, current_y = alien_width, alien_height
    while current_y < (self.settings.screen_height - 3 * alien_height):
        while current_x < (self.settings.screen_width - 2 * alien_width):
            self._create_alien(current_x, current_y)
            current_x += 2 * alien_width
        current_x = alien_width
        current_y += 2 * alien_height

def _change_fleet_direction(self):
    for alien in self.aliens.sprites():
        alien.rect.y += self.settings.fleet_drop_speed
    self.settings.fleet_direction *= -1
Salvo localmente.
OFICINA 04 · CAPÍTULO 13

4. Colisões de Sprites, GameStats e Fim de Jogo

O Pygame resolve colisões entre grupos inteiros com uma única chamada: pygame.sprite.groupcollide(self.bullets, self.aliens, True, True) compara retângulos e remove ambos os sprites atingidos. Quando not self.aliens: se torna verdadeiro, esvaziamos os projéteis restantes, aumentamos a velocidade e geramos uma nova frota.

Já a derrota de uma vida ocorre quando pygame.sprite.spritecollideany(self.ship, self.aliens) retorna um alien ou quando _check_aliens_bottom() detecta alien.rect.bottom >= self.settings.screen_height. Em _ship_hit(), GameStats decrementa ships_left; se ainda houver naves, o jogo limpa a tela, centraliza a nave e pausa sleep(0.5). Ao zerar ships_left, self.game_active = False encerra a partida.

Python · Detecção de colisões e _ship_hit()
def _check_bullet_alien_collisions(self):
    collisions = pygame.sprite.groupcollide(
        self.bullets, self.aliens, True, True
    )
    if not self.aliens:
        self.bullets.empty()
        self._create_fleet()
        self.settings.increase_speed()

def _ship_hit(self):
    if self.stats.ships_left > 0:
        self.stats.ships_left -= 1
        self.bullets.empty()
        self.aliens.empty()
        self._create_fleet()
        self.ship.center_ship()
        sleep(0.5)
    else:
        self.game_active = False
        pygame.mouse.set_visible(True)
Salvo localmente.
OFICINA 05 · CAPÍTULO 14

5. Botão Play, Mouse e Escalonamento Dinâmico

No Capítulo 14, o jogo passa a iniciar inativo (self.game_active = False) aguardando o clique no botão Play. A classe Button em button.py renderiza texto como imagem usando pygame.font.SysFont(None, 48) dentro de _prep_msg(). Ao detectar pygame.MOUSEBUTTONDOWN, verificamos self.play_button.rect.collidepoint(mouse_pos) and not self.game_active para impedir cliques acidentais na área do botão invisível durante o combate — e ocultamos o cursor com pygame.mouse.set_visible(False).

Para criar progressão de dificuldade, dividimos Settings entre configurações estáticas (no __init__) e variáveis que reiniciam a cada partida em initialize_dynamic_settings(). A cada frota destruída, increase_speed() multiplica ship_speed, bullet_speed e alien_speed por speedup_scale = 1.1, além de multiplicar alien_points por score_scale = 1.5.

Python · Configurações dinâmicas em settings.py
def initialize_dynamic_settings(self):
    self.ship_speed = 1.5
    self.bullet_speed = 2.5
    self.alien_speed = 1.0
    self.fleet_direction = 1
    self.alien_points = 50

def increase_speed(self):
    self.ship_speed *= self.speedup_scale
    self.bullet_speed *= self.speedup_scale
    self.alien_speed *= self.speedup_scale
    self.alien_points = int(self.alien_points * self.score_scale)
Salvo localmente.
OFICINA 06 · CAPÍTULO 14

6. Scoreboard, Arredondamento e Vidas Restantes

A classe Scoreboard em scoreboard.py transforma estatísticas numéricas em elementos gráficos: pontuação atual no canto superior direito, recorde (high_score, inicializado em GameStats.__init__ para nunca zerar entre partidas) centralizado no topo, nível logo abaixo do placar e vidas restantes no canto superior esquerdo como um Group() de miniaturas Ship.

Dois detalhes elegantes do Capítulo 14: primeiro, round(self.stats.score, -1) arredonda o placar para o múltiplo de 10 mais próximo e f"{rounded_score:,}" formata milhares. Segundo, ao pontuar colisões, iteramos sobre collisions.values() multiplicando alien_points * len(aliens) para garantir que dois aliens atingidos no mesmo frame contem integralmente.

Python · Pontuação de colisões e Scoreboard.prep_score()
if collisions:
    for aliens in collisions.values():
        self.stats.score += self.settings.alien_points * len(aliens)
    self.sb.prep_score()
    self.sb.check_high_score()

def prep_score(self):
    rounded_score = round(self.stats.score, -1)
    score_str = f"{rounded_score:,}"
    self.score_image = self.font.render(
        score_str, True, self.text_color, self.settings.bg_color
    )
Salvo localmente.
LABORATÓRIO INTERATIVO · CAPÍTULO 14

Simulador de Dificuldade & Pontuação

Ajuste a onda atual, o fator de aceleração (speedup_scale) e o multiplicador de pontos (score_scale) para prever o ritmo real da classe Settings.

Alien Speed1.00 px
Bullet Speed2.50 px
Alien Points50 pts
Nível inicial equilibrado: velocidade base de 1.00 px/frame e 50 pontos por alienígena abatido.
VERIFICAÇÃO DE DOMÍNIO · 4 QUESTÕES

Quiz de Consolidação Lógica

Teste seus conhecimentos sobre Pygame, sprites, colisões e Scoreboard.

1. Por que _update_bullets() percorre self.bullets.copy() ao remover projéteis fora da tela?

2. O que faz pygame.sprite.groupcollide(self.bullets, self.aliens, True, True)?

3. Por que high_score deve ser inicializado em GameStats.__init__() em vez de reset_stats()?

4. Qual é o efeito de round(self.stats.score, -1) na classe Scoreboard?

CONCLUSÃO DA ETAPA 4

Checklist de Prontidão & Exportação

Marque cada marco concluído; seu progresso fica salvo no navegador.

PRÓXIMA PARADA · ETAPA 5 DE 5

Projetos de Aplicação Real — Dados e Web

Transforme dados em gráficos com Matplotlib e Plotly, consuma APIs reais e construa uma aplicação web completa com Django nos Capítulos 15 a 20.

Ir para Etapa 5 →