pratica

Usando Listas para Inimigos e Tiros

Aprenda sobre usando listas para inimigos e tiros

50 min
Aula 2 de 5

🚀 Usando Listas para Inimigos e Tiros em Pgzero

Olá, futuros desenvolvedores de jogos! 👋

No módulo anterior, aprendemos a movimentar nosso jogador e a criar um único inimigo ou tiro. Mas e se quisermos ter muitos inimigos na tela ao mesmo tempo? Ou atirar várias balas? Criar uma variável para cada um (inimigo1, inimigo2, tiro1, tiro2...) seria um pesadelo! 😱

É aí que as listas entram em ação! As listas são como caixas organizadas onde podemos guardar muitos itens parecidos. Em Python, elas são super poderosas e nos ajudarão a gerenciar múltiplos objetos no nosso jogo de forma muito mais fácil e eficiente.

Nesta aula prática, vamos aprender a:

  • Criar e gerenciar listas de objetos (Actors).
  • Desenhar e mover múltiplos inimigos e tiros usando listas.
  • Detectar colisões entre o jogador, tiros e múltiplos inimigos.
  • Remover objetos das listas quando eles saem da tela ou são destruídos.

Prontos para levar seus jogos para o próximo nível? Vamos lá! 🎮


1. O Que São Listas em Python?

Imagine que você tem uma coleção de figurinhas. Em vez de espalhá-las por todo o quarto, você as guarda em um álbum. Uma lista em Python é como esse álbum: um lugar para guardar uma coleção de coisas.

# Uma lista de números
numeros = [1, 2, 3, 4, 5]
print(numeros) # Saída: [1, 2, 3, 4, 5]
 
# Uma lista de nomes de personagens
personagens = ["Mario", "Luigi", "Peach"]
print(personagens) # Saída: ['Mario', 'Luigi', 'Peach']
 
# Você pode acessar itens da lista usando um índice (a posição do item, começando do 0)
print(personagens[0]) # Saída: Mario (o primeiro item)
print(personagens[2]) # Saída: Peach (o terceiro item)
 
# Você pode adicionar itens a uma lista usando .append()
personagens.append("Yoshi")
print(personagens) # Saída: ['Mario', 'Luigi', 'Peach', 'Yoshi']
 
# Você pode remover itens de uma lista usando .remove() ou .pop()
personagens.remove("Luigi")
print(personagens) # Saída: ['Mario', 'Peach', 'Yoshi']
 
# Você pode percorrer todos os itens de uma lista usando um loop 'for'
for nome in personagens:
    print(f"Olá, {nome}!")
# Saída:
# Olá, Mario!
# Olá, Peach!
# Olá, Yoshi!

Em Pgzero, podemos ter listas de Actors! Isso é perfeito para inimigos, tiros, moedas, etc.


2. Gerenciando Múltiplos Inimigos 👾

Vamos começar com um exemplo prático. Primeiro, certifique-se de ter as imagens nave.png, inimigo.png, tiro.png e fundo.png na sua pasta images. Se não tiver, pode usar as imagens padrão do Pgzero ou criar as suas.

nave.png (player) inimigo.png (enemy) tiro.png (bullet) fundo.png (background)

2.1. Criando e Desenhando Múltiplos Inimigos

Vamos criar uma lista vazia para nossos inimigos e, em seguida, adicionar alguns Actors a ela.

import pgzrun
import random # Vamos precisar disso para posições aleatórias
 
WIDTH = 800
HEIGHT = 600
 
# Nosso jogador
player = Actor('nave')
player.pos = WIDTH // 2, HEIGHT - 50
 
# Uma lista vazia para guardar nossos inimigos
enemies = []
 
# Variáveis do jogo
score = 0
game_over = False
 
# --- FUNÇÕES DE INICIALIZAÇÃO E REINICIALIZAÇÃO ---
def init_game():
    global enemies, score, game_over
    player.pos = WIDTH // 2, HEIGHT - 50
    enemies = [] # Limpa a lista de inimigos para um novo jogo
    score = 0
    game_over = False
    
    # Adiciona 5 inimigos iniciais à nossa lista
    for i in range(5):
        # A posição x é aleatória, a y começa no topo
        enemies.append(Actor('inimigo', (random.randint(50, WIDTH - 50), random.randint(50, 200))))
 
# --- FUNÇÃO DRAW ---
def draw():
    screen.clear()
    screen.blit('fundo', (0, 0)) # Desenha o fundo
    
    player.draw() # Desenha o jogador
    
    # Percorre a lista de inimigos e desenha cada um
    for enemy in enemies:
        enemy.draw()
        
    screen.draw.text(f"Score: {score}", (10, 10), color="white", fontsize=30)
    
    if game_over:
        screen.draw.text("GAME OVER!", center=(WIDTH/2, HEIGHT/2), color="red", fontsize=60)
 
# --- FUNÇÃO UPDATE (MOVIMENTO E LÓGICA) ---
def update():
    global game_over # Precisamos da palavra-chave global para modificar game_over
    
    if game_over: # Se o jogo acabou, não faz mais nada
        return
 
    # Movimento do jogador (igual ao que já aprendemos)
    if keyboard.left:
        player.x -= 5
    if keyboard.right:
        player.x += 5
    player.x = max(20, min(WIDTH - 20, player.x)) # Mantém o jogador na tela
 
    # Movimento dos inimigos
    # Percorre a lista de inimigos e move cada um para baixo
    for enemy in enemies:
        enemy.y += 1 # Move o inimigo para baixo
        
        # Se um inimigo chegar ao final da tela, o jogo acaba
        if enemy.y > HEIGHT:
            game_over = True
 
# --- FUNÇÃO ON_KEY_DOWN (EVENTOS DE TECLADO) ---
def on_key_down(key):
    global game_over
    if game_over and key == keys.SPACE: # Se o jogo acabou e SPACE é pressionado, reinicia
        init_game()
 
# Inicializa o jogo quando o script começa
init_game()
 
pgzrun.go()

O que fizemos aqui?

  1. Criamos uma lista vazia chamada enemies.
  2. Na função init_game(), adicionamos 5 objetos Actor (nossos inimigos) à lista enemies usando append(). Cada inimigo tem uma posição x aleatória.
  3. Na função draw(), usamos um loop for para percorrer cada enemy na lista enemies e chamamos enemy.draw(). Assim, todos os inimigos são desenhados!
  4. Na função update(), outro loop for move cada enemy para baixo. Também verificamos se algum inimigo saiu da tela, o que causa um game_over.

3. Gerenciando Múltiplos Tiros 🔫

Agora, vamos adicionar a capacidade de nosso jogador atirar várias balas!

3.1. Adicionando e Movendo Tiros

Assim como os inimigos, vamos usar uma lista para os tiros.

import pgzrun
import random
 
WIDTH = 800
HEIGHT = 600
 
player = Actor('nave')
player.pos = WIDTH // 2, HEIGHT - 50
 
enemies = []
bullets = [] # Uma nova lista para nossos tiros!
 
score = 0
game_over = False
 
def init_game():
    global enemies, bullets, score, game_over
    player.pos = WIDTH // 2, HEIGHT - 50
    enemies = []
    bullets = [] # Limpa a lista de tiros também
    score = 0
    game_over = False
    
    for i in range(5):
        enemies.append(Actor('inimigo', (random.randint(50, WIDTH - 50), random.randint(50, 200))))
 
def draw():
    screen.clear()
    screen.blit('fundo', (0, 0))
    
    player.draw()
    for enemy in enemies:
        enemy.draw()
        
    # Desenha todos os tiros
    for bullet in bullets:
        bullet.draw()
        
    screen.draw.text(f"Score: {score}", (10, 10), color="white", fontsize=30)
    
    if game_over:
        screen.draw.text("GAME OVER!", center=(WIDTH/2, HEIGHT/2), color="red", fontsize=60)
 
def update():
    global game_over
    
    if game_over:
        return
 
    # Movimento do jogador
    if keyboard.left:
        player.x -= 5
    if keyboard.right:
        player.x += 5
    player.x = max(20, min(WIDTH - 20, player.x))
 
    # Movimento dos inimigos
    for enemy in enemies:
        enemy.y += 1
        if enemy.y > HEIGHT:
            game_over = True
 
    # Movimento dos tiros
    # Percorre a lista de tiros e move cada um para cima
    for bullet in bullets:
        bullet.y -= 10 # Tiros sobem mais rápido
        
    # --- IMPORTANTE: REMOVENDO OBJETOS DAS LISTAS ---
    # Se um tiro sair da tela, ele não precisa mais existir.
    # Criamos uma nova lista apenas com os tiros que AINDA estão na tela.
    global bullets # Precisamos de global para reatribuir a lista
    bullets = [bullet for bullet in bullets if bullet.y > 0] # Apenas mantém tiros com y > 0
 
def on_key_down(key):
    global bullets, game_over
    if game_over and key == keys.SPACE:
        init_game()
    elif key == keys.SPACE and not game_over:
        # Quando a barra de espaço é pressionada, adiciona um novo tiro à lista
        # O tiro nasce na posição central superior do jogador
        bullets.append(Actor('tiro', player.midtop))
 
init_game()
pgzrun.go()

Novidades:

  1. Criamos uma lista bullets = [].
  2. Na on_key_down(), quando a tecla SPACE é pressionada, adicionamos um novo Actor de tiro à lista bullets usando bullets.append(Actor('tiro', player.midtop)).
  3. Na draw(), um loop for desenha todos os tiros.
  4. Na update(), um loop for move todos os tiros para cima.
  5. Ponto Crucial: bullets = [bullet for bullet in bullets if bullet.y > 0] é uma forma elegante e eficiente de remover tiros que saíram da tela. Ela cria uma nova lista contendo apenas os tiros cujo y ainda é maior que 0 (ou seja, ainda estão visíveis). Se não fizermos isso, a lista de tiros cresceria infinitamente, consumindo memória e diminuindo o desempenho do jogo.

4. Detectando Colisões com Listas 💥

Agora a parte mais emocionante: fazer os tiros atingirem os inimigos!

4.1. Colisão Jogador vs. Inimigos

Vamos primeiro garantir que o jogador perca se colidir com qualquer inimigo.

# ... (código anterior) ...
 
def update():
    global game_over
    
    if game_over:
        return
 
    # Movimento do jogador
    if keyboard.left:
        player.x -= 5
    if keyboard.right:
        player.x += 5
    player.x = max(20, min(WIDTH - 20, player.x))
 
    # Movimento dos inimigos
    for enemy in enemies:
        enemy.y += 1
        if enemy.y > HEIGHT:
            game_over = True
        
        # COLISÃO: Jogador vs. Inimigos
        if player.colliderect(enemy):
            game_over = True # Se o jogador tocar em qualquer inimigo, o jogo acaba!
 
    # Movimento e remoção dos tiros
    global bullets
    bullets = [bullet for bullet in bullets if bullet.y > 0]
    for bullet in bullets:
        bullet.y -= 10
 
# ... (código on_key_down e init_game) ...

4.2. Colisão Tiros vs. Inimigos

Esta é a parte mais complexa, pois precisamos verificar a colisão de cada tiro com cada inimigo. Se houver uma colisão, ambos (tiro e inimigo) devem ser removidos.

import pgzrun
import random
 
WIDTH = 800
HEIGHT = 600
 
player = Actor('nave')
player.pos = WIDTH // 2, HEIGHT - 50
 
enemies = []
bullets = []
 
score = 0
game_over = False
 
def init_game():
    global enemies, bullets, score, game_over
    player.pos = WIDTH // 2, HEIGHT - 50
    enemies = []
    bullets = []
    score = 0
    game_over = False
    
    for i in range(5):
        enemies.append(Actor('inimigo', (random.randint(50, WIDTH - 50), random.randint(50, 200))))
 
def draw():
    screen.clear()
    screen.blit('fundo', (0, 0))
    
    player.draw()
    for enemy in enemies:
        enemy.draw()
    for bullet in bullets:
        bullet.draw()
        
    screen.draw.text(f"Score: {score}", (10, 10), color="white", fontsize=30)
    
    if game_over:
        screen.draw.text("GAME OVER!", center=(WIDTH/2, HEIGHT/2), color="red", fontsize=60)
    # Se todos os inimigos forem destruídos, o jogador ganha!
    elif not enemies: # Se a lista de inimigos estiver vazia
        screen.draw.text("VOCÊ VENCEU!", center=(WIDTH/2, HEIGHT/2), color="green", fontsize=60)
 
def update():
    global score, game_over, enemies, bullets # Precisamos de global para todas essas variáveis
    
    if game_over or not enemies: # Se o jogo acabou ou venceu, não faz mais nada
        return
 
    # Movimento do jogador
    if keyboard.left:
        player.x -= 5
    if keyboard.right:
        player.x += 5
    player.x = max(20, min(WIDTH - 20, player.x))
 
    # Movimento dos inimigos e colisão com o jogador
    # Usamos uma nova lista para os inimigos que SOBREVIVERAM
    enemies_to_keep = []
    for enemy in enemies:
        enemy.y += 1
        if enemy.y > HEIGHT:
            game_over = True # Inimigo saiu da tela, game over
        
        if player.colliderect(enemy):
            game_over = True # Jogador colidiu com inimigo, game over
            
        if not game_over: # Se o jogo não acabou, mantém o inimigo na lista de sobreviventes
            enemies_to_keep.append(enemy)
            
    enemies = enemies_to_keep # Atualiza a lista de inimigos
 
    # Movimento dos tiros
    for bullet in bullets:
        bullet.y -= 10
 
    # COLISÃO: Tiros vs. Inimigos
    # Vamos criar novas listas para os tiros e inimigos que SOBREVIVERAM à rodada de colisões
    bullets_to_keep = []
    
    # Percorremos cada tiro
    for bullet in bullets:
        hit_enemy = False # Variável para saber se este tiro atingiu algum inimigo
        
        # Percorremos cada inimigo para verificar a colisão com o tiro atual
        for enemy in enemies:
            if bullet.colliderect(enemy):
                score += 10 # Aumenta a pontuação
                enemies.remove(enemy) # Remove o inimigo atingido da lista de inimigos
                hit_enemy = True # Marca que o tiro atingiu um inimigo
                break # O tiro atingiu um inimigo, não precisa verificar os outros inimigos
        
        if not hit_enemy: # Se o tiro não atingiu nenhum inimigo, ele continua na tela
            bullets_to_keep.append(bullet)
            
    bullets = [bullet for bullet in bullets_to_keep if bullet.y > 0] # Atualiza a lista de tiros, removendo os que saíram da tela ou atingiram inimigos
 
    # Spawna novos inimigos se a lista estiver vazia (para continuar o jogo)
    if not enemies and not game_over:
        for i in range(5):
            enemies.append(Actor('inimigo', (random.randint(50, WIDTH - 50), random.randint(50, 200))))
 
 
def on_key_down(key):
    global bullets, game_over, enemies
    if game_over and key == keys.SPACE:
        init_game()
    elif not enemies and key == keys.SPACE: # Se venceu e SPACE, reinicia
        init_game()
    elif key == keys.SPACE and not game_over:
        bullets.append(Actor('tiro', player.midtop))
 
init_game()
pgzrun.go()

Explicação Detalhada da Colisão Tiros vs. Inimigos:

  1. Criamos uma nova lista bullets_to_keep.
  2. Iteramos sobre cada bullet na lista bullets.
  3. Para cada bullet, iteramos sobre cada enemy na lista enemies. Isso é chamado de loop aninhado.
  4. Se bullet.colliderect(enemy) for verdadeiro:
    • Aumentamos a score.
    • Removemos o inimigo da lista enemies usando enemies.remove(enemy). Isso é importante para que o inimigo desapareça.
    • Definimos hit_enemy = True para indicar que este tiro atingiu algo.
    • Usamos break para sair do loop interno (for enemy in enemies). O tiro já atingiu um inimigo, então não precisa verificar os outros inimigos com o mesmo tiro.
  5. Após verificar todos os inimigos para um determinado bullet:
    • Se hit_enemy ainda for False (o tiro não atingiu nenhum inimigo), adicionamos esse bullet à bullets_to_keep.
  6. Finalmente, atualizamos a lista bullets com bullets = [bullet for bullet in bullets_to_keep if bullet.y > 0]. Isso garante que apenas os tiros que não atingiram inimigos E ainda estão na tela permaneçam.

Observação Importante sobre Remover Itens de Listas: Remover itens de uma lista enquanto você está iterando sobre ela pode ser complicado e causar erros inesperados.

  • No caso dos inimigos, enemies.remove(enemy) dentro do loop for bullet in bullets funciona porque estamos removendo de enemies enquanto iteramos sobre bullets.
  • Para os tiros, usamos a técnica de construir uma nova lista (bullets_to_keep) e depois reatribuir bullets = bullets_to_keep. Isso é uma maneira segura e comum de filtrar itens de uma lista.

5. Exercícios e Desafios 🧠

Agora é a sua vez de experimentar! Use o código acima como base e tente implementar as seguintes melhorias:

Tarefas:

  • Desafio 1: Inimigos mais rápidos ou diferentes:
    • Faça com que alguns inimigos se movam mais rápido que outros.
    • Ou faça com que eles se movam da esquerda para a direita enquanto descem. Dica: você pode adicionar uma propriedade direcao_x ao Actor do inimigo.
  • Desafio 2: Spawner de Inimigos:
    • Em vez de spawnar todos os inimigos de uma vez, faça com que novos inimigos apareçam periodicamente (a cada 2-3 segundos, por exemplo) usando clock.schedule_interval().
  • Desafio 3: Vidas do Jogador:
    • Em vez de game_over instantâneo, dê ao jogador 3 vidas. Quando ele colide com um inimigo ou um inimigo sai da tela, ele perde uma vida. O jogo só acaba quando as vidas chegam a zero.
    • Mostre as vidas na tela.
  • Desafio 4: Power-up de Tiro Duplo:
    • Crie um novo tipo de Actor (um power-up) que aparece aleatoriamente.
    • Se o jogador coletar o power-up, ele passa a atirar duas balas ao invés de uma por um tempo limitado (ex: 10 segundos).

6. Resumo e Próximos Passos 🚀

Parabéns! Você deu um grande salto na criação de jogos com Python e Pgzero!

Nesta aula, você aprendeu o poder das listas para gerenciar múltiplos objetos em seu jogo. Vimos como:

  • Criar listas de Actors.
  • Desenhar e mover todos os objetos em uma lista.
  • Adicionar novos objetos às listas (como tiros).
  • Remover objetos das listas de forma eficiente (tiros que saem da tela, inimigos destruídos).
  • Detectar colisões entre o jogador e múltiplos inimigos, e entre múltiplos tiros e múltiplos inimigos.

As listas são uma ferramenta fundamental em qualquer jogo que tenha muitos elementos interativos. Com elas, seus jogos podem se tornar muito mais dinâmicos e desafiadores!

Próximos Passos: No próximo módulo, vamos explorar como adicionar sons e música para tornar seu jogo ainda mais imersivo e divertido! 🎵 Fiquem ligados!

© 2025 Escola All Dev. Todos os direitos reservados.

Usando Listas para Inimigos e Tiros - Game Maker: Python com Pgzero para Crianças | escola.all.dev.br