Organizando o Código - Terminando a Refatoração
No capítulo anterior, aplicamos o método de identificar a ação, isolar em uma função e substituir a chamada no game loop — primeiro a nave, depois os tiros. Agora fechamos o ciclo aplicando essa mesma lógica aos meteoros, colisões e renderização.
Vamos dar continuidade à refatoração, começando pela movimentação dos meteoros.
Movimentação dos Meteoros
Atualmente temos a movimentação e a colisão soltas dentro do game loop.
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
Observe que esse trecho possui duas responsabilidades:
- mover os meteoros;
- verificar a colisão;
Vamos separar essas responsabilidades começando apenas pelo movimento. Para isso criamos uma função chamada mover_meteoros() e movemos apenas o código responsável por atualizar a posição dos meteoros.
def mover_meteoros(): for meteoro in meteoros: meteoro["y"] += meteoro["velocidade"] meteoro["rotacao"] += meteoro["dir_rotacao"] meteoro["colisao"].centerx = meteoro["x"] meteoro["colisao"].centery = meteoro["y"]
Agora basta substituir esse trecho pela chamada da função.
while running: mover_nave() manter_nave_na_tela() atirar() mover_tiros() mover_meteoros() ...
O padrão se repete: o game loop diz o quê acontece. A função guarda como aquilo acontece.
Detecção de Colisões
Depois de separar a movimentação dos elementos e o sistema de tiros, vamos separar as colisões que no momento estão soltas em trechos diferentes no game loop.
Temos duas colisões para organizar: a dos tiros contra os meteoros, e a da nave contra os meteoros. Vamos separá-las uma de cada vez.
Tiro x Meteoro
Esse é o trecho responsável por verificar a colisão dos tiros com os meteoros:
for tiro in tiros[:]: for meteoro in meteoros[:]: if tiro["colisao"].colliderect(meteoro["colisao"]): meteoros.remove(meteoro) tiros.remove(tiro)
Para os tiros podemos criar a função verificar_colisao_tiros() e adicionar o trecho responsável pela sua colisão.
def verificar_colisao_tiros(): for tiro in tiros[:]: for meteoro in meteoros[:]: if tiro["colisao"].colliderect(meteoro["colisao"]): meteoros.remove(meteoro) tiros.remove(tiro)
💡
Repare que a cópia da lista com
[:]volta a aparecer aqui — mesma estratégia do Capítulo 7, necessária porque estamos removendo itens durante o laço.
Em seguida substituímos o trecho no game loop, pela chamada da função.
while running: mover_nave() manter_nave_na_tela() atirar() mover_tiros() mover_meteoros() verificar_colisao_tiros()
Nave x Meteoro
Para a colisão entre a nave e os meteoros, temos o seguinte trecho:
for meteoro in meteoros: if meteoro["colisao"].colliderect(nave["colisao"]): print("Fim de jogo!") rodando = False
Observe que ao ocorrer uma colisão ele encerra o jogo alterando o valor de rodando para False.
Diferente dos outros trechos, essa função não apenas age — ela decide. A colisão da nave com o meteoro precisa comunicar ao game loop se o jogo deve encerrar.
Para isso, vamos criar a função verificar_colisao_nave(). Ela deve retornar True caso o meteoro tenha atingido a nave.
💡
Retomando o mesmo recurso que usamos no Capítulo 5 para controlar a chuva de meteoros, aqui o
returntambém serve para levar uma resposta de volta — só que agora essa resposta decide se o jogo continua ou não.
def verificar_colisao_nave(): for meteoro in meteoros: if meteoro["colisao"].colliderect(nave["colisao"]): print("Fim de jogo!") return True return False
No game loop, substituímos o trecho por uma condição que encerra o jogo caso a colisão entre a nave e o meteoro ocorra.
while running: mover_nave() manter_nave_na_tela() atirar() mover_tiros() mover_meteoros() verificar_colisao_tiros() if verificar_colisao_nave(): rodando = False
Com isso toda a parte da atualização dos elementos está organizada. Para finalizar vamos separar as funções que desenham os elementos do jogo.
Desenhando a Nave
Começando pela nave, esse é o trecho responsável por desenhá-la na tela.
janela.blit(nave["imagem"], (nave["x"], nave["y"]))
Vamos criar uma função chamada desenhar_nave() e adicionar esse trecho dentro dela.
def desenhar_nave(): janela.blit(nave["imagem"], (nave["x"], nave["y"]))
Em seguida substituímos o trecho pela chamada da função no game loop.
while running: mover_nave() manter_nave_na_tela() atirar() mover_tiros() mover_meteoros() verificar_colisao_tiros() if verificar_colisao_nave(): rodando = False display.fill("black") desenhar_nave()
Desenhando os Tiros
Para os tiros, temos o seguinte trecho responsável por desenhá-los.
for tiro in tiros: janela.blit(tiro["imagem"], (tiro["x"], tiro["y"]))
Vamos criar uma função chamada desenhar_tiros() e adicionar esse trecho a ela.
def desenhar_tiros(): for tiro in tiros: janela.blit(tiro["imagem"], (tiro["x"], tiro["y"]))
Depois substituímos pela chamada da função do game loop.
while running: mover_nave() manter_nave_na_tela() atirar() mover_tiros() mover_meteoros() verificar_colisao_tiros() if verificar_colisao_nave(): rodando = False display.fill("black") desenhar_nave() desenhar_tiros()
Desenhando os Meteoros
A lógica de desenho dos meteoros é parecida com a dos tiros. A única diferença é que a imagem do meteoro precisa ser rotacionada antes de aparecer na tela.
Esse é o trecho responsável por desenhá-los:
for meteoro in meteoros: 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)
Vamos criar uma função chamada desenhar_meteoros() e adicionar o trecho.
def desenhar_meteoros(): for meteoro in meteoros: 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)
Em seguida substituímos pela chamada da função no game loop.
while running: mover_nave() manter_nave_na_tela() atirar() mover_tiros() mover_meteoros() verificar_colisao_tiros() if verificar_colisao_nave(): rodando = False display.fill("black") desenhar_nave() desenhar_tiros() desenhar_meteoros()
O que Aprendemos até Agora?
Até aqui você organizou o código usando funções com responsabilidades únicas e simplificou o game loop, deixando o projeto mais legível.
Neste capítulo você:
- Criou uma função para movimentar os meteoros
- Separou as colisões com efeitos diferentes
- Organizou a renderização dos elementos
Próximo Passo: Adicionando Pontuação
Agora que temos o código organizado, podemos adicionar uma nova mecânica, seguindo o mesmo padrão já estabelecido no projeto.
O game loop virou uma lista de chamada de função — dá para ler o fluxo inteiro do jogo sem entrar em nenhuma delas.
Repare que destruir um meteoro e desviar dele dão exatamente o mesmo resultado — nada. No próximo capítulo resolvemos isso com um sistema de pontuaçã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)), "atraso_tiro": 30, "contador_tiro": 0 } 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), (70, 50)), } return meteoro def criar_tiro(x, y): img_tiro = pygame.image.load("img/tiro.png") tiro = { "imagem": img_tiro, "x": x, "y": y, "velocidade": 10, "colisao": pygame.Rect((0, 0), (9, 40)) } return tiro def mover_nave(): 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"] nave["colisao"].centerx = nave["x"] + nave["imagem"].get_width() // 2 nave["colisao"].centery = nave["y"] + nave["imagem"].get_height() // 2 def manter_nave_na_tela(): 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() def atirar(): teclas = pygame.key.get_pressed() nave["contador_tiro"] += 1 if teclas[pygame.K_SPACE]: if nave["contador_tiro"] >= nave["atraso_tiro"]: tiro = criar_tiro(nave["x"] + nave["imagem"].get_width() // 2, nave["y"]) tiros.append(tiro) nave["contador_tiro"] = 0 def mover_tiros(): for tiro in tiros: tiro["y"] -= tiro["velocidade"] tiro["colisao"].centerx = tiro["x"] + tiro["imagem"].get_width() // 2 tiro["colisao"].centery = tiro["y"] + tiro["imagem"].get_height() // 2 def mover_meteoros(): for meteoro in meteoros: meteoro["y"] += meteoro["velocidade"] meteoro["rotacao"] += meteoro["dir_rotacao"] meteoro["colisao"].centerx = meteoro["x"] meteoro["colisao"].centery = meteoro["y"] def verificar_colisao_tiros(): for tiro in tiros[:]: for meteoro in meteoros[:]: if tiro["colisao"].colliderect(meteoro["colisao"]): meteoros.remove(meteoro) tiros.remove(tiro) def verificar_colisao_nave(): for meteoro in meteoros: if meteoro["colisao"].colliderect(nave["colisao"]): print("Fim de jogo!") return True return False def desenhar_nave(): janela.blit(nave["imagem"], (nave["x"], nave["y"])) def desenhar_tiros(): for tiro in tiros: janela.blit(tiro["imagem"], (tiro["x"], tiro["y"])) def desenhar_meteoros(): for meteoro in meteoros: 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) meteoros = [] tiros = [] 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) ## Nave ## mover_nave() manter_nave_na_tela() ## Tiros ## atirar() mover_tiros() ## Meteoros ## mover_meteoros() ## Colisões ## verificar_colisao_tiros() if verificar_colisao_nave(): rodando = False ## Renderização ## janela.fill("black") desenhar_nave() desenhar_tiros() desenhar_meteoros() pygame.display.flip() clock.tick(60) pygame.quit()