Organizando o Código - Terminando a Refatoração

30/04/2026
iniciante pygame python 2d guia série

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:

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 return també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ê:

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()