Organizando o Código - Refatorando Nave e Tiros

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

No capítulo anterior, vimos como:

Até aqui o nosso jogo funciona, mas o game loop começou a crescer. Conforme adicionamos novas mecânicas, encontrar uma parte específica do código pode ficar mais difícil.

Neste capítulo você aprenderá a:

Inicialmente vamos focar em organizar a nave e os tiros e entender como o processo funciona. Depois podemos terminar refatorando os meteoros, colisões e renderização.

Por que Separar Responsabilidades?


Em qualquer projeto, o desenvolvedor reserva um tempo para organizar o código. Essa prática facilita a manutenção e a adição de novas funcionalidades. Esse processo é chamado de refatoração.

Você pode perceber que começamos a separar responsabilidades do código. As funções que criamos para os tiros e os meteoros já fazem parte dessa organização.

Vamos fazer a mesma coisa para o resto do código. Para isso, seguimos os passos abaixo:

Movimentação da Nave


Vamos começar pela movimentação da nave, este é o trecho responsável por essa ação:

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"]

Esse trecho só faz uma coisa: mover a nave. Por isso, mover_nave() é um nome direto e descritivo.

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

Note que a colisão é posicionada na nave assim que ela se move.

Vamos aplicar o processo para os demais trechos do game loop. Não vamos mais deixar a lógica solta no loop principal. Em vez disso, vamos agrupá-la em funções com nomes descritivos.

Limite de Posição


Para a lógica que mantém a nave na tela, vamos criar uma função chamada manter_nave_na_tela() e adicionar a lógica dos limites.

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

Substituindo as lógicas pelas funções no game loop, temos o seguinte resultado:

while running:
    mover_nave()
    manter_nave_na_tela()

Sistema de Tiros


O sistema de tiros pode ter uma função chamada atirar().

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

Assim como em mover_nave(), usamos pygame.key.get_pressed() para checar as teclas. Cada função só olha para as teclas que interessam a ela.

Movimentação dos Tiros


Para o movimento dos tiros podemos criar uma função chamada mover_tiros().

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

Perceba que mover_tiros() só cuida da posição e da colisão. O desenho dos tiros ainda está solto dentro do game loop.

Com isso, o game loop começa a ficar mais limpo e legível:

while running:
    mover_nave()
    manter_nave_na_tela()

    atirar()
    mover_tiros()

    ...

O que Aprendemos até Agora?


Até aqui você começou a organizar o código separando responsabilidades com funções distintas.

Neste capítulo você:


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

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)

    mover_nave()
    manter_nave_na_tela()

    atirar()
    mover_tiros()

    janela.fill("black")

    janela.blit(nave["imagem"], (nave["x"], nave["y"]))

    for tiro in tiros[:]:
        for meteoro in meteoros[:]:
            if tiro["colisao"].colliderect(meteoro["colisao"]):
                meteoros.remove(meteoro)
                tiros.remove(tiro)

        janela.blit(tiro["imagem"], (tiro["x"], tiro["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: Terminando a Refatoração


A metade de cima do game loop já está organizada — repare como ficou mais fácil de ler. A parte de baixo, porém, ainda é a mesma bagunça de sempre: meteoros, colisões e desenho, tudo solto. No próximo capítulo, fechamos essa reforma.