Como criar o jogo Alien Invasion em Python

Felipe Salles
22 min readMay 17, 2021

Antes de começar, vale lembrar que você precisa ter instalado na sua máquina o Python, uma IDE de sua preferência para programar e também será necessário instalar a biblioteca Pygame. Essa biblioteca será fundamental para nos ajudar a desenvolver diversos passos e aspectos do nosso jogo.

Para instalar a biblioteca Pygame, basta utilizar no terminal de comando $python -m pip install--user pygame (se você usa macOS e isso não funcionar tente remover “--user” e execute o comando novamente). Agora que você instalou tudo que precisamos, vamos desenvolver nosso jogo!

Nessa primeira etapa, iremos desenvolver uma nave capaz de mover para a direita e para a esquerda com as setas do teclado. Além disso, ela será capaz de soltar bala caso o usuário aperte espaço no teclado. Primeiro, criaremos uma janela do Pygame para posteriormente adicionar nossa nave, os aliens e tornar nosso jogo responsivo a decisões do usuário!

Importamos a biblioteca sys e a pygame. A biblioteca pygame como dito anteriormente possui recursos importantes para desenvolver nosso jogo e a sys também, já que ela nos fornece ferramentas para sair do jogo caso o usuário deseje. Logo, iremos usar essas duas bibliotecas fazendo o import da sys e da biblioteca pygame.

Criamos uma classe chamada de AlienInvasion. Nessa classe temos a inicialização das configurações de fundo da imagem que a biblioteca Pygame necessita para funcionar corretamente: pygame.init(). Em self.screen criamos uma janela responsável por exibir todos os nossos elementos gráficos. Ela recebe (1200, 800) que nada mais é que uma tupla para definir a dimensão da nossa janela em pixels (largura por altura).

O jogo é controlado em run_game(), pois temos um loop infinito responsável por atualizar os eventos da tela do nosso jogo. Os eventos são caracterizados pelas ações que o usuário executa ao jogar o jogo. Por isso, precisamos entender esses eventos para desenvolver as tarefas apropriadas, como por exemplo, locomover a nave para direita ou esquerda usando as setas do teclado.

Para acessar os eventos detectáveis pelo Pygame usamos a função pygame.event.get() para fazer a interpretação do evento e responder da maneira adequada utilizando condições(if, else). Criamos um evento para detectar quando o usuário clica no botão de fechar pygame.QUIT para chamar sys.exit() e encerrar o jogo.

Criamos self.bg_color para podermos mudar a cor do nosso fundo que anteriormente era preto por padrão. As cores no Pygame são definidas em RGB (mistura de vermelho, verde e azul.)

Usamos self.screen.fill para pintar nosso fundo com a cor desejada.

Criaremos uma classe responsável por conter todas as configurações em um só lugar. Com isso, conseguimos adicionar e alterar novas funcionalidades do jogo com mais facilidade, eficiência e organização. Para isso, criaremos um novo arquivo em python chamado settings.py dentro da pasta do seu projeto (armazene os arquivos sempre na mesma pasta).

Para interligar ambos arquivos precisamos modificar nosso código principal. Importamos usando “from settings import Settings” para depois atribuir as configurações para self.settings após pygame.init() e também em fill().

Agora vamos adicionar uma imagem da nossa nave para o jogo. O Pygame por default carrega arquivos bitmaps. Logo, usarei uma imagem sem background de uma nave em bitmap para facilitar o processo, mas se você preferir pode usar imagens em jpg, png ou converter imagens nesses formatos para bitmap (.bmp).

Criaremos um arquivo ship.py para implementar a classe da nossa nave e trataremos nossa nave e a tela como retângulos para facilitar a análise de colisões.

Importamos a biblioteca Pygame antes de definirmos nossa classe. O init recebe dois parâmetros: uma referência própria e outra para a instância atual da classe AlienInvasion para dar acesso a todos recursos definidos em AlienInvasion.

Em self.screen permitimos facilmente acessar os métodos dentro da classe ao atribuir a tela como um atributo da nave. E em self.screen_rect permitimos colocar a nave na posição correta da tela.

Para colocarmos a nave no centro da tela precisamos que o valor de self.rect.midbottom seja o mesmo de midbottom.

Desenhamos a imagem na tela posicionando especificamente no lugar que definimos por self.rect.

Precisamos importar ship em alien_invasion.py para que possamos mostrar a nave na tela.

Após a criação da tela, criamos uma instância referindo a atual instância de AlienInvasion, o que permite o acesso aos recursos do jogo a Ship (nave).

Após preencher o fundo, desenhamos a nave na tela chamando self.ship.blitme().

O próximo passo será organizar melhor nosso run_game para que possamos separar e tornar nosso código mais lógico. Por isso, vamos criar _check_events() para isolar o loop de run_game().

Agora, fica mais claro no run_game() que estamos interessados em novos eventos e na atualização da tela em cada passagem pelo loop.

Nosso próximo passo é pilotar a nossa nave. Para isso, vamos passar a tornar ela responsiva ao pressionar as setas do teclado. Lembrando que quando pressionamos uma tecla, o Pygame considera isso como um evento. Cada evento é pego por pygame.event.get(). Mas, precisamos especificar que tipo de eventos queremos que o jogo verifique e no nosso caso o evento é chamado de KEYDOWN, ou seja, evento gerado quando uma tecla estiver sendo pressionada.

Definimos dentro do for uma condição para um evento do tipo KEYDOWN para caso a tecla for a seta da direita mover a nave para a direita aumentado o valor de self.ship.rect.x por 1. Se você testar seu programa, sua nave é capaz de mover para a direita um pixel por vez.

Agora, precisamos implementar um movimento contínuo para que a nave mova continuamente se o usuário permanecer segurando a tecla.

Em ship.py iremos implementar a flag de movimento e a função update:

Quando a flag moving_right é falsa, a nave ficará imóvel, mas caso o usuário pressione a seta da direita do teclado, a flag agora será True e caso o usuário solte a tecla, a flag será falsa novamente. Usaremos moving_right e update() para verificar o status da flag. Precisamos modificar em alien_invasion.py o _check_events() para que moving_right funcione corretamente.

Também adicionamos self.ship.update() no while True para que seja chamada no loop a cada passagem fazendo com que a posição da nave seja atualizada depois da checagem dos eventos das teclas do teclado e a atualização da tela.

Agora, precisamos fazer a mesma coisa para a esquerda. Vamos começar no arquivo ship.py!

Agora, precisamos ajustar a nossa checagem de eventos em alien_invasion.py!

Com isso, conseguimos checar e tratar corretamente os possíveis movimentos da nave de acordo com o que o usuário aperta no teclado. Atualmente, nossa nave move um pixel por ciclo do loop, mas pretendemos ajustar a velocidade da nave. Para isso, temos que adicionar o atributo ship_speed na classe de settings em settings.py.

Atribuímos o valor inicial de 1.5 a nave para que a posição seja ajustada por 1.5 pixels a cada iteração no loop. Precisamos fazer modificações em ship.py, já que tratamos como um retângulo e usamos um número decimal para nossa velocidade sabendo que os atributos de “rect” armazenam apenas valores inteiros. Em ship.py:

Nós precisávamos atribuir a posição a uma variável capaz de armazenar um valor decimal, pois precisamos ser precisos e “rect” só manteria a parte inteira do nosso valor. Por isso, criamos um atributo chamado “self.x” para guardar valores decimais e usamos a função float para converter o valor de “self.rect.x” para decimal.

Você já deve ter percebido que ao segurar a tecla por muito tempo, perdemos nossa nave da tela. Portanto, vamos corrigir agora esse problema!

Em ship.py precisamos limitar a nave a janela. Para isso, faremos a seguinte modificação em update():

Fazendo as seguintes modificações, conseguimos tratar a posição da nave antes de mudar o valor de self.x verificando se a nave atingiu determinada borda da tela. Agora, vamos refatorar parte do nosso código em _check_events em alien_invasion.py!

Quebramos nossa checagem de eventos em pequenas checagens para facilitar o desenvolvimento de novas funcionalidades para o jogador. O próximo passo será criar algo que faça o usuário sair do jogo ao pressionar a tecla Q de Quit, vamos lá!

Em alien_invasion.py, adicionamos a condição para sair do jogo caso o jogador aperte a tecla Q. Agora, vamos fazer algumas alterações para que o jogo seja exibido em tela cheia em alien_invasion.py.

Se você não quiser que o jogo rode em tela cheia pode permanecer com as configurações anteriores. O próximo passo é adicionar balas a nossa nave e permitir que ela comece a se preparar para os alienígenas. Em settings.py vamos criar uma classe chamada Bullet.

Agora, vamos criar a classe em um arquivo chamado bullet.py.

Importamos sprite da biblioteca Pygame, pois podemos relacionar elementos em grupos no jogo e representar todos os grupos de uma vez. A segunda parte de nosso arquivo fica da seguinte forma:

Em alien_invasion precisamos adicionar o grupo e atualizar a posição das balas a cada iteração do loop da seguinte forma:

O próximo passo será atirar essas balas que acabamos de desenvolver, vamos lá!

Em alien_invasion.py:

Ao testar nosso jogo, percebemos que as balas estão sendo atiradas normalmente, mas temos um problema importante para resolver. Após os tiros, elas continuam consumindo memória e processamento porque por mais que não estamos mais vendo na tela, isso só acontece porque o Pygame não consegue desenhar além da tela. Logo, para resolver esse problema precisamos detectar quando o valor do bottom é 0, indicando que a bala passou do topo da tela.

Agora, para tornar nosso jogo mais desafiante, vamos limitar a quantidade de balas que o jogador terá para que ele busque acertar mais. Para isso, vamos em settings.py e adicionamos self.bullets_allowed:

Com isso, limitamos a 3 a quantidade de balas por tempo. Em alien_invasion.py precisamos verificar quantas balas foram atiradas antes de criar uma nova em _fire_bullet():

Quando o jogador pressiona o espaço, nós verificamos o tamanho de bullets. Se o tamanho for menor que 3, criamos uma bala nova. Mas, caso 3 balas ainda estejam disponíveis, nada acontece quando o espaço está pressionado pelo jogador. Teste o programa para verificar que agora conseguimos apenas atirar em grupos de 3.

O próximo passo será composto por criar um método para atualizar as balas e deixar nosso código mais organizado. Em alien_invasion.py faça as seguintes modificações:

O próximo passo do nosso jogo será criar nosso primeiro Alien. Para criar o primeiro alien temos que entender que já fizemos algo parecido, pois pretendemos adicionar uma imagem a tela e fizemos isso anteriormente ao adicionar a nave no jogo. Vamos criar uma classe chamada Alien. Para isso, busque uma imagem do seu gosto e siga as mesmas questões de quando adicionamos a nave anteriormente. Lembre-se de adicionar a imagem na pasta “images” do nosso projeto.

Agora, vamos criar um arquivo chamado alien.py para criar a nossa classe.

Perceba que essa classe é bem similar a feita para a nossa nave. Vamos criar uma instância para mostrar o Alien na tela. Em alien_invasion.py:

Ao realizar essas implementações, você será capaz de ver o alien na tela!

O próximo passo será desenvolver um esquadrão de aliens, mas para isso precisamos entender algumas coisas: quantos aliens podem caber na tela e quantas fileiras de alienígenas podem caber na tela. Primeiro, vamos descobrir o espaço horizontal entre os aliens e criar uma fila. Depois, vamos determinar o espaço vertical e criar o esquadrão. Vamos lá!

Vamos refatorar nosso código!

Para finalizar o nosso esquadrão, precisamos determinar o número de linhas que cabem na tela e repetir o loop para criar o número correto de aliens. Além disso, precisamos pensar em deixar um espaço entre os aliens e a nave para que o jogador tenha condições de atirar no começo de cada nível.

Agora que conseguimos criar o esquadrão de aliens, vamos fazer eles se moverem na tela!

Em settings.py:

Em alien.py:

Em alien_invasion.py:

Se você rodar o código pode perceber que vemos o esquadrão de aliens se movendo para a direita. Vamos criar configurações para mudar isso!

Em settings.py adicionamos:

Vamos implementar uma função para verificar quando um alien bate na borda. Em alien.py:

Vamos agora mudar a direção quando o esquadrão de aliens bater na borda. Em alien_invasion.py:

Agora, chegamos numa das partes mais legais. Vamos atirar nos aliens!

Para fazer isso, precisamos tratar bem sobre colisões, já que atualmente quando atiramos nos aliens as balas atravessam o inimigo. Temos que detectar quando a bala acerta o alien e desenvolver nosso código. Para fazer isso, trataremos da seguinte forma a colisão:

Testamos se as balas e os aliens colidiram, caso seja True deletamos da tela.

Nosso próximo passo será fazer uma implementação em alien_invasion.py capaz de substituir o grupo de aliens que foi eliminado pelo jogador. Para fazer isso, precisamos checar se o grupo está vazio, caso esteja chamamos _create_fleet() para podermos criar o novo esquadrão ao final de _update_bullets(), já que é o local onde atiramos nos aliens de forma individual.

Agora, vamos aumentar a velocidade das balas, pois estamos tento uma certa dificuldade de desempenho ao tentar matar todos os aliens. Para fazer isso, vamos mudar a velocidade da bale em settings.py ajustando a velocidade da bala para 1.5 visando aumentar a sua velocidade.

Nosso próximo passo será refatorar nosso código para melhorar a organização de nosso projeto e facilitar o entendimento do código. Vamos começar refatorando _update_bullets() para que não tenhamos muitas tarefas diversas movendo a detecção de colisão para um método separado em alien_invasion.py da seguinte forma:

Mas, você já parou para pensar que nas configurações atuais não perdemos? Qual é a graça? Por isso, precisamos implementar algo capaz de detectar quando os aliens conseguem colidir e eliminar a nave ao mesmo tempo que fazemos uma limitação do número de naves. Além disso, destruiremos a nave quando os aliens alcançarem o limite de baixo da tela. O jogo encerrará quando o jogador usar todas as naves disponíveis. Nosso primeiro passo será detectar a colisão entre alien e nave, vamos lá!

A função spritecollideany() recebe dois argumentos: sprite e um grupo. Seu objetivo é procurar por algum membro do grupo que colidiu com o nosso objeto sprite e parar o loop enquanto não encontrar um membro que colide com o sprite. Nesse caso, teremos o retorno do primeiro alien que colidiu com a nave. Caso não ocorra nenhuma colisão, a função retornará None e o bloco do if não será executado. Se quiser você pode verificar testando o jogo, vamos ao próximo passo!

Agora, queremos contar quantas vezes a nave foi atingida por um alien criando uma classe responsável por estruturar as estatísticas do nosso jogo. Vamos criar um arquivo chamado de game_stats.py e nele criar uma classe chamada de GameStats.

Criamos uma função de reset para tratar dos casos quando o jogador começar um novo jogo. E para isso, as estatísticas precisam ser reinicializadas. Por enquanto, temos apenas a quantidade de naves restantes (ships_left) e o número de naves que o jogador começará, é armazenado em settings.py como ship_limit:

Também precisamos fazer algumas mudanças para criar instâncias de GameStats. Vamos lá!

Vamos importar sleep para pausar nosso jogo quando a nave for atingida e também importar GameStats. Agora, vamos criar uma instância de GameStats em __init__():

Quando um alien atingir a nave, iremos subtrair 1 do número de naves restantes, destruir todos os aliens e balas existentes e reposicionar a nave no meio da tela. Além disso, pausaremos o jogo para que o jogador perceba que aconteceu uma colisão e consiga se preparar para jogar novamente criando um novo método chamado de _ship_hit().

Em _update_aliens(), trocamos a função print por _ship_hit() para tratar quando uma nave for atingida por um alien.

Nosso próximo passo será tratar quando os aliens alcançam a parte de baixo da tela com o objetivo de tratar da mesma forma do que fizemos quando a nave é atingida por um alien. Para isso, vamos adicionar um novo método em alien_invasion.py logo após _update_aliens:

Para centralizar a nave na tela, basta utilizar o seguinte método em ship.py:

Nosso próximo passo será implementar uma nova funcionalidade para que o jogo pare quando o jogador não estiver mais naves disponíveis. Para isso, vamos implementar em game_stats.py:

E agora em alien_invasion.py adicionamos game_active em _ship_hit():

Também precisamos verificar se o game está ativo ou não no nosso loop principal. Para isso, vamos adicionar uma condição na funcão def run_game no arquivo alien_invasion.py, já que não temos a necessidade de fazer certas atualizações caso o jogo não esteja ativo:

Agora, se você tentar jogar, o seu jogo deve congelar caso você já tenha utilizado todas as naves disponíveis. Foi uma longa jornada até aqui, mas ainda precisamos finalizar o nosso jogo. Vamos lá!

Nosso próximo passo será criar um botão para começar o jogo e recomeçar caso o jogo se encerre. Além disso, vamos acelerar o jogo caso o jogador avance de nível e também implementaremos um sistema de pontos.

Em game_stats.py modificaremos nosso código de forma que o jogo comece num estado inativo:

Agora, vamos criar um arquivo chamado button.py e criar uma classe para o Botão.

Chamamos screen.fill() para desenhar e blit para desenhar a imagem de texto para a tela passando uma imagem e um objeto rect para finalizar a nossa classe.

Em alien_invasion.py importamos nossa classe e criamos o botão em __init__():

Em _update_screen chamamos draw_button():

Nosso próximo passo será iniciar o jogo quando o jogador clicar em play. Em alien_invasion.py na função _check_events, faremos uma implementação:

Restringimos a detecção do click do mouse apenas ao botão. Além disso, devemos criar uma nova função para receber as coordenadas/posição do mouse logo abaixo da função _check_events:

Caso o jogador clique na região do botão, o jogo passa a ser ativado(True) e nosso jogo começa!

Nosso próximo passo será implementar um reset no jogo. Para fazer isso, precisamos dar um reset nas estatísticas do jogo, limpar os aliens e balas antigas, construir um novo esquadrão e centralizar a nave. Em alien_invasion.py:

Agora, você conseguirá dar um reset no jogo ao clicar em Play quantas vezes quiser, mas ainda temos um problema que precisamos consertar. A região do botão continua respondendo a cliques mesmo que o botão não esteja visível na tela. Vamos consertar isso. Em alien_invasion.py:

Agora, vamos ocultar nosso mouse ao começarmos o jogo. Para fazer isso, vamos em _check_play_button() em alien_invasion.py:

Agora, faremos o cursor reaparecer caso o jogador queira jogar novamente em _ship_hit no arquivo alien_invasion.py:

Nosso próximo passo é adicionar níveis de dificuldade ao jogo, pois atualmente quando o jogador atira em um esquadrão inteiro o nível de dificuldade permanece o mesmo. Vamos tornar nosso jogo mais desafiador. Em settings.py:

Lembre-se de mover ship_speed, bullet_speed e alien_speed para dentro da função initialize.

Agora, criaremos uma função em settings.py para alterar a velocidade ao avançarmos de nível no jogo:

Em alien_invasion.py precisamos chamar em _check_bullet_alien_collisions() quando o último alien do esquadrão for abatido:

Agora, precisamos dar um reset na velocidade quando o jogador começar o jogo novamente. Em alien_invasion.py:

Nosso próximo passo será desenvolver nosso sistema de pontos. Em game_stats.py:

Para mostrar os pontos na tela, primeiro criamos uma nova classe chamada Scoreboard em um novo arquivo chamado scoreboard.py:

Para mostrar o texto na tela como uma imagem, chamamos prep_score() em scoreboard.py:

E adicionamos draw_score para mostrar na tela de acordo com o que definimos em score_rect:

Agora, vamos criar uma instância em AlienInvasion para mostrar na tela no arquivo alien_invasion.py:

Em __init__():

Para assim, desenharmos em _update_screen():

Nosso próximo passo será atualizar os pontos à medida que os aliens forem abatidos. Em settings.py:

Em alien_invasion.py precisamos atualizar os pontos a cada alien abatido:

Precisamos agora dar um reset nos pontos quando começamos um novo jogo. Em alien_invasion.py em _check_play_button:

Atualmente, temos um problema na contagem de pontos, já que 2 balas podem colidir com aliens durante a mesma passagem no loop, por isso precisamos melhorar a maneira que identificamos a colisão entre alien e bala. Em alien_invasion.py adicionamos * len(aliens):

No próximo passo, vamos implementar um sistema de pontos condizente com a dificuldade do nível que o jogador se encontra, já que temos uma dificuldade maior a cada nível. Então, nada mais justo que ganhar mais pontos progressivamente. Em settings.py:

Em def increase_speed:

Agora, vamos acertar a formatação da pontuação do jogo e melhorar a contagem. Em scoreboard.py:

Nosso próximo passo é armazenar as pontuações mais altas para motivar o jogador a desafiar seus limites e competir com seus amigos. Em game_stats.py em __init__:

Agora, vamos modificar Scoreboard para mostrar a maior pontuação na tela. Vamos começar em __init__():

Como a maior pontuação será mostrada separadamente, precisamos de uma nova função chamada de prep._high_score(). Em scoreboard.py:

Adicionamos um blit para o high score em show_score e criamos uma função para checar a pontuação máxima em scoreboard.py após prep_ships:

E adicionamos em show_score:

Agora, precisamos chamar check_high_score() cada vez que um alien for atingido após atualizamos a pontuação em _check_bullet_alien_collisions():

Nosso próximo passo será criar o sistema de level no nosso jogo. Em game_stats.py:

Para mostrar o level atual, chamaremos prep._level de __init__() em scoreboard.py:

Também precisamos atualizar show_score() e adicionar:

Agora, vamos incrementar stats.level e atualizar o level em check bullet no arquivo alien_invasion.py:

Para garantir que o level atualize corretamente quando começamos um novo jogo, chamamos prep._level() quando o jogador clica no botão Play. Em alien_invasion.py na função _check_play_button:

Tendo assim, a pontuação máxima no centro da tela, a atual no canto direito e o level embaixo da pontuação atual.

Nosso próximo passo é adicionar o número de naves disponíveis para o jogador. Em ship.py:

Agora, precisamos modificar o Scoreboard para criar um grupo de naves. Em scoreboard.py:

Em __init__():

E também adicionamos prep_ships() após prep_level:

E criamos a função prep._ships após prep_level:

Agora, precisamos desenhar na tela o grupo de naves. Em scoreboard.py:

Em _check_play_button em AlienInvasion no arquivo alien_invasion.py mostramos ao player quantas naves ele possui para começar a jogar.

E também chamamos prep_ships() quando a nave é atingida para atualizar quando o jogador perde uma nave. Em _ship_hit() adicionamos:

Nós chamamos prep_ships() antes de diminuir o valor das naves restantes para exibir corretamente o número a medida que uma nave for destruída.

Segue abaixo duas imagens do nosso jogo funcionando!

Parabéns, você conseguiu e chegou ao fim do desenvolvimento do nosso jogo!

Espero que tenha gostado do resultado, mas não pare por aí. Se você se sentir confortável tente implementar novas funcionalidades ao jogo, refatorar algumas partes do nosso código e se divertir ainda mais no desenvolvimento desse projeto. Busquei ser o mais breve possível para não deixar a leitura cansativa, mas caso tenha ficado alguma dúvida, procure entender os motivos por trás das linhas de código, se pergunte o motivo daquilo ter sido feito de tal forma que tenho certeza que isso te ajudará caso você sinta alguma dificuldade. Divirta-se!

--

--

Felipe Salles

I’m a Comp Sci undergrad from Brazil who loves Cyber Security. I write articles about different topics to help others learn and develop in the field. Enjoy!