Organizando o Código - Refatorando Nave e Tiros
No capítulo anterior, vimos como:
- Fazer a nave atirar
- Controlar intervalo entre os disparos
- Destruir meteoros ao serem atingidos.
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:
- Organizar o game loop em funções menores
- Separar responsabilidades dentro do código
- Refatorar um projeto sem alterar seu funcionamento
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:
- Identificar a ação
- Criar uma função com um nome que a descreva
- Substituir o código pela função
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ê:
- Separou trecho de códigos usando funções
- Simplificou o game loop
- Tornou o código mais legível ## Código Completo até Aqui
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.