I'm making a classic game on pygame , a fleet of aliens descends on the screen and the hero's ship needs to shoot the aliens down as the fleet moves sideways across the screen and goes down the screen. I'm using the pygame.sprite.groupcollide(ammunition, aliens, True, True) function. In the tests I did, I notice that if I hit an alien with a shot, when the fleet reaches the edge of the screen without an alien that was destroyed, the entire fleet runs down from the screen as if it were a bug. If I don't destroy any aliens, the fleet moves correctly, from right to left and then it goes down one level on the screen and so on until it disappears at the bottom of the screen. If you hit an alien with a shot, there is a problem with the entire fleet reaching the edge of the screen and descending all at once.
Code:
invasao_alien.py
import pygame
from pygame.sprite import Group
from configuracoes import Configuracoes #módulo configurações
from nave import Nave #módulo nave (herói)
import jogo_funcoes as jf #módulo funções do jogo com aliás jf
def run_game():
pygame.init() #Inicializa o jogo e cria um objeto para a tela
ai_configuracoes = Configuracoes() #objeto ai_configuracoes da Classe Configuracoes
tela = pygame.display.set_mode ((ai_configuracoes.tela_width, ai_configuracoes.tela_height))
pygame.display.set_caption("Space War")
nave_espacial = Nave(ai_configuracoes, tela) #objeto nave_espacial da classe Nave
municoes = Group() #Cria um grupo no qual serão armazenados as munições
aliens = Group() #Cria um grupo vazio para armazenar os aliens do jogo
jf.cria_frota(ai_configuracoes, tela, nave_espacial, aliens) #Cria uma frota de aliens (usa configurações, tela e um grupo vazio de aliens)
while True: #Inicia o laço principal do jogo
jf.check_eventos(ai_configuracoes, tela, nave_espacial, municoes) #checa os eventos
nave_espacial.update_nave() #posicionamento da nave
jf.update_municoes(ai_configuracoes, tela, nave_espacial, aliens, municoes) #verifica as municoes na tela ,se houve disparo
jf.update_aliens(ai_configuracoes, aliens)
jf.update_tela(ai_configuracoes, tela, nave_espacial, aliens, municoes) #update da tela
run_game()
jogo_funcoes.py
"""Módulo que armazena as funções do jogo"""
import sys
import pygame
from municao import Municao
from alien import Alien
def check_keydown_events(evento, ai_configuracoes, tela, nave_espacial, municoes):
"""Responde a pressionamento de teclas"""
if evento.key == pygame.K_RIGHT:
nave_espacial.move_direita = True
elif evento.key == pygame.K_LEFT:
nave_espacial.move_esquerda = True
elif evento.key == pygame.K_SPACE: #Esta condição cria um novo projétil e adiciona ao grupo de projéteis
tiro_municao(ai_configuracoes, tela, nave_espacial, municoes)
elif evento.key == pygame.K_q:
sys.exit()
def check_eventos(ai_configuracoes, tela, nave_espacial, municoes):
"""Responde a eventos de pressionamento de teclas e do mouse"""
for evento in pygame.event.get(): #pegar cada evento
if evento.type == pygame.QUIT: #caso seja solicitado a saída
sys.exit()
elif evento.type == pygame.KEYDOWN: #pressionamento de tecla
check_keydown_events(evento, ai_configuracoes, tela, nave_espacial, municoes)
elif evento.type == pygame.KEYUP: #soltura de tecla
check_keyup_events(evento, nave_espacial)
def check_keyup_events(evento, nave_espacial):
"""Responde a solturas de tecla"""
if evento.key == pygame.K_RIGHT:
nave_espacial.move_direita = False
elif evento.key == pygame.K_LEFT:
nave_espacial.move_esquerda = False
elif evento.key == pygame.K_q:
sys.exit()
def update_tela(ai_configuracoes, tela, nave_espacial, aliens, municoes):
"""Atualiza as imagens na tela e alterna para a nova tela"""
tela.fill(ai_configuracoes.bg_color)
for municao in municoes.sprites():
municao.desenha_projetil()
nave_espacial.blitme()
aliens.draw(tela) #desenha cada alienigena do grupo na tela
pygame.display.flip()
def update_municoes(ai_configuracoes, tela, nave_espacial, aliens, municoes):
"""Atualiza as posicoes dos projéteis antigos, se o projétil chega ao fim da tela é removido"""
municoes.update()
for municao in municoes.copy():
if municao.rect.bottom <= 0: #verifica se o projétil chegou ao fim da tela
municoes.remove(municao)
check_municao_alien_colisoes(ai_configuracoes, tela, nave_espacial, municoes, aliens)
def check_municao_alien_colisoes(ai_configuracoes, tela, nave_espacial, municoes, aliens):
"""Responde a colisões entre projéteis e alienígenas."""
colisoes = pygame.sprite.groupcollide(municoes, aliens, True, True) # verifica se algum projétil atingiu os alienígenas , em caso afirmativo se livra do projétil e do alien
if len(aliens) == 0: #Destrói os projéteis existentes e cria uma nova frota
municoes.empty() #remove todos os sprites de munições
cria_frota(ai_configuracoes, tela, nave_espacial, aliens)
def tiro_municao(ai_configuracoes, tela, nave_espacial, municoes):
"""Dispara um projétil se o limite ainda não foi lançado"""
if len(municoes) < ai_configuracoes.municoes_permitida: #Caso não tenha número máximo de munições na tela(3) , add munição
nova_municao = Municao(ai_configuracoes, tela, nave_espacial)
municoes.add(nova_municao) #adicionando munição ao grupo municoes (criado no arquivo principal)
def criar_alien(ai_configuracoes, tela, aliens, qtde_aliens, linha):
alien = Alien(ai_configuracoes, tela) #Cria uma nave alien
alien_width = alien.rect.width
alien.x = alien_width + 2 * alien_width * qtde_aliens
alien.rect.x = alien.x
alien.rect.y = alien.rect.height + 2 * alien.rect.height * linha
aliens.add(alien)
def get_numero_aliens_x(ai_configuracoes, alien_largura ):
"""Determina o número de alienígenas que cabem em uma linha"""
espaco_livre_x = ai_configuracoes.tela_width - 2 * alien_largura
numero_aliens_x = int(espaco_livre_x / (2 * alien_largura ))
return numero_aliens_x
def get_numero_linhas(ai_configuracoes, nave_espacial, alien):
"""Determina o número de linhas com alinígenas que cabem na tela"""
espaco_livre_y = (ai_configuracoes.tela_height - (3 * alien.rect.height) - nave_espacial.rect.height)
numero_linhas = int(espaco_livre_y / (2 * alien.rect.height))
return numero_linhas
def cria_frota(ai_configuracoes, tela, nave_espacial, aliens):
"""Cria uma frota completa de aliens"""
alien = Alien(ai_configuracoes, tela) #Cria um alienígena
alien_largura = alien.rect.width
numero_aliens_x = get_numero_aliens_x(ai_configuracoes, alien_largura) #Calcula a qtde de alienígenas que cabem numa linha
numero_linhas = get_numero_linhas(ai_configuracoes, nave_espacial, alien)
for linha in range(numero_linhas): #laço para preencher as linhas da tela(eixo y) com aliens
for qtde_aliens in range(numero_aliens_x): #laço para preencher uma linha (eixo x) com aliens
criar_alien(ai_configuracoes, tela, aliens, qtde_aliens, linha)
aliens.add(alien) #Adiciona o alienígena criado ao Group()
def check_frota_bordas(ai_configuracoes, aliens):
"""Responde se algum alien chegou na borda da tela"""
for alien in aliens.sprites():
if alien.check_bordas():
trocar_direcao_frota(ai_configuracoes, aliens)
break
def trocar_direcao_frota(ai_configuracoes, aliens):
"""Faz a frota inteira descer e mudar a direcao"""
for alien in aliens.sprites():
alien.rect.y += ai_configuracoes.frota_drop_speed #horda desce na tela
ai_configuracoes.frota_direcao *= -1 #muda a direção do alien
def update_aliens(ai_configuracoes, aliens):
"""Verifica se a frota está numa das bordas e então atualiza as posições dos aliens"""
check_frota_bordas(ai_configuracoes, aliens)
aliens.update() # Usando o método update() no Grupo aliens , fará o método update() de cada alienígena ser chamado automaticamente
municao.py
import pygame
from pygame.sprite import Sprite
class Municao(Sprite):
"""Uma classe que administra projéteis disparados pela espaçonave"""
def __init__(self, ai_configuracoes, tela, nave_espacial):
"""Cria um objeto para o projétil na posição atual da espaçonave"""
super(Municao, self).__init__() #herdando de Sprite
self.tela = tela
self.rect = pygame.Rect(0, 0, ai_configuracoes.municao_width, ai_configuracoes.municao_height) #Cria um projétil em (0,0) e define a posição correta
self.rect.centerx = nave_espacial.rect.centerx #rect do projétil igual ao da nave
self.rect.top = nave_espacial.rect.top #rect top do projétil igual ao topo da nave
self.y = float(self.rect.y) #Armazena a posição do projétil como um valor decimal na orientação y para ajustes de velocidade do mesmo
self.color = ai_configuracoes.municao_color
self.speed_factor = ai_configuracoes.municao_speed_factor
def update(self):
"""Move o projétil para cima na tela"""
self.y -= self.speed_factor #Dá velocidade ao projétil alterando sua posição no eixo y (subindo na tela)
self.rect.y = self.y #Atualiza a posição de rect
def desenha_projetil(self):
"""Desenha o projétil na tela"""
pygame.draw.rect(self.tela, self.color, self.rect)
alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""Uma classe que representa um único alienígena da frota."""
def __init__(self, ai_configuracoes, tela):
"""Inicializa o alienígena e define sua posição inicial."""
super(Alien, self).__init__()
self.tela = tela
self.ai_configuracoes = ai_configuracoes
self.image = pygame.image.load('/home/marco_user/Documentos/Projetos/Python_ProjetoLivro/imagens/aliens-1.bmp')
self.rect = self.image.get_rect()
self.rect.x = self.rect.width #Inicia cada novo alienígena próximo à parte superior esquerda da tela
self.rect.y = self.rect.height
self.x = float(self.rect.x) #Armazena a posição exata do alienígena
def check_bordas(self):
"""Verifica se o alienígena chegou na borda da tela e devolve True"""
tela_rect = self.tela.get_rect()
if self.rect.right >= tela_rect.right: #SE frota alien na borda direita da tela
return True
elif self.rect.left <= 0: #SE frota alien na borda esquerda da tela
return True
def update(self):
"""Move o alienígena para a direita ou para a esquerda"""
self.x += (self.ai_configuracoes.alien_speed_factor * self.ai_configuracoes.frota_direcao) #Soma o fator de velocidade ao eixo x da imagem do alien
self.rect.x = self.x #Atualizo a posição do rect do alien
nave.py
class Nave():
def __init__(self, ai_configuracoes, tela):
"""Inicializa a espaçonave e define sua posição inicial."""
self.ai_configuracoes = ai_configuracoes
self.tela = tela
self.image = pygame.image.load('/home/marco_user/Documentos/Projetos/Python_ProjetoLivro/imagens/nave_nova1.bmp') #Carrega a imagem da espaçonave
self.rect = self.image.get_rect() #atributo da superfície - o Pygame trata elementos como retângulos , rects
self.tela_rect = tela.get_rect()
self.rect.centerx = self.tela_rect.centerx #Inicia cada nova espaçonave na parte inferior central da tela
self.center = float(self.rect.centerx) #Transforma self.rect.centerx em ponto flutuante para aceitar valores decimais
self.rect.bottom = self.tela_rect.bottom #Inicia cada nova espaçonave na parte inferior
self.move_direita = False #Flag de movimento da nave para a direita
self.move_esquerda = False #Flag de movimento da nave para a esquerda
def update_nave(self):
"""Atualiza a posição da espaçonave de acordo com a flag de movimento"""
if self.move_direita and (self.rect.right < self.tela_rect.right): #se flag True e se antes do FIM DIREITO da tela
self.center += self.ai_configuracoes.nave_speed_factor #utiliza o fator de 1.5 de movimento definido em Configurações
if self.move_esquerda and self.rect.left > 0: #se a flag para a esquerda for True e não for o FIM ESQUERDO da tela
self.center -= self.ai_configuracoes.nave_speed_factor #utiliza o fator de 1.5 de movimento
self.rect.centerx = self.center #Atualiza o objeto rect(que controla a posição da nave) de acordo com self.center
def blitme(self):
"""Desenha a espaçonave em sua posição atual"""
self.tela.blit(self.image, self.rect)
configuracoes.py
"""Módulo de Configurações"""
class Configuracoes():
"""Uma classe para armazenar todas as configurações da Invasão Alienígena"""
def __init__(self):
"""Inicializa as configurações do jogo"""
self.tela_width = 1359 #Configuração da tela , largura
self.tela_height = 700 #Configuracao da tela , altura
self.bg_color = (0, 0, 0) #Configuração da cor de fundo da tela
self.nave_speed_factor = 1.5 #Configurações da espaçonave para ajuste da velocidade
self.alien_speed_factor = 1 #Configuração da espaçonave alienígena para ajuste de velocidade
self.frota_drop_speed = 10 #Configuração da velocidade com que a frota desce na tela após chegar nas bordas da tela
self.frota_direcao = 1 #fleet_direction -1 representa direção para a esquerda da tela, 1 seria para a direita
self.municao_width = 1 #largura do projétil
self.municao_height = 15 #tamanho do projétil
self.municao_color = 247, 20, 43 #cor do projétil
self.municao_speed_factor = 1 #velocidade do projétil
self.municoes_permitida = 3 #atributo que limita a 3 tiros ao mesmo tempo na tela