Atirando com a Nave - Destruindo Meteoros
No capítulo anterior adicionamos interação entre os elementos do jogo, fazendo com que o jogo fosse encerrado caso a nave seja atingida por um meteoro.
Até agora a nave não consegue se defender dos meteoros, podendo apenas desviar deles.
Vamos fazer com que a nave consiga se defender permitindo que ela atire e destrua os meteoros.
Neste capítulo você aprenderá a:
- Criar tiros
- Fazer a nave atirar
- Destruir os meteoros
Criando os Tiros
Com as imagens baixadas no segundo capítulo. Escolha um tiro e salve na pasta
imgdo seu projeto.
Vamos usar os mesmos conceitos que vimos no capítulo que geramos os meteoros automaticamente.
Primeiro criamos uma função que retorna um dicionário com as informações do tiro.
def criar_tiro(x, y): img_tiro = pygame.image.load("img/tiro.png") tiro = { "imagem": img_tiro, "x": x, "y": y, "velocidade": 10 } return tiro
Diferente da função de criação dos meteoros, criar_tiro recebe dois parâmetros: x e y. Eles representam a posição onde o tiro será criado.
Na função criamos um dicionário com as informações do tiro, em seguida esse dicionário é retornado.
Agora podemos fazer com que a nave consiga atirar nos meteoros.
Fazendo a Nave Atirar
Primeiro precisamos criar uma lista que armazene os tiros criados, assim como fizemos para os meteoros.
tiros = []
Agora vamos criar os tiros caso a tecla espaço tenha sido pressionada. Isso deve ser feito a cada frame.
if teclas[pygame.K_SPACE]: tiro = criar_tiro(nave["x"] + nave["imagem"].get_width() // 2, nave["y"]) tiros.append(tiro)
Criamos o tiro, posicionamos no centro da nave, com a mesma lógica usada para centralizar as hitboxes e adicionamos na lista de tiros.
Agora vamos atualizar cada tiro da lista fazendo com que eles se movam para cima.
for tiro in tiros: tiro["y"] -= tiro["velocidade"] janela.blit(tiro["imagem"], (tiro["x"], tiro["y"]))
Executando o jogo, ao pressionar espaço a nave passa a atirar.
A nave já consegue atirar, mas não existe um intervalo entre cada tiro, criando uma linha de vários tiros.
Adicionando Intervalo Entre os Tiros
Criamos um novo tiro a cada frame que a tecla espaço está pressionada. Isso faz com que vários tiros sejam criados um atrás do outro muito rápido. Criando a linha de tiros.
Para resolver isso, vamos controlar a frequência dos tiros. A ideia: só criar um novo tiro após um número de frames ter passado.
Vamos adicionar as informações necessária para esse intervalo na estrutura da nave.
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 }
O valor atraso_tiro corresponde a quantidade de frames que deve se passar até a nave atirar. Definimos o seu valor para 30 frames.
Como nosso jogo roda a 60 frames por segundo, um atraso de 30 frames equivale a meio segundo entre cada disparo.
Agora contador_tiro será incrementado em todo frame, quando o seu valor for maior ou igual a quantidade de frames necessárias criamos um novo tiro.
A cada vez que a nave atira o valor de contador_tiro é zerado.
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
Com isso a nave passa a atirar de maneira mais controlada.
Adicionando Interação com os Meteoros
A nave já consegue atirar, mas os tiros não possuem interação com os meteoros. Vamos fazer com que o jogo entenda quando um tiro atingiu um meteoro.
Para isso precisamos adicionar a colisão no dicionário do tiro que a função criar_tiro() retorna.
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
Da mesma forma que os outros elementos, a hitbox do tiro também é um pouco menor que sua imagem.
Com a hitbox criada, vamos fazer o meteoro ser destruído ao ser atingido.
Para detectar e processar a colisão, seguimos três passos:
- Posicionamos a hitbox do tiro no centro da imagem
- Percorremos a lista de meteoros em busca de colisões
- Ao detectar uma colisão, removemos tanto o tiro quanto o meteoro
Com a base do sistema de colisão que montamos para a nave já estabelecida, aplicamos a mesma função colliderect(), desta vez para verificar se um tiro tocou um meteoro.
Como vamos remover itens das listas durante a iteração, usamos
tiros[:]emeteoros[:]. O[:]cria uma cópia temporária da lista para o loop percorrer. Assim a lista original pode ser modificada com segurança.
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 for meteoro in meteoros[:]: if tiro["colisao"].colliderect(meteoro["colisao"]): meteoros.remove(meteoro) tiros.remove(tiro) break janela.blit(tiro["imagem"], (tiro["x"], tiro["y"]))
Executando o jogo a nave conseguirá destruir os meteoros com apenas um tiro.
O que Aprendemos até Agora?
Adicionamos uma forma da nave se proteger dos meteoros fazendo com que ela atirasse, destruindo os meteoros.
Neste capítulo você:
- Criou um sistema de tiro
- Controlou a frequência dos tiros
- Destruiu meteoros
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 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) 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["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 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 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 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: Melhorando o Código
Neste momento, o game loop já tem mais de 60 linhas. Ainda falta adicionar pontuação, vidas ou tela de fim de jogo. Antes de continuar adicionando funcionalidades, precisamos reorganizar o código.
No próximo capítulo, vamos separar cada responsabilidade em funções dedicadas, tornando o projeto escalável para o que vem a seguir.