Tutorial de Pygame (Python)
Tutorial de Pygame (Python)
26 de Março de 2013
1
Vamos desenvolver um jogo clássico... O Pong.
A indústria dos jogos de computador é uma das mais lucrativas e com maior crescimento da
atualidade. Tendo sido considerados como parentes pobres das outras aplicações informáticas
“mais sérias”, os jogos de computador possuem, na atualidade, orçamentos equiparados aos
filmes de Hollywood.
O motor do jogo
O primeiro passo na criação de um jogo é a seleção ou o desenvolvimento do motor de jogo1. Um
motor de jogo é, na sua base, muito simplesmente um ciclo que atualiza todos os objetos e, sendo
a maior parte dos jogos aplicações gráficas interativas, lê os dispositivos de entrada e sintetiza
imagem e som nos dispositivos de saída.
1
Atualmente já existe uma grande diversidade de motores do jogo disponíveis para o
programador com uma extensa gama de funcionalidades.
2
Assim, o motor do jogo realiza as seguintes tarefas de forma cíclica:
1. Dispositivos de entrada (eventos)
2. Lógica do jogo
3. Dispositivos de saída
Em Pygame, o código que implementa este ciclo é o seguinte:
# ciclo de jogo
while True:
# 1. Dispositivos de entrada (eventos)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# 2. Logica do jogo
# 3. Dispositivos de saida
pygame.draw.circle(DISPLAYSURF, (255,255,0), (400, 300), 30, 0)
pygame.display.update()
Em que há um ciclo infinito que engloba os três blocos de código. Um ciclo for lê todos os
eventos que tenham sido ativados pelos dispositivos de entrada ou pelo sistema. Neste caso
particular é lido apenas o evento QUIT que serve para terminar o ciclo de jogo e sair do
programa. (Por muito que nos agrade um jogo, ele irá ter mesmo que terminar... )
Após o passo de leitura dos eventos, é preciso atualizar o estado e os objetos de jogo. Neste
exemplo ainda não colocamos nenhum código, que irá aparecendo ao longo das próximas
secções...
Finalmente, o passo 3 envia os objetos para os dispositivos de saída e atualiza o ecrã gráfico,
através da instrução pygame.display.update(). Neste caso particular é desenhado um círculo
amarelo no centro do ecrã.
Mas antes de se definir o ciclo de jogo, é necessário colocar algum código para inicializar
algumas definições do Pygame e o próprio ambiente gráfico:
import pygame, sys
from pygame.locals import *
##############
# Definicoes #
##############
#################
# inicializacao #
#################
pygame.init()
Nas duas primeiras linhas são importados os módulos Pygame e Sys, que disponibilizam os
objetos necessários para a execução do nosso programa.
Segue-se uma secção de definições como é boa prática de programação. Nesta caso são apenas
definidas as dimensões da janela gráfica (largura e altura).
3
Na secção de inicialização chama-se a função pygame.init() e define-se a dimensão da janela
gráfica e o título dessa janela. É nesta secção que se devem inicializar todas as variáveis e objetos
do jogo, antes de se entrar no ciclo de jogo.
Desenhando no ecrã
Uma das saídas mais importantes para a maioria dos jogos de computador é a imagem (daí que
sejam também conhecidos por videojogos). As imagens são apresentadas em ecrãs de computador
segundo um sistema de referência cartesiano, onde cada objeto geométrico é representado pelos
seus vértices, cada um dos quais se associa a um par de coordenadas (x,y), tal como apresentado
na Figura 2.
(x2,y2)
(x1,y1)
y
X
4
Figura 3 - Definição das imagens através de pixels
O Pygame não opera diretamente no ecrã, por razões que têm que ver com restrições impostas
pelos sistemas operativos, mas sim em janelas gráficas. As coordenadas destas janelas são valores
inteiros entre 0 e as dimensões da imagem (subtraídos de 1). Ou seja, tomando como exemplo
uma janela com dimensões 800x600, a coordenada x pode tomar valores entre 0 e 799 e a
coordenada y pode tomar valores entre 0 e 599. A origem do referencial é o canto superior
esquerdo.
Já consegue perceber o que faz a seguinte instrução:
DISPLAYSURF = pygame.display.set_mode((janela_largura, janela_altura))
Cria uma janela gráfica com as dimensões definidas pelas variáveis janela_largura e
janela_altura. Neste caso particular as dimensões indicadas foram 800x600.
5
modelo e a abreviação RGB vêm das três cores primárias: vermelho, verde e azul (Red, Green e
Blue, em inglês).
No código apresentado anteriormente, a instrução que desenhava o círculo tinha como segundo
parâmetro o triplo (255,255,0) que define a sua cor (amarelo). Repare-se que o amarelo é definido
pela mistura das componentes vermelho e verde.
Voltando ao nosso jogo do Pong, verifica-se a necessidade de desenhar a bola e ambas as
raquetes. Para a primeira, utilizaremos um círculo, enquanto para as raquetes utilizaremos
retângulos.
largRaquete rebordo
raiobola
compRaquete
raquete1
(bolaX, bolaY)
raquete2
#bola
pygame.draw.circle(DISPLAYSURF, (255,255,0), (bolaX, bolaY), raiobola, 0)
# raquetes
pygame.draw.rect(DISPLAYSURF, (0,0,255), raquete1, 0) # raquete azul
pygame.draw.rect(DISPLAYSURF, (255,0,0), raquete2, 0) # raquete vermelha
pygame.display.update()
No entanto, há que definir algumas variáveis auxiliares para controlar a dimensão da bola e das
raquetes, bem como a distância entre estas últimas e os limites da janela (rebordo).
Escreva o seguinte código na secção de definições:
# definicoes dos objetos graficos
raiobola = 10
compRaquete = 80
largRaquete = 20
rebordo = 20
6
E claro que também necessitamos de definir as variáveis que controlarão a posição da bola e das
raquetes ao longo do jogo e os seus valores iniciais.
O seguinte código deverá ser colocado na secção de inicialização:
# bola
bolaX = janela_largura / 2
bolaY = janela_altura / 2
# raquetes
raquete1 = pygame.Rect(rebordo,(janela_altura-compRaquete)/2,largRaquete,compRaquete)
raquete2 = pygame.Rect(janela_largura-rebordo-largRaquete,
(janela_altura-compRaquete)/2,largRaquete,compRaquete)
Este código deverá ser invocado sempre que o jogo seja reiniciado.
As peças do jogo
Qualquer jogo de computador precisa de um conjunto de variáveis que armazenem as
propriedades dos diversos elementos do jogo. Neste caso particular, consideram-se os dois
jogadores, as suas raquetes, a bola e os valores ou restrições associados ao jogo.
A bola e as raquetes já têm variáveis que permitem redefinir a sua posição. No entanto faltam as
variáveis associadas ao jogo em si, o número de golos de cada jogador e o número máximo de
golos que faz um dos jogadores ganhar o jogo.
Assim, na secção de inicialização acrescente o seguinte código:
# jogo
golos1 = 0
golos2 = 0
golosVitoria = 5
7
A raquete do jogador 1 apenas pode movimentar-se na vertical, pelo que utilizaremos apenas o valor
da ordenada (yrato) para posicionar o centro da raquete.
LAB3: Espaço para experimentar o código desenvolvido.
Acrescente este código ao anterior....
A raquete do jogador 1 começa a responder ao movimento do rato... Mas o resultado é
inesperado...
# bola
pygame.draw.circle(DISPLAYSURF, (255,255,0), (bolaX, bolaY), raiobola, 0)
# raquetes
pygame.draw.rect(DISPLAYSURF, (0,0,255), raquete1, 0) # raquete azul
pygame.draw.rect(DISPLAYSURF, (255,0,0), raquete2, 0) # raquete vermelha
A animação deve-se realizar a uma determinada frequência que, no caso das aplicações gráficas,
se denomina Frames Por Segundo (FPS). Utilizaremos para tal um temporizador para nos dar 30
FPS.
Para que se possa mais tarde alterar essa frequência, crie uma variável na secção de definições:
FPS = 30
E finalmente, insira no final do ciclo de jogo a chamada ao método do temporizador que faz o
sincronismo (fpsTemp.tick), de forma a garantir a frequência definida:
pygame.display.update()
fpsTemp.tick(FPS)
Desta forma, fica assim assegurado que as animações se processam de forma semelhante em
qualquer máquina.
LAB4: Espaço para experimentar o código desenvolvido.
Acrescente este código ao anterior....
Alternativamente pode fazer download do ficheiro pong3.py .
Agora sim, a raquete do jogador 1 é animada de forma suave, como era suposto.
Lendo do teclado
O rato é, tipicamente, um dispositivo apontador, o que significa que serve para apontar coordenadas
no ecrã. Embora possua botões, o dispositivo por excelência para indicar opções e comandos é o
teclado.
8
De forma alternativa à utilização do rato, no nosso jogo do Pong iremos possibilitar a utilização do
teclado. Para tal, a tecla "T" inibirá a ação do rato, passando a ser utilizadas as setas do cursor (cima e
baixo). A tecla "R" volta a atribuir o controlo da raquete ao rato. Esta funcionalidade é
disponibilizada no código seguinte, a colocar no bloco que percorre os eventos.
O evento a ler é o KEYDOWN. Será ainda necessário definir uma outra variável que selecione o tipo
de controlo (rato ou teclado):
# modo de controlo (rato/teclado)
controloRato = True
#################
# ciclo de jogo #
#################
while True:
# 1. Dispositivos de entrada (eventos)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEMOTION and controloRato:
xrato, yrato = event.pos
raquete1.centery = yrato
elif event.type == KEYDOWN:
if event.key == K_t:
controloRato = False
pygame.key.set_repeat(FPS)
elif event.key == K_r:
controloRato = True
pygame.key.set_repeat()
elif not controloRato: # controlo pelo teclado
if event.key == K_UP:
raquete1.centery = raquete1.centery - 5
elif event.key == K_DOWN:
raquete1.centery = raquete1.centery + 5
9
V=(xvel, yvel) V=(xvel, -yvel)
O ângulo inicial deverá ser obtido aleatoriamente. Este ângulo não deverá fazer mais de 45º com
a horizontal de forma a não tornar o jogo demasiado lento (no limite, se a velocidade fosse
vertical, a bola nunca atingiria as raquetes!). A posição inicial da bola será no centro do ecrã. Será
assim necessário criar uma função lancabola:
# procedimento para o lancamento da bola
velbola = 10
def lancabola():
# angulo aleatorio de 180 graus
angbola = math.pi * random.random()
Este procedimento deverá ser utilizado no início do jogo, pelo que se deve inserir uma chamada
na inicialização:
# bola
bolaX = janela_largura / 2
bolaY = janela_altura / 2
xvelbola, yvelbola = lancabola()
Mas para que a bola seja efetivamente animada é necessário atualizar a sua posição no ecrã a cada
ciclo de jogo. O código definido a seguir deverá ser inserido no bloco da lógica do jogo (bloco 2).
A primeira questão a considerar é a atualização da posição da bola, de acordo com a velocidade,
bem como a determinação das coordenadas de ecrã:
# 2. Logica do jogo
bolaX = bolaX + xvelbola
bolaY = bolaY + yvelbola
A segunda questão é a confrontação com os limites do jogo. Repare que sendo as coordenadas da
bola relativas ao seu centro, a confrontação terá que ter em conta o seu raio:
10
# limite superior ou inferior
if bolaY < raiobola or bolaY > janela_altura - raiobola:
yvelbola = -yvelbola
# limite esquerdo
elif bolaX < raiobola:
# golo do jogador 2
golos2 = golos2 + 1
# bola ao centro
bolaX = janela_largura / 2
bolaY = janela_altura / 2
xvelbola, yvelbola = lancabola()
# limite direito
elif bolaX > janela_largura - raiobola:
# golo do jogador 1
golos1 = golos1 + 1
# bola ao centro
bolaX = janela_largura / 2
bolaY = janela_altura / 2
xvelbola, yvelbola = lancabola()
E, finalmente, a terceira questão a considerar é a confrontação da bola com as raquetes. Dado que
a raquete é representada por um retângulo, optou-se por verificar a colisão destes com o retângulo
da bola.
# testa com as raquetes
bolaRect = pygame.Rect(bolaX-raiobola, bolaY-raiobola, 2*raiobola, 2*raiobola)
# raquete do jogador 1
if raquete1.colliderect(bolaRect) or raquete2.colliderect(bolaRect):
xvelbola = -xvelbola
11
computadores distintos, partilhando uma ligação em rede. Outra forma, é "dar inteligência" ao
computador para que possa tomar o lugar dos outros jogadores. Esta "inteligência" pode ir de algo
extremamente simples, como será o caso deste jogo, até algoritmos extremamente complexos,
como no caso de jogos de estratégia, caindo no âmbito de áreas científicas com a Inteligência
Artificial.
Neste jogo do Pong, optaremos pela segunda opção, em que o computador controlará o jogador 2.
A estratégia será simples, fazendo deslocar a raquete na vertical enquanto a coordenada y da bola
for diferente. A raquete só se deslocará quando a bola for na sua direção, e de acordo com o facto
de a bola estar acima ou abaixo da raquete, o sentido do deslocamento será distinto. O seguinte
código, a colocar no bloco 2., executa esta funcionalidade:
# deslocamento da raquete do jogador 2
if xvelbola > 0:
if bolaY > raquete2.centery:
raquete2.centery = raquete2.centery + min(velraquete2, bolaY - raquete2.centery)
else:
raquete2.centery = raquete2.centery - min(velraquete2, raquete2.centery - bolaY)
O computador é bastante eficiente e preciso a jogar, dado ter acesso às variáveis do jogo. Isto nem
sempre é uma vantagem, pois desmoraliza o jogador humano que, se vir que será quase impossível
ganhar, na maioria das vezes, desiste. Assim, é comum diminuir a precisão do seu jogo, através da
limitação do acesso a estes dados ou da introdução de "ruído", incorporando erros aleatórios. No
nosso caso, poderíamos tentar limitar a resposta do computador a um "campo de visão" limitado. Ou
seja, o computador só poderia mexer a sua raquete quanto a bola estivesse a uma distância inferior a
um determinado valor (por exemplo, metade da janela de jogo).
LAB8: Espaço para experimentar o código desenvolvido.
Acrescente este código ao anterior....
Alternativamente pode fazer download do ficheiro pong6.py .
Agora já se parece mais com um jogo. Dois jogadores conformam-se... Mas não há
vencedor!
12
# HUD
fontGolos = pygame.font.Font('freesansbold.ttf', fontSize)
# golos do jogador 1
textoGolos = fontGolos.render(str(golos1), True, (255,255,255))
rectGolos = textoGolos.get_rect()
rectGolos.centerx = janela_largura/4
rectGolos.top = rebordo
DISPLAYSURF.blit(textoGolos, rectGolos)
Este é o código para apresentar o texto do jogador 1. Em primeiro lugar é preciso criar uma font
para desenhar os caracteres. Posteriormente, cria-se o texto relativo ao número presente em
golos1 e centra-se o retângulo do texto no topo do ecrã e a meio do campo do jogador 1.
A visualização do texto é feito através do método blit, de que falaremos mais à frente...
Para o jogador 2 o código é semelhante:
# golos do jogador 2
textoGolos = fontGolos.render(str(golos2), True, (255,255,255))
rectGolos = textoGolos.get_rect()
rectGolos.centerx = janela_largura*3/4
rectGolos.top = rebordo
DISPLAYSURF.blit(textoGolos, rectGolos)
Este tipo de interface, denominada HUD (Heads-up display)2, caracteriza-se por se sobrepor de
forma transparente ao jogo, minimizando o espaço ocupado. Como princípio de usabilidade, a
interface deve mostrar toda a informação que é essencial, sem ser intrusiva.
LAB9: Espaço para experimentar o código desenvolvido.
Acrescente este código ao anterior....
Alternativamente pode fazer download do ficheiro pong7.py .
Agora já consegue saber quem está a ganhar...
2
HUD é um dispositivo utilizado na aviação e que possibilita aos pilotos lerem informação sobre
a aeronave sem terem que desviar a sua atenção do exterior para olhar para os instrumentos de
bordo.
13
Na secção de definições coloque o seguinte código:
# sprite da bola
bolaSprite = pygame.sprite.Sprite() # cria sprite
bolaSprite.image = pygame.image.load("bola.bmp").convert() # carrega imagem
bolaSprite.rect = bolaSprite.image.get_rect() # dimensões da imagem
bolaSprite.image.set_colorkey((255,0,255)); # cor magenta transparente
raiobola = bolaSprite.rect.width/2
O ficheiro bola.bmp deve estar na mesma pasta do ficheiro com o código python, ou então deve
ser incluído o path do ficheiro no nome.
O atributo colorkey da imagem permite definir a cor que se converte em transparente. Repare
também que foi necessário alterar a definição de raiobola, pois agora a nossa bola tem outra
dimensão...
E finalmente no bloco 3, da saída gráfica alteramos a forma de visualizar a bola:
# bola
#pygame.draw.circle(DISPLAYSURF, (255,255,0), (bolaX, bolaY), raiobola, 0)
bolaSprite.rect.center = (bolaX, bolaY)
DISPLAYSURF.blit(bolaSprite.image, bolaSprite.rect)
O método blit sintetiza uma imagem no ecrã num determinado retângulo e segundo as suas
propriedades.
LAB10: Espaço para experimentar o código desenvolvido.
Acrescente este código ao anterior....
Alternativamente pode fazer download do ficheiro pong8.py .
A bola está com muito melhor aspeto... Aproveite também para melhorar as raquetes.
Na realidade, a maior parte dos sprites são animados, possuindo uma sequência de imagens para a
sua definição, que é visualizada em ciclo de forma criar o efeito de animação. Frequentemente, os
personagens possuem várias animações para cada uma das ações que desenvolvem no jogo.
Já se ouvem sons...
Um jogo deve proporcionar o máximo de imersão, e como tal, deverá despertar a maior parte dos
nossos sentidos. O som é extremamente importante para a orientação do ser humano e deve ser
parte integrante do processo de desenvolvimento de um jogo. Geralmente existem duas
componentes sonoras num jogo de computador, à semelhança de um filme: os efeitos sonoros e a
banda sonora.
Os efeitos sonoros permitem ao jogador aperceber-se de determinados eventos (colisões,
explosões, acionamento de mecanismos, etc.) enquanto a banda sonora proporciona determinados
tipos de emoções em distintas fases do jogo. Para este jogo do Pong, iremos implementar os
efeitos sonoros correspondentes ao bater da bola na parede e ao bater da bola na raquete.
A primeira fase corresponde ao carregamento em memória dos sons a reproduzir ao longo do
jogo. No caso do jogo do Pong, iremos utilizar dois sons: um para o batimento da bola na raquete
(raquete.wav) e outro para o batimento da bola na parede (parede.wav). Tal realiza-se com o
seguinte código, a colocar no bloco de inicialização:
# sons
somRaquete = pygame.mixer.Sound('raquete.wav')
somParede = pygame.mixer.Sound('parede.wav')
14
Para a reprodução do som, basta utilizar o método play(). Este método possibilita o carregamento
de ficheiros WAV, MP3 ou OGG. Para tal teremos que acrescentar o seguinte código ao bloco 2,
nas instruções onde se deteta a colisão com a parede:
# limite superior ou inferior
if bolaY < raiobola or bolaY > janela_altura - raiobola:
yvelbola = -yvelbola
# som de bater na parede
somParede.play()
Ou com a raquete:
# testa com as raquetes
bolaRect = pygame.Rect(bolaX-raiobola, bolaY-raiobola, 2*raiobola, 2*raiobola)
if raquete1.colliderect(bolaRect) or raquete2.colliderect(bolaRect):
xvelbola = -xvelbola
# som da raquete
somRaquete.play()
Embora não sendo tão relevante, é comum acrescentar-se uma banda sonora aos jogos, que
permita transmitir o ambiente emocional de cada fase de um jogo. Uma das opções é reproduzir,
de forma cíclica, um determinado ficheiro de som com a banda sonora.
Este tipo de funcionalidade pode ser implementada através do objeto pygame.mixer.music, que
possibilita a audição de ficheiros WAV, MP3, or MIDI. Faça download de um ficheiro MIDI, por
exemplo, e renomeie-o como fundo.mid.3 Os métodos play e stop permitem reproduzir e parar a
música.
Nem sempre a audição da banda sonora é desejada, pelo que deverá ser possível optar por este
efeito. Para este jogo vai ser utilizada a tecla "M" para ligar a banda sonora e a tecla "S" para a
desligar, de acordo com o seguinte código, a colocar no final do bloco 1, que lê o evento
KEYDOWN:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEMOTION and controloRato:
xrato, yrato = event.pos
raquete1.centery = yrato
elif event.type == KEYDOWN:
if event.key == K_t:
controloRato = False
pygame.key.set_repeat(FPS)
elif event.key == K_r:
controloRato = True
pygame.key.set_repeat()
elif not controloRato: # controlo pelo teclado
if event.key == K_UP:
raquete1.centery = raquete1.centery - 5
elif event.key == K_DOWN:
raquete1.centery = raquete1.centery + 5
# musica de fundo
if event.key == K_m: # Musica
pygame.mixer.music.load('fundo.mid')
pygame.mixer.music.play(-1, 0.0)
elif event.key == K_s: # Silencio
pygame.mixer.music.stop()
3
Existem vários sítios Web com ficheiros gratuitos, como por exemplo:
http://www.mfiles.co.uk/classical-midi.htm
15
LAB11: Espaço para experimentar o código desenvolvido.
Acrescente este código ao anterior....
Alternativamente pode fazer download do ficheiro pong9.py.
Agora já ouve o jogo a desenrolar-se...
Tente acrescentar um som de aplausos quando os jogadores marcam golo.
As etapas do Jogo
Um jogo é composto por diversas etapas, denominadas estados, traduzindo a evolução do seu
enredo.
No jogo do Pong, consideraremos apenas 3 estados: o menu inicial, o decorrer do jogo com bola e
o menu final do jogo, que indica o vencedor. Uma vez que o motor do jogo funciona de forma
cíclica, é necessário operar esta característica através de uma máquina de estados.
Para se saber o estado atual, a cada ciclo do motor de jogo, este é armazenado numa variável,
estado, que deverá ser adicionada ao bloco de inicialização, após a definição do número de golos
que faz terminar o jogo:
# jogo
golos1 = 0
golos2 = 0
golosVitoria = 5
estado = 0
O diagrama da Figura 8 apresenta a máquina de estados que iremos implementar para este jogo.
0. Menu inicial
1. Jogo
golos1=golosVitoria OU golos2=golosVitoria
2. Final jogo
Premir qualquer tecla
”
O programa inicia-se no estado "menu inicial". Após premir a tecla "barra de espaços" passa-se
para o estado "jogo", que por sua vez, evolui para o estado "final do jogo" quando o número de
golos de qualquer um dos jogadores for igual ao número máximo, definido por golosVitoria.
Premindo qualquer tecla retorna-se ao menu inicial.
16
Tipicamente, em cada fase do ciclo de jogo uma estrutura de decisão seleciona o código a
executar com base na variável estado:
if estado == 0:
# inicio do jogo
elif estado == 1:
# jogo
else:
# final do jogo
Será assim necessário introduzir esta estrutura de controlo em todos os blocos do ciclo de jogo:
1. Dispositivos de entrada (eventos)
2. Lógica do jogo
3. Dispositivos de saída
O código atual será incluído no estado 1, mas terá que ser definido o código para os restantes
estados. Adicionalmente, será necessário definir a mudança de estado, tal como definida no diagrama
anterior. Como a maior parte das mudanças de estado se processa por teclas, a maior parte das
alterações de estado será realizada no bloco 1.
LAB12: Espaço para experimentar o código desenvolvido.
Pode fazer download do ficheiro pong10.py .
Bibliografia
Ferreira, F. Nunes; Coelho, António: Scheme na Descoberta da Programação. Livro em formato
eletrónico (e-book), FEUP Edições, 2011. ISBN 978-972-752-115-9
Sweigart, Albert: Making Games with Python & Pygame, 2012. Available online at:
http://inventwithpython.com/pygame
Pygame. Available online at:
http://www.pygame.org/
Python Programming Language – Official Website. Available online at:
http://www.python.org/
17