Game Maker: Python com Pgzero para Crianças
Usando Listas para Inimigos e Tiros
Aprenda sobre usando listas para inimigos e tiros
🚀 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?
- Criamos uma lista vazia chamada
enemies. - Na função
init_game(), adicionamos 5 objetosActor(nossos inimigos) à listaenemiesusandoappend(). Cada inimigo tem uma posiçãoxaleatória. - Na função
draw(), usamos um loopforpara percorrer cadaenemyna listaenemiese chamamosenemy.draw(). Assim, todos os inimigos são desenhados! - Na função
update(), outro loopformove cadaenemypara baixo. Também verificamos se algum inimigo saiu da tela, o que causa umgame_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:
- Criamos uma lista
bullets = []. - Na
on_key_down(), quando a teclaSPACEé pressionada, adicionamos um novoActorde tiro à listabulletsusandobullets.append(Actor('tiro', player.midtop)). - Na
draw(), um loopfordesenha todos os tiros. - Na
update(), um loopformove todos os tiros para cima. - 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 cujoyainda é 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:
- Criamos uma nova lista
bullets_to_keep. - Iteramos sobre cada
bulletna listabullets. - Para cada
bullet, iteramos sobre cadaenemyna listaenemies. Isso é chamado de loop aninhado. - Se
bullet.colliderect(enemy)for verdadeiro:- Aumentamos a
score. - Removemos o inimigo da lista
enemiesusandoenemies.remove(enemy). Isso é importante para que o inimigo desapareça. - Definimos
hit_enemy = Truepara indicar que este tiro atingiu algo. - Usamos
breakpara 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.
- Aumentamos a
- Após verificar todos os inimigos para um determinado
bullet:- Se
hit_enemyainda forFalse(o tiro não atingiu nenhum inimigo), adicionamos essebulletàbullets_to_keep.
- Se
- Finalmente, atualizamos a lista
bulletscombullets = [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 loopfor bullet in bulletsfunciona porque estamos removendo deenemiesenquanto iteramos sobrebullets. - Para os tiros, usamos a técnica de construir uma nova lista (
bullets_to_keep) e depois reatribuirbullets = 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_xaoActordo 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().
- 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
- Desafio 3: Vidas do Jogador:
- Em vez de
game_overinstantâ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.
- Em vez de
- 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).
- Crie um novo tipo de
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!