Detectando Colisões - Adicionando Interação entre Elementos
No capítulo anterior alimentamos a lista de meteoros automaticamente pelo timer que configuramos. Nossa chuva de meteoros já tem vida própria.
Os meteoros caem, giram e se renovam sem que o jogador precise fazer nada. Só falta o jogo perceber quando um deles acertar a nave.
Neste capítulo, vamos fazer com que o jogo seja encerrado caso a nave seja atingida por um meteoro.
Ao longo deste capítulo você aprenderá a:
- Preparar caixas de colisão
- Ancorar colisão com os elementos
- Detectar colisões
Vamos começar configurando as colisões para a nave e os meteoros.
Configurando as Colisões
Para configurar as colisões corretamente, vamos usar o pygame.Rect que vimos no capítulo onde criamos a base dos meteoros.
O pygame.Rect vai além de posicionar imagens na tela. Ele também traz funções prontas para gerenciar colisões.
Adicionando Colisão para a Nave
Primeiro vamos configurar a colisão da nave. Para isso usamos o pygame.Rect para determinar sua area de colisão.
A área de colisão é chamada de hitbox no desenvolvimento de jogos. Ela não precisa ter o mesmo tamanho visual da imagem.
Na estrutura da nave vamos adicionar uma nova chave para armazenar sua colisão.
nave = { "imagem": img_nave, "x": janela.get_width() // 2 - img_nave.get_width() // 2, "y": janela.get_height() // 2 - img_nave.get_height() // 2, "velocidade": 5, "colisao": pygame.Rect((0, 0), (74, 33)) }
Criamos um novo Rect, sua posição inicialmente está zerada o que significa que ele está no canto superior esquerdo da janela.
Criamos a colisão com 74x33 pixels de tamanho, ele se baseia no espaço disponível entre as asas da nave.
Precisamos fazer com que a colisão sempre fique posicionada no centro da nave. Então vamos adicionar a lógica abaixo dentro do game loop.
nave["colisao"].centerx = nave["x"] + nave["imagem"].get_width() // 2 nave["colisao"].centery = nave["y"] + nave["imagem"].get_height() // 2
Somamos a posição da nave com metade de sua largura (ou altura). O resultado é o ponto central da nave. É nesse ponto que ancoraremos a área de colisão.

Agora podemos adicionar a mesma lógica para os meteoros.
Adicionando Colisões para os Meteoros
Primeiro vamos adicionar a colisão no dicionário que a função criar_meteoro() retorna.
def criar_meteoro(): meteoro_img = pygame.image.load("img/meteoro.png") meteoro = { "imagem": meteoro_img, "x": random.randint(0, janela.get_width()), "y": -100, "velocidade": 2, "rotacao": 0, "dir_rotacao": random.randint(-1, 1), "colisao": pygame.Rect((0, 0), (64, 50)) } return meteoro
Para os meteoros, também criamos uma hitbox um pouco menor que a imagem: 64x50 pixels.
Agora vamos centralizar a colisão com o meteoro. Isso deve ser feito dentro do loop onde atualizamos a lista de meteoros.
for meteoro in meteoros: meteoro["y"] += meteoro["velocidade"] meteoro["rotacao"] += meteoro["dir_rotacao"] meteoro["colisao"].centerx = meteoro["x"] meteoro["colisao"].centery = meteoro["y"] imagem_rotacionada = pygame.transform.rotate(meteoro["imagem"], meteoro["rotacao"]) retangulo_rotacionado = imagem_rotacionada.get_rect(center = (meteoro["x"], meteoro["y"])) janela.blit(imagem_rotacionada, retangulo_rotacionado)
Diferente da nave, os meteoros já são centralizado quando são rotacionados. Então apenas deixamos a colisão na mesma posição do meteoro.

Com as áreas de colisão criadas e posicionadas, temos tudo o que precisamos para detectar o impacto.
Detectando as Colisões
Vamos ver se um meteoro atingiu a nave. Com a base do pygame.Rect já estabelecida para posicionar e rotacionar os meteoros, podemos agora explorar outra de suas capacidades: a função colliderect().
Imagine que cada elemento do jogo carrega uma caixa invisível ao redor de si. A função
colliderect()verifica se a caixa de um elemento invadiu o espaço da caixa do outro. É como dois blocos físicos que não podem ocupar o mesmo lugar.
Dentro do loop de atualização dos meteoros, verificamos se a colisão do meteoro sobrepôs com a da nave. Caso isso aconteça, vamos encerrar o jogo.
for meteoro in meteoros: meteoro["y"] += meteoro["velocidade"] meteoro["rotacao"] += meteoro["dir_rotacao"] meteoro["colisao"].centerx = meteoro["x"] meteoro["colisao"].centery = meteoro["y"] if meteoro["colisao"].colliderect(nave["colisao"]): print("Fim de jogo!") rodando = False imagem_rotacionada = pygame.transform.rotate(meteoro["imagem"], meteoro["rotacao"]) retangulo_rotacionado = imagem_rotacionada.get_rect(center = (meteoro["x"], meteoro["y"])) janela.blit(imagem_rotacionada, retangulo_rotacionado)
Ao executar o jogo, quando um meteoro atingir a nave, a janela será fechada e a mensagem Fim de jogo! aparecerá no console.
O que Aprendemos até Agora?
Agora o jogo sabe o que fazer quando a nave for atingida por um meteoro.
Neste capítulo você:
- Criou colisões
- Posicionou as hitboxes
- Encerrou o jogo ao ocorrer uma colisão
Código Completo
import pygame import random pygame.init() janela = pygame.display.set_mode((800, 600)) clock = pygame.time.Clock() img_nave = pygame.image.load("img/nave.png") nave = { "imagem": img_nave, "x": janela.get_width() // 2 - img_nave.get_width() // 2, "y": janela.get_height() // 2 - img_nave.get_height() // 2, "velocidade": 5, "colisao": pygame.Rect((0, 0), (74, 33)) } def criar_meteoro(): img_meteoro = pygame.image.load("img/meteoro.png") meteoro = { "imagem": img_meteoro, "x": random.randint(0, janela.get_width()), "y": -100, "velocidade": 2, "rotacao": 0, "dir_rotacao": random.randint(-1, 1), "colisao": pygame.Rect((0, 0), (64, 50)) } return meteoro meteoros = [] criar_novo_meteoro = pygame.USEREVENT + 1 pygame.time.set_timer(criar_novo_meteoro, 1500) rodando = True while rodando: for evento in pygame.event.get(): if evento.type == pygame.QUIT: rodando = False if evento.type == criar_novo_meteoro: meteoro = criar_meteoro() meteoros.append(meteoro) teclas = pygame.key.get_pressed() if teclas[pygame.K_w]: nave["y"] -= nave["velocidade"] if teclas[pygame.K_s]: nave["y"] += nave["velocidade"] if teclas[pygame.K_a]: nave["x"] -= nave["velocidade"] if teclas[pygame.K_d]: nave["x"] += nave["velocidade"] if nave["x"] <= 0: nave["x"] = 0 elif nave["x"] + nave["imagem"].get_width() >= janela.get_width(): nave["x"] = janela.get_width() - nave["imagem"].get_width() if nave["y"] <= 0: nave["y"] = 0 elif nave["y"] + nave["imagem"].get_height() >= janela.get_height(): nave["y"] = janela.get_height() - nave["imagem"].get_height() nave["colisao"].centerx = nave["x"] + nave["imagem"].get_width() // 2 nave["colisao"].centery = nave["y"] + nave["imagem"].get_height() // 2 janela.fill("black") janela.blit(nave["imagem"], (nave["x"], nave["y"])) for meteoro in meteoros: meteoro["y"] += meteoro["velocidade"] meteoro["rotacao"] += meteoro["dir_rotacao"] meteoro["colisao"].centerx = meteoro["x"] meteoro["colisao"].centery = meteoro["y"] if meteoro["colisao"].colliderect(nave["colisao"]): print("Fim de Jogo!") rodando = False imagem_rotacionada = pygame.transform.rotate(meteoro["imagem"], meteoro["rotacao"]) retangulo_rotacionado = imagem_rotacionada.get_rect(center = (meteoro["x"], meteoro["y"])) janela.blit(imagem_rotacionada, retangulo_rotacionado) pygame.display.flip() clock.tick(60) pygame.quit()
Próximo Passo: Atirando com a Nave
A nave já sente os impactos. Mas ainda não pode reagir.
No próximo capítulo vamos fazer a nave ganhar a capacidade de atirar e destruir os meteoros antes que eles cheguem.