Introdução#
Neste handout vamos começar a nos preparar para desenvolver nosso primeiro projeto de Developer Life. Para isso vamos apresentar uma biblioteca de desenvolvimento de jogos em Python chamada pygame. Hoje iremos aprender como criar uma janela e desenhar formas geométricas simples.
Estrutura básica de um jogo#
Vamos começar entendendo a estrutura de um jogo qualquer. Note que este conhecimento independe da biblioteca ou linguagem de programação utilizada. Essa estrutura pode ser resumida da seguinte maneira:
Inicialização#
- Importa e inicia pacotes: por exemplo, realiza os
imports
no Python; - Inicia estruturas de dados: por exemplo, inicia listas, dicionários, variáveis, etc. com seus valores padrão;
- Inicia assets: exemplos de assets são imagens, sons, sprites, modelos 3D, etc. que são utilizados pelo jogo;
- Gera tela principal: criar a janela onde o jogo será apresentado;
- Dispara loop principal: depois que toda a inicialização está concluída o jogo propriamente dito se inicia.
Loop principal#
O jogo e toda a interação acontece durante esta fase. O loop principal, também chamado de game loop, se repete enquanto o jogo estiver rodando. Em geral, ao final de uma iteração do loop principal será gerado um novo frame para ser apresentado para o usuário.
- Trata eventos: por exemplo, movimentos/cliques de mouse, input de teclado, timer, clique do usuário no X para fechar a janela, etc.
- Verifica consequências: verifica se houve colisões, aplica as ações do usuário (por exemplo, muda a direção do personagem se o jogador apertou uma tecla), etc. de acordo com as regras e mecânica do jogo;
- Atualiza estado do jogo: move os objetos na tela, atualiza os estados dos assets (por exemplo, muda a animação) e as estruturas de dados utilizadas (por exemplo, atualiza a lista de personagens vivos);
- Gera saídas: exibe as mudanças para o jogador (gera uma nova imagem/frame, toca um som, etc.).
Finalização#
Na finalização pode ser realizado o fechamento de arquivos, salvar o placar do jogo, fechar a janela do jogo, etc.
Seu primeiro jogo usando pygame#
O pygame é um framework (ou game engine) para desenvolvimento de jogos em Python, baseado na biblioteca SDL2 (Simple DirectMedia Layer).
Conforme formos aprendendo sobre a estrutura de um jogo em pygame iremos implementar as funções pedidas nos testes. Ao final deste handout todos testes estarão passando e seu programa deverá funcionar ao ser executado no VSCode.
O exercício do handout de hoje será feito passo a passo. Abra-o no VSCode e retorne para o handout.
Instalação#
Nas aulas de Python básico só usamos pacotes que já estão inclusos na instalação padrão. A pygame é uma biblioteca extra desenvolvida por um grupo diferente de pessoas e precisa ser instalada manualmente.
Instalamos bibliotecas externas com o comando pip
. Abra o terminal do seu VSCode e digite o seguinte comando.
pip install pygame
Verifique se tudo está correto executando o programa jogo.py
(que foi aberto pelo exercício deste handout). Ao executá-lo o terminal não deverá mostrar nenhuma mensagem, mas também não deve ocorrer nenhum erro.
Warning
Se ao rodar o código seu terminal mostrar algo como abaixo então deu algo errado. Chame o professor.
Com o ambiente configurado vamos iniciar a atividade prática de hoje.
Criando uma janela#
Para criar uma janela com Pygame precisamos implementar todas aquelas etapas da organização básica do jogo que vimos acima. Ou seja, será necessário utilizarmos muitos comandos para fazer coisas aparentemente simples, como mostrar uma janela em branco. Não se assuste. Essa estrutura básica se repete de maneira muito semelhante em qualquer jogo que você for desenvolver. Vamos começar então com o exemplo que acabamos de comentar: como mostrar uma janela do pygame.
Código jogo_v0.py
Para entender melhor este código extenso iremos separá-lo nas partes da estrutura básica que mostramos no início. Ou seja, nos próximos exercícios iremos criar as seguintes funções:
inicializa
: inicializa pygame e recursos necessários para o jogodesenha
: atualiza a tela, desenhando figuras e escrevendo texto na telarecebe_eventos
: recebe interação do usuáriogameloop
: roda até que o jogador feche o jogo. Chama as funçõesdesenha
erecebe_eventos
.finaliza
: finaliza a pygame e salva possíveis resultados do jogo.
Tip 1
A pygame possui um grande número de funcionalidades. Consulte a documentação se não entender algum dos comandos.
Nossas próximas atividades de pygame estarão organizadas usando estas funções.
A etapa de inicialização consiste em configurar a pygame, definindo o tamanho da janela desejada e seu título. As linhas onde isto é feito são apontadas abaixo.
Código jogo_v0.py
- Inicialização
pygame.init()
(linha 5): o framework pygame é iniciado. Apenas após este comando é que os recursos do pygame podem ser utilizados;pygame.display.set_mode((500, 400))
(linha 8): cria uma janela com 500 pixels de largura e 400 pixels de altura;pygame.display.set_caption('Hello World!')
(linha 9): define o título da janela (que aparece na barra superior) como o texto'Hello World!'
;
Como só mostramos uma janela simples não fazemos muito por aqui, mas tarefas comuns nesta seção são
- carregar imagens, sons e outros recursos necessários;
- definir os valores iniciais de todas as variáveis do jogo
Tuplas
Você reparou no argumento de pygame.display.set_mode((500, 400))
? Este par (500, 400)
é uma tupla e representa as dimensões da janela a ser criada. Tuplas são usadas quanto queremos representar pequenos conjuntos de dados que são naturalmente agrupados.
Dada uma tupla tamanho = (400, 300, 100)
, mostramos abaixo o que podemos e não podemos fazer com tuplas.
- podemos ler seu conteúdo usando índices (
tamanho[0]
contém o valor400
) - podemos comparar tuplas usando
==
(tamanho == (400, 300, 100)
éTrue
) - não podemos modificar elementos da tupla (
tamanho[1] = 500
resulta em erro) - não podemos adicionar novos elementos na tupla
Código Python bem escrito em geral contém muitas tuplas. Se acostume a usá-las sempre que precisar passar valores que só fazem sentido em conjunto.
Vamos iniciar nossa versão "organizada" deste código agora.
Exercise 1
Warning
Por enquanto vamos só criar as funções para que elas passem nos testes preliminares. No fim do handout criaremos a função roda_jogo()
que vai efetivamente chamar todas essas funções e colocar nosso programa para funcionar.
Vamos agora para nossa função de tratamento de eventos. As linhas que fazem este trabalho estão explicadas abaixo.
Código jogo_v0.py
- Recebendo eventos
for event in pygame.event.get():
(linha 17):pygame.event.get()
devolve uma lista com todos os eventos (cliques/movimentos de mouse, teclas apertadas, botões da janela apertados, etc.) que ocorreram desde a última vez que essa função foi chamada. Ofor
percorre cada um desses eventos, aplicando as consequências necessárias a cada caso;if event.type == pygame.QUIT:
(linha 19): verifica se o tipo do evento épygame.QUIT
, ou seja, se o usuário clicou no botão de fechar a janela (consulte a documentação para ver mais eventos possíveis);game = False
(linha 20): muda o valor paraFalse
para que a próxima iteração do loop principal não seja mais executada. Depois disso o programa termina;
Exercise 2
Agora vamos trabalhar com a função de desenho. Vejamos quais linhas do jogo_v0.py
desenham na tela.
Código jogo_v0.py
- Desenhando na tela
window.fill((255, 255, 255))
(linha 23): preenche a janela com a cor branca. As cores são valores RGB (Red, Green, Blue) que variam entre 0 e 255. Note que existe um parênteses ao redor dos três valores. Esse parênteses é muito importante, pois define uma tupla (uma sequência de valores, semelhante a uma lista);pygame.display.update()
(linha 26): tudo o que é feito na tela (window
) não é mostrado para o usuário imediatamente, assim é possível desenhar várias coisas e somente depois de terminar de desenhar mostramos a nova tela (ou frame) para o usuário. Essa função é responsável por mostrar a nova tela que foi desenhada;
Para fazer o próximo exercício você deverá consultar a documentação da função fill()
neste link. Com ela em mãos, clique em continuar
A variável window
é do tipo pygame.Surface
, que representa um superfície 2D que pode ser desenhada na tela. A função fill
preenche a tela toda com a cor passada. Do jeito que chamamos ela recebe só um argumento, mas a documentação mostra ela recebendo três!
Esse parâmetros extra possuem valores padrão, então podemos não passá-los e eles receberão o valor declarado. Vamos agora interpretar isso.
Exercise 3
Answer
O segundo parâmetro representa o retângulo que deverá ser preenchido com a cor passada para fill
.
Agora consulte a documentação de pygame.Rect
para descobrir como construir retângulos válidos.
Exercise 4
Answer
Como visto na documentação, passamos primeiro as coordenadas x
e y
do topo do retângulo e depois sua largura e altura.
Vamos juntar tudo agora.
Exercise 5
Answer
Como queremos pintar toda a tela sempre usamos a versão sem o segundo argumento. Nem sempre nossa tela será do tamanho \(320\times 240\), então o correto aqui é não passar esse parâmetro.
Exercise 6
Answer
Podemos eliminar algumas alternativas lembrando que a cor branca é (255, 255, 255)
. Como nossa tela tem tamanho \(640\times 480\), precisamos que nosso retângulo inicie na metade disso (ou seja, x=320
) e que ele comece no topo da tela (y=0
). Como dividimos a tela em dois na vertical, ela tem largura 320
e altura 480
. Juntando tudo isso chegamos à chamada
window.fill((255, 255, 255), pygame.Rect(320, 0, 320, 480))
Exercise 7
Nossas duas últimas tarefas são implementar as funções de loop do jogo e de finalização.
Código jogo_v0.py
- Loop do jogo e Finalização
O loop do jogo é feito entre as linhas 12 e 26. Note que boa parte desse código já está contido nas funções recebe_eventos
e desenha
. Falta só chamá-las em loop enquanto o jogo não for fechado.
Exercise 8
A finalização do jogo é feita pela função pygame.quit()
(linha 29). É importante chamar essa função para que ele feche todos os recursos que abriu (por exemplo a janela).
Exercise 9
Success
Estamos prontos para juntar tudo isso e ver nosso jogo rodando!
Agora todos os testes devem estar passando. Vamos então criar a função que vai realmente rodar nosso jogo! Ela se chamará roda_jogo
e deverá ser escrita usando as funções que vocês já escreveram. Lembre-se a organização do jogo, em que primeiro inicializamos os recursos, depois rodamos o loop do jogo e por último finalizamos o pygame.
Usuários de macOS
Existe um sistema de permissões que impede aplicações de requisitar algumas capacidades. Ao rodar seu primeiro jogo é possível que a seguinte janela apareça. Siga as instruções nesse caso
Exercício
Crie a função roda_jogo
. Agora não vamos disponibilizar testes, então você deverá rodar o programa para checar se tudo está ocorrendo corretamente.
Nosso programa irá chamar roda_jogo
usando o seguinte código. Cole-o no fim de jogo.py
e execute!
Tip 2
O trecho acima é necessário pois não queremos que nosso jogo seja executado quando o arquivo dele for importado por outro programa python. Se só colocássemos roda_jogo()
como fizemos anteriormente, nosso jogo rodaria se algum outro programa python fizesse import jogo
.
Para finalizar esse exercício, execute seu jogo e veja se o resultado foi o esperado. Você deverá ver uma tela retangular com título "Jogo do ...." e um fundo vermelho.