Você está na página 1de 73

Traduzido do Inglês para o Português - www.onlinedoctranslator.

com

PARTE II
USUÁRIO GRÁFICO
INTER FAC ESWITHP YG AME

Esses capítulos apresentampygame,um pacote externo que


adiciona funcionalidades comuns aos programas GUI. O
Pygame permite que você escreva programas Python que
tenham janelas, respondam ao mouse e ao teclado,
reproduzam sons e muito mais.
O Capítulo 5 fornece uma compreensão básica de como o pygame funciona e fornece um
modelo padrão para a construção de programas baseados em pygame. Construiremos alguns
programas simples primeiro, criaremos um programa que controle uma imagem com o teclado e,
em seguida, construiremos um programa de salto de bola.
O Capítulo 6 explica como o pygame pode ser melhor usado como um framework
orientado a objetos. Você verá como reescrever o programa ball-bouncing usando técnicas
orientadas a objetos e desenvolver botões simples e campos de entrada de texto.

O Capítulo 7 descreve o módulo pygwidgets, que contém implementações


completas de muitos widgets de interface de usuário padrão, como botões, campos de
entrada e saída, botões de opção, caixas de seleção e muito mais, todos usando
programação orientada a objetos. Todo o código está disponível para que você possa
usá-lo para construir seus próprios aplicativos. Vou dar vários exemplos.
INT RO DUCTIONTOP YG AME
5
A linguagem Python foi projetada para lidar com
entrada e saída de texto. Ele fornece a capacidade
de obter e enviar texto para o usuário, um arquivo
e a Internet. A linguagem central, no entanto, não
tem como lidar com conceitos mais modernos, como janelas,
cliques do mouse, sons e assim por diante. Então, e se você
quiser usar o Python para criar
algo mais avançado do que um programa baseado em texto? Neste capítulo vou apresentar
pygame, um pacote externo de código aberto gratuito que foi projetado para estender o
Python para permitir que os programadores criem programas de jogos. Você também pode
usar o pygame para construir outros tipos de programas interativos com uma interface
gráfica do usuário (GUI). Ele adiciona a capacidade de criar janelas, mostrar imagens,
reconhecer movimentos e cliques do mouse, reproduzir sons e muito mais. Em resumo, ele
permite que os programadores Python construam os tipos de jogos e aplicativos com os quais
os usuários de computador atuais se familiarizaram.
Não é minha intenção transformar todos vocês em programadores de jogos – mesmo
que isso possa ser um resultado divertido. Em vez disso, usarei o ambiente pygame para
tornar certas técnicas de programação orientadas a objetos mais claras e mais visuais. Ao
trabalhar com pygame para tornar os objetos visíveis em uma janela e lidar com um usuário
interagindo com esses objetos, você deve obter uma compreensão mais profunda de como
usar efetivamente as técnicas de POO.
Este capítulo fornece uma introdução geral ao pygame, portanto, a maioria das
informações e exemplos neste capítulo usarão codificação procedural. Começando com
o próximo capítulo, explicarei como usar OOP efetivamente com pygame.

Instalando o Pygame
Pygame é um pacote gratuito para download. Usaremos o gerenciador de pacotes
pip(abreviatura depip instala pacotes) para instalar pacotes Python. Conforme
mencionado na introdução, estou assumindo que você instalou a versão oficial do
Python depython.org. O programa pip está incluído como parte desse download,
então você já deve tê-lo instalado.
Ao contrário de um aplicativo padrão, você deve executar o pip na linha de comando.
Em um Mac, inicie o aplicativo Terminal (localizado naServiços de utilidade pública
subpasta dentro doFormuláriospasta). Em um sistema Windows, clique no ícone do
Windows, digitecmde pressione ENTER.

NOTA Este livro não foi testado com sistemas Linux. No entanto, a maioria, se não todo, o conteúdo deve
funcionar com ajustes mínimos. Para instalar o pygame em uma distribuição Linux, abra um
terminal da maneira que você estiver acostumado.

Digite os seguintes comandos na linha de comando:

python3 -m pip install -U pip --user python3


-m pip install -U pygame --user

O primeiro comando garante que você tenha a versão mais recente do


programa pip. A segunda linha instala a versão mais recente do pygame.
Se você tiver algum problema ao instalar o pygame, consulte a documentação
do pygame emhttps://www.pygame.org/wiki/GettingStarted. Para testar se o
pygame foi instalado corretamente, abra o IDLE (o ambiente de desenvolvimento
que acompanha a implementação padrão do Python) e, na janela do shell, digite:

importar pygame

Se você vir uma mensagem dizendo algo como “Olá da comunidade pygame” ou se
você não receber nenhuma mensagem, então o pygame foi instalado corretamente. A
falta de uma mensagem de erro indica que o Python conseguiu encontrar e carregar o
pacote pygame e está pronto para uso. Se você gostaria de ver um jogo de exemplo
usando pygame, digite o seguinte comando (que inicia uma versão doInvasores do
espaço):

python3 -m pygame.examples.aliens

90 capítulo 5
Antes de começarmos a usar o pygame, preciso explicar dois conceitos importantes. Primeiro,
explicarei como os pixels individuais são endereçados em programas que usam uma GUI. Em
seguida, discutirei os programas orientados a eventos e como eles diferem dos programas típicos
baseados em texto. Depois disso, codificaremos alguns programas que demonstram os principais
recursos do pygame.

Detalhes da janela
Uma tela de computador é composta por um grande número de linhas e colunas de
pequenos pontos chamadospíxeis(das palavraselemento de imagem). Um usuário
interage com um programa GUI por meio de uma ou mais janelas; cada janela é uma
parte retangular da tela. Os programas podem controlar a cor de qualquer pixel
individual em sua(s) janela(s). Se você estiver executando vários programas GUI, cada
programa normalmente é exibido em sua própria janela. Nesta seção, discutirei como
você aborda e altera pixels individuais em uma janela. Esses conceitos são
independentes do Python; eles são comuns a todos os computadores e são usados em
todas as linguagens de programação.

O sistema de coordenadas da janela

Você provavelmente está familiarizado com as coordenadas cartesianas em uma grade como a Figura 5-1.

eixo y

1
eixo x

-6 -5 -4 -3 -2 -1 1 2 3 4 5 6
-1

-2

-3

-4

-5

-6

Figura 5-1: O sistema de coordenadas cartesianas padrão

Introdução ao Pygame 91
Qualquer ponto em uma grade cartesiana pode ser localizado especificando suas
coordenadas x e y (nessa ordem). A origem é o ponto especificado como (0, 0) e se
encontra no centro da grade.
As coordenadas da janela do computador funcionam de maneira semelhante (Figura 5-2).

0 Máx. x

Max y

Figura 5-2: O sistema de coordenadas de uma janela de computador

No entanto, existem algumas diferenças importantes:

1. O ponto de origem (0, 0) está no canto superior esquerdo da janela.


2. O eixo y é invertido para que os valores de y comecem em zero na parte superior da janela e
aumentem à medida que você desce.

3. Os valores xey são sempre inteiros. Cada par (x, y) especifica um único pixel
na janela. Esses valores são sempre especificados em relação ao canto
superior esquerdo da janela, não à tela. Dessa forma, o usuário pode
mover a janela para qualquer lugar da tela sem afetar as coordenadas
dos elementos do programa exibidos na janela.

A tela completa do computador tem seu próprio conjunto de coordenadas (x, y) para
cada pixel e usa o mesmo tipo de sistema de coordenadas, mas os programas raramente
precisam lidar com as coordenadas da tela.
Quando escrevemos um aplicativo pygame, precisamos especificar a largura e a
altura da janela que queremos criar. Dentro da janela, podemos endereçar qualquer
pixel usando suas coordenadas x e y, como mostrado na Figura 5-3.
A Figura 5-3 mostra um pixel preto na posição (3, 5). Esse é um valor x de 3
(observe que esta é na verdade a quarta coluna, já que as coordenadas começam em
0) e um valor y de 5 (na verdade, a sexta linha). Cada pixel em uma janela é
comumente referido como umponto. Para referenciar um ponto em uma janela, você
normalmente usaria uma tupla Python. Por exemplo, você pode ter uma instrução de
atribuição como esta, com o valor x primeiro:

pixelLocalização = (3, 5)

92 capítulo 5
0 1 2 3 4 5 6 7 8 9 10 11 12 13…

10

11

12

13

Figura 5-3: Um único ponto (um único pixel) em uma janela de computador

Para mostrar uma imagem em uma janela, precisamos especificar as coordenadas


de seu ponto inicial - sempre o canto superior esquerdo da imagem - como um par (x,
y), como na Figura 5-4, onde desenhamos a imagem no local (3, 5).
Ao trabalhar com uma imagem, muitas vezes você precisará lidar com o retângulo
delimitador, que é o menor retângulo que pode ser feito que envolve completamente
todos os pixels da imagem. Um retângulo é representado em pygame por um conjunto
de quatro valores: x, y, largura, altura. O retângulo da imagem na Figura 5-4 tem
valores de 3, 5, 11, 7. Mostrarei como usar um retângulo como este em um programa
de exemplo futuro. Mesmo que sua imagem não seja retangular (por exemplo, se for
um círculo ou uma elipse), você ainda deve considerar seu retângulo delimitador para
posicionamento e detecção de colisão.

Introdução ao Pygame 93
0 1 2 3 4 5 6 7 8 9 10 11 12 13…

10

11

12

13

Figura 5-4: Uma imagem em uma janela

Cores de pixel

Vamos explorar como as cores são representadas na tela do computador. Se você tem
experiência com um programa gráfico como o Photoshop, provavelmente já sabe como
isso funciona, mas pode querer uma rápida atualização de qualquer maneira.
Cada pixel na tela é composto por uma combinação de três cores: vermelho, verde
e azul, muitas vezes referidas comoRGB. A cor exibida em qualquer pixel é composta
por alguma quantidade de vermelho, verde e azul, onde a quantidade de cada um é
especificada como um valor de 0, significando nenhum, a 255, significando intensidade
total. Portanto, existem 256 × 256 × 256 combinações possíveis, ou 16.777.216 (muitas
vezes referidas como apenas “16 milhões”) cores possíveis, para cada pixel.

As cores no pygame são fornecidas como valores RGB e as escrevemos como tuplas
Python de três números. Aqui está como criamos constantes para as cores principais:

VERMELHO = (255, 0, 0) # vermelho completo, sem verde, sem azul


VERDE = (0, 255, 0) # sem vermelho, verde completo, sem azul
AZUL = (0, 0, 255) # sem vermelho, sem verde, azul completo

94 capítulo 5
Aqui estão as definições de mais algumas cores. Você pode criar uma cor
usando qualquer combinação de três números entre 0 e 255:

PRETO = (0, 0, 0) # sem vermelho, sem verde, sem azul


BRANCO = (255, 255, 255)# vermelho completo, verde completo, azul
completo CINZA_ESCURO = (75, 75, 75) CINZA_MÉDIO = (128, 128, 128)
CINZA_CLARO = (175, 175, 175)

TEAL = (0, 128, 128)# sem vermelho, verde com metade da força, azul com metade da força
AMARELO = (255, 255, 0) ROXO = (128, 0, 128)

No pygame, você precisará especificar cores quando quiser preencher o plano de fundo de uma janela,
desenhar uma forma em uma cor, desenhar um texto em uma cor e assim por diante. Definir cores
antecipadamente como constantes de tupla as torna muito fáceis de identificar posteriormente no código.

Programas orientados a eventos

Na maioria dos programas do livro até agora, o código principal viveu em um


enquantociclo. O programa pára em uma chamada para o built-inentrada()função e espera que

alguma entrada do usuário funcione. A saída do programa é normalmente tratada usando


chamadas paraimprimir().
Em programas de GUI interativos, esse modelo não funciona mais. GUIs introduzem um
novo modelo de computação conhecido comoorientado a eventosmodelo. Programas
orientados a eventos não dependem deentrada()eimprimir();em vez disso, o usuário interage
com elementos em uma janela à vontade usando um teclado e/ou mouse ou outro dispositivo
apontador. Eles podem clicar em vários botões ou ícones, fazer seleções nos menus, fornecer
entradas em campos de texto ou dar comandos por meio de cliques ou pressionamentos de
teclas para controlar algum avatar na janela.

NOTA Chamadas paraimprimir()ainda pode ser muito útil para depuração, quando usado para escrever resultados
intermediários.

Central para a programação orientada a eventos é o conceito de umevento. Os eventos são


difíceis de definir e são melhor descritos com exemplos, como um clique do mouse e um
pressionamento de tecla (cada um dos quais é na verdade composto de dois eventos: mouse para
baixo e mouse para cima e tecla para baixo e tecla para cima, respectivamente). Aqui está a minha
definição de trabalho.

evento Algo que acontece enquanto seu programa está sendo executado e que seu programa deseja ou
precisa responder. A maioria dos eventos são gerados por ações do usuário

Um programa GUI orientado a eventos é executado constantemente em um loop infinito.


Cada vez que passa pelo loop, o programa verifica se há novos eventos aos quais precisa
reagir e executa o código apropriado para lidar com esses eventos. Além disso, a cada vez que
passa pelo loop, o programa precisa redesenhar todos os elementos na janela para atualizar o
que o usuário vê.

Introdução ao Pygame 95
Por exemplo, digamos que temos um programa GUI simples que exibe dois
botões: Bark e Meow. Quando clicado, o botão Bark reproduz o som de um
cachorro latindo e o botão Meow reproduz o som de um gato miando (Figura 5-5).

Figura 5-5: Um programa simples


com dois botões

O usuário pode clicar nesses botões em qualquer ordem e a qualquer


momento. Para lidar com as ações do usuário, o programa é executado em um
loop e verifica constantemente se algum botão foi clicado. Quando recebe um
evento de mouse para baixo em um botão, o programa lembra que o botão foi
clicado e desenha a imagem pressionada desse botão. Quando recebe um evento
mouse up no botão, ele lembra o novo estado e redesenha o botão com sua
aparência original e reproduz o som apropriado. Como o loop principal é
executado tão rapidamente, o usuário percebe que o som é reproduzido
imediatamente após clicar no botão. Cada vez que passa pelo loop, o programa
redesenha ambos os botões com uma imagem correspondente ao estado atual
de cada botão.

Usando Pygame
A princípio, o pygame pode parecer um pacote extremamente grande com muitas chamadas
diferentes disponíveis. Embora seja grande, na verdade não há muito que você precise
entender para colocar um pequeno programa em funcionamento. Para apresentar o pygame,
primeiro fornecerei um modelo que você pode usar para todos os programas pygame que
você criar. Então eu vou construir sobre esse modelo, adicionando peças-chave de
funcionalidade pouco a pouco.
Nas seções a seguir, mostrarei como:

• Abra uma janela em branco.


• Mostre uma imagem.

• Detectar um clique do mouse.

• Detecta pressionamentos de tecla simples e contínuos.

• Crie uma animação simples.


• Reproduza efeitos sonoros e sons de fundo.
• Desenhe formas.

No próximo capítulo, continuaremos a discussão do pygame e você verá


como:

• Animar muitos objetos.


• Construir e reagir a um botão.
• Crie um campo de exibição de texto.

96 capítulo 5
Abrindo uma janela em branco
Como eu disse anteriormente, os programas pygame são executados constantemente em
um loop, verificando eventos. Pode ajudar pensar em seu programa como uma animação,
onde cada passagem pelo loop principal é um quadro. O usuário pode clicar em algo durante
qualquer quadro, e seu programa deve não apenas responder a essa entrada, mas também
acompanhar tudo o que precisa desenhar na janela. Por exemplo, em um programa de
exemplo mais adiante neste capítulo, moveremos uma bola pela janela para que em cada
quadro a bola seja desenhada em uma posição ligeiramente diferente.

A Listagem 5-1 é um modelo genérico que você pode usar como ponto de partida
para todos os seus programas pygame. Este programa abre uma janela e pinta todo o
conteúdo de preto. A única coisa que o usuário pode fazer é clicar no botão fechar para
sair do programa.

Arquivo: PygameDemo0_WindowOnly/PygameWindowOnly.py

# pygame demo 0 - apenas janela

#1 - Importar pacotes
importar pygame
de pygame.locals import *
import sys

# 2 - Definir constantes
PRETO = (0, 0, 0)
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
FRAMES_PER_SECOND = 30

#3 - Inicialize o mundo
pygame.init()
janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

# 4 - Carregar assets: imagem(s), som(s), etc.

#5 - Inicialize as variáveis

# 6 - Loop para sempre


enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
# Clicou no botão fechar? Saia do pygame e termine o programa if
event.type == pygame.QUIT:
pygame.quit()
sys.exit()

# 8 - Faça qualquer ação "por frame"

# 9 - Limpe a janela

Introdução ao Pygame 97
window.fill(PRETO)

#10 - Desenhe todos os elementos da janela

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND)

Listagem 5-1: Um modelo para criar programas pygame

Vamos percorrer as diferentes partes deste modelo:

1. Importe pacotes.
O modelo começa com oimportardeclarações. Primeiro importamos o próprio pacote
pygame, depois algumas constantes definidas dentro do pygame que usaremos mais
tarde. A última importação é asistemapacote, que usaremos para sair do nosso programa.

2. Defina constantes.
Em seguida, definimos quaisquer constantes para nosso programa. Primeiro definimos o
valor RGB paraPRETO,que usaremos para pintar o fundo da nossa janela. Em seguida,
definimos constantes para a largura e altura da nossa janela em pixels e uma constante
para a taxa de atualização do nosso programa. Este número define o número máximo de
vezes que o programa fará um loop (e, portanto, redesenhará a janela) por segundo.
Nosso valor de 30 é bastante típico. Se a quantidade de trabalho feito em nosso loop
principal for excessiva, o programa pode ser executado mais lentamente que esse valor,
mas nunca será executado mais rápido. Uma taxa de atualização muito alta pode fazer
com que o programa seja executado muito rápido. Em nosso exemplo de bola, isso
significa que a bola pode quicar na janela mais rápido do que o pretendido.

3. Inicialize o ambiente pygame.


Nesta seção, chamamos uma função que diz ao pygame para inicializar a si mesmo.
Pedimos então ao pygame para criar uma janela para o nosso programa com o
pygame.display.set_mode()função e passe na largura e altura desejadas da
janela. Finalmente, chamamos outra função pygame para criar um objeto
clock, que será usado na parte inferior do nosso loop principal para manter
nossa taxa de quadros máxima.

4. Carregue recursos: imagem(ns), som(s) e assim por diante.

Esta é uma seção de espaço reservado, na qual eventualmente adicionaremos código para
carregar imagens externas, sons e assim por diante do disco para uso em nosso programa.
Neste programa básico não estamos usando nenhum ativo externo, então esta seção está
vazia por enquanto.

5. Inicialize as variáveis.
Aqui, eventualmente, inicializaremos quaisquer variáveis que nosso programa
usará. Atualmente não temos nenhum, então não temos código aqui.

98 capítulo 5
6. Faça um loop para sempre.

Aqui começamos nosso loop principal. Este é um simplesenquanto verdadeiroLoop infinito.


Novamente, você pode pensar em cada iteração através do loop principal como um quadro
em uma animação.

7. Verifique e lide com eventos; comumente referido como oloop de eventos.


Nesta seção, chamamospygame.event.get()para obter uma lista dos eventos que
aconteceram desde a última vez que verificamos (a última vez que o loop principal
foi executado), então faça uma iteração pela lista de eventos. Cada evento
relatado ao programa é um objeto e cada objeto de evento tem um tipo. Se
nenhum evento ocorreu, esta seção é ignorada.

Neste programa mínimo, onde a única ação que um usuário pode realizar é
fechar a janela, o único tipo de evento que verificamos é a constante
pygame.QUIT,gerado pelo pygame quando o usuário clica no botão fechar. Se encontrarmos

esse evento, dizemos ao pygame para sair, o que libera todos os recursos que ele estava
usando. Então desistimos do nosso programa.

8. Faça qualquer ação “por quadro”.


Nesta seção, eventualmente, colocaremos qualquer código que precise ser
executado em cada quadro. Isso pode envolver mover coisas na janela ou verificar
colisões entre elementos. Neste programa mínimo, não temos nada para fazer
aqui.

9. Limpe a janela.
Em cada iteração através do loop principal, nosso programa deve redesenhar tudo na
janela, o que significa que precisamos limpá-lo primeiro. A abordagem mais simples é
apenas preencher a janela com uma cor, o que fazemos aqui com uma chamada para
janela.preencher(),especificando um fundo preto. Também poderíamos desenhar uma
imagem de fundo, mas vamos adiar isso por enquanto.

10. Desenhe todos os elementos da janela.

Aqui colocaremos o código para desenhar tudo o que queremos mostrar em nossa
janela. Neste programa de exemplo não há nada para desenhar.

Em programas reais, as coisas são desenhadas na ordem em que aparecem no


código, em camadas da última para a frente. Por exemplo, suponha que
queremos desenhar dois círculos parcialmente sobrepostos, A e B. Se
desenharmos A primeiro, A aparecerá atrás de B e partes de A serão obscurecidas
por B. Se desenharmos B primeiro e depois A, o oposto acontece, e vemos A na
frente de B. Este é um mapeamento natural equivalente às camadas em
programas gráficos como o Photoshop.

11. Atualize a janela.


Esta linha diz ao pygame para pegar todo o desenho que incluímos e mostrá-lo
na janela. O Pygame realmente faz todo o desenho nas etapas 8, 9 e 10 em um
buffer fora da tela. Quando você diz ao pygame para atualizar, ele pega o
conteúdo desse buffer fora da tela e o coloca na janela real.

Introdução ao Pygame 99
12. Desacelere um pouco as coisas.

Os computadores são muito rápidos e, se o loop continuar para a próxima iteração


imediatamente sem pausa, o programa pode ser executado mais rapidamente do que a taxa
de quadros designada. A linha nesta seção diz ao pygame para esperar até que um
determinado período de tempo tenha decorrido para que os quadros do nosso programa
sejam executados na taxa de quadros que especificamos. Isso é importante para garantir que
o programa seja executado em uma taxa consistente, independente da velocidade do
computador em que está sendo executado.

Quando você executa este programa, o programa apenas exibe uma janela em branco
preenchida com preto. Para encerrar o programa, clique no botão Fechar na barra de título.

Desenhando uma Imagem

Vamos desenhar algo na janela. Há duas partes para mostrar uma imagem gráfica:
primeiro carregamos a imagem na memória do computador, depois exibimos a
imagem na janela do aplicativo.
Com o pygame, todas as imagens (e sons) precisam ser mantidas em arquivos externos
ao seu código. O Pygame suporta muitos formatos de arquivos gráficos padrão, incluindo.png,
.jpg, e.gif. Neste programa vamos carregar uma imagem de uma bola do arquivobola.png.
Como lembrete, o código e os recursos associados a todas as principais listagens deste livro
estão disponíveis para download emhttps://www.nostarch.com/objectorientedpython/ehttps://
github.com/IrvKalb/Object-Oriented-Python-Code/.
Embora só precisemos de um arquivo gráfico neste programa, é uma boa ideia usar uma
abordagem consistente para lidar com arquivos gráficos e de som, então vou apresentar um
para você aqui. Primeiro, crie uma pasta de projeto. Coloque seu programa principal nessa
pasta, junto com quaisquer arquivos relacionados que contenham classes e funções do
Python. Em seguida, dentro da pasta do projeto, crie umimagenspasta na qual você colocará
os arquivos de imagem que deseja usar em seu programa. Crie também umsonspasta e
coloque os arquivos de som que deseja usar lá. A Figura 5-6 mostra a estrutura sugerida.
Todos os programas de exemplo neste livro usarão este layout de pasta de projeto.

Figura 5-6: Hierarquia de pastas do projeto sugerida

UMAcaminho(também chamado denome do caminho) é uma string que identifica


exclusivamente o local de um arquivo ou pasta em um computador. Para carregar um arquivo
gráfico ou de som em seu programa, você deve especificar o caminho para o arquivo. Existem
dois tipos de caminhos: relativos e absolutos.
UMAcaminho relativoé um relativo à pasta atual, geralmente chamada dediretório de
trabalho atual. Quando você executa um programa usando um IDE como IDLE ou

100 capítulo 5
PyCharm, ele define a pasta atual para aquela que contém seu programa Python principal para que
você possa usar caminhos relativos com facilidade. Neste livro, assumirei que você está usando um
IDE e representará todos os caminhos como caminhos relativos.
O caminho relativo para um arquivo gráfico (por exemplo,bola.png) na mesma pasta
que seu arquivo principal do Python seria apenas o nome do arquivo como uma string (por
exemplo, 'bola.png').Usando a estrutura de projeto sugerida, o relativo
caminho seria 'imagens/bola.png'.
Isso diz que dentro da pasta do projeto haverá outra pasta chamada imagens, e dentro
dessa pasta há um arquivo chamadobola.png. Em strings de caminho, os nomes das pastas
são separados pelo caractere barra.
No entanto, se você espera executar seu programa a partir da linha de comando,
precisará construir caminhos absolutos para todos os arquivos. Umcaminho absolutoé aquele
que começa na raiz do sistema de arquivos e inclui toda a hierarquia de pastas do seu arquivo.
Para construir um caminho absoluto para qualquer arquivo, você pode usar um código como
este, que cria uma string de caminho absoluto para obola.pngarquivo noimagenspasta dentro
da pasta do projeto:

do caminho de importação pathlib

# Coloque isso na seção #2, definindo uma constante


BASE_PATH = Caminho(__file__).resolve().parent

# Construa um caminho para o arquivo na pasta de imagens


pathToBall = BASE_PATH + 'imagens/bola.png'

Agora vamos criar o código do programa ball, começando com o modelo anterior de 12
etapas e adicionando apenas duas novas linhas de código, conforme mostrado na Listagem 5-2.

Arquivo: PygameDemo1_OneImage/PygameOneImage.py

# pygame demo 1 – desenhe uma imagem

- - - recorte ---
#3 - Inicialize o mundo
pygame.init()
janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

# 4 - Carregar assets: imagem(s), som(s), etc. 1


ballImage = pygame.image.load('images/ball.png')

#5 - Inicialize as variáveis

- - - recorte ---

#10 - Desenhe todos os elementos da janela


# puxa a bola na posição 100 através (x) e 200 para baixo (y) 2
window.blit(ballImage, (100, 200))

#11 - Atualize a janela

Introdução ao Pygame 101


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND) # faz o pygame esperar

Listagem 5-2: Carregue uma imagem e desenhe-a em cada quadro.

Primeiro, dizemos ao pygame para encontrar o arquivo que contém a imagem


da bola e carregar essa imagem na memória1.A variávelballImageagora se refere à
imagem da bola. Observe que essa instrução de atribuição é executada apenas uma
vez, antes do início do loop principal.

NOTA Na documentação oficial do pygame, cada imagem, incluindo a janela do aplicativo, é conhecida
comosuperfície. Usarei termos mais específicos: vou me referir à janela do aplicativo simplesmente
como umjanelae para qualquer imagem carregada de um arquivo externo como imagem. reservo o
prazosuperfíciepara qualquer imagem desenhada em tempo real.

Nós então dizemos ao programa para desenhar a bola2toda vez que passamos pelo loop
principal. Especificamos a localização que representa a posição para colocar o canto superior
esquerdo do retângulo delimitador da imagem, normalmente como uma tupla de
coordenadas x e y.
O nome da funçãoblit()é uma referência muito antiga às palavrastransferência de
bloco de bits, mas neste contexto significa apenas “desenhar”. Como o programa
carregou a imagem da bola anteriormente, o pygame sabe o tamanho da imagem,
então só precisamos dizer onde desenhar a bola. Na Listagem 5-2, damos um valor x
de 100 e um valor y de 200.
Quando você executa o programa, em cada iteração através do loop (30
vezes por segundo) cada pixel na janela é definido como preto, então a bola é
desenhada sobre o fundo. Do ponto de vista do usuário, parece que nada está
acontecendo - a bola apenas fica em um ponto com o canto superior esquerdo
de seu retângulo delimitador no local (100, 200).

Detectando um clique do mouse

Em seguida, permitiremos que nosso programa detecte e reaja a um clique do


mouse. O usuário poderá clicar na bola para fazê-la aparecer em outro lugar da
janela. Quando o programa detecta um clique do mouse na bola, ele escolhe
aleatoriamente novas coordenadas e desenha a bola naquele novo local. Em vez de
usar coordenadas codificadas de (100, 200), criaremos duas variáveis,bolaX
ebola,e refira-se às coordenadas da bola na janela como a tupla (bolaX,
bolaY).A Listagem 5-3 fornece o código.

Arquivo: PygameDemo2_ImageClickAndMove/PygameImageClickAndMove.py

# pygame demo 2 - uma imagem, clique e mova

#1 - Importar pacotes
importar pygame
de pygame.locals import *
import sys

102 capítulo 5
1importar aleatório

# 2 - Definir constantes
PRETO = (0, 0, 0)
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
FRAMES_PER_SECOND = 30
2BALL_WIDTH_HEIGHT = 100
MAX_WIDTH = WINDOW_WIDTH - BALL_WIDTH_HEIGHT
MAX_HEIGHT = WINDOW_HEIGHT - BALL_WIDTH_HEIGHT

#3 - Inicialize o mundo
pygame.init()
janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

# 4 - Carregar assets: imagem(s), som(s), etc.


ballImage = pygame.image.load('images/ball.png')

#5 - Inicialize as variáveis
3bolaX = random.randrange(MAX_WIDTH)
bolaY = random.randrange(MAX_HEIGHT)
4ballRect = pygame.Rect(ballX, ballY, BALL_WIDTH_HEIGHT, BALL_WIDTH_HEIGHT)

# 6 - Loop para sempre


enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
# Clicou no botão fechar? Saia do pygame e termine o programa if
event.type == pygame.QUIT:
pygame.quit()
sys.exit()

# Veja se o usuário clicou


5if event.type == pygame.MOUSEBUTTONUP:
# mouseX, mouseY = event.pos # Poderia fazer isso se precisássemos

# Verifique se o clique foi no reto da bola


# Se sim, escolha um novo local aleatório
6if ballRect.collidepoint(event.pos):
bolaX = random.randrange(MAX_WIDTH)
ballY = random.randrange(MAX_HEIGHT)
ballRect = pygame.Rect(ballX, ballY, BALL_WIDTH_HEIGHT,
BALL_WIDTH_HEIGHT)

# 8 Faça qualquer ação "por quadro"

# 9 - Limpe a janela
window.fill(PRETO)

#10 - Desenhe todos os elementos da janela


# Desenhe a bola no local aleatório 7
window.blit(ballImage, (ballX, ballY))

Introdução ao Pygame 103


#11 - Atualize a janela
pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND) # faz o pygame esperar

Listagem 5-3: Detectando um clique do mouse e agindo sobre ele

Como precisamos gerar números aleatórios para as coordenadas da bola,


importamos oaleatóriapacote1.
Em seguida, adicionamos uma nova constante para definir a altura e a largura da nossa
imagem como 100 pixels2.Também criamos mais duas constantes para limitar as
coordenadas máximas de largura e altura. Ao usar essas constantes em vez do tamanho da
janela, garantimos que nossa imagem da bola sempre aparecerá totalmente dentro da janela
(lembre-se que quando nos referimos à localização de uma imagem, estamos especificando a
posição de seu canto superior esquerdo) . Usamos essas constantes para escolher valores
aleatórios para as coordenadas x e y iniciais para nossa bola3.

A seguir, chamamospygame.Rect()para criar um retângulo4.Definir um retângulo


requer quatro parâmetros - uma coordenada x, uma coordenada y, uma largura e uma
altura, nesta ordem:

<rectObject>=pygame.Rect(<x>,<e>,<largura>,<altura>)

Isso retorna um objeto retângulo pygame, oureto.Usaremos o retângulo da


bola no processamento dos eventos.
Também adicionamos código para verificar se o usuário clicou com o mouse. Como
mencionado, um clique do mouse é, na verdade, composto de dois eventos diferentes: um evento do
mouse para baixo e um evento do mouse para cima. Como o evento mouse up é normalmente
usado para sinalizar a ativação, procuraremos apenas esse evento aqui. Este evento é sinalizado
por um novotipo de eventovalor depygame.MOUSEBUTTONUP5.Quando descobrimos que um
mouse up ocorreu, então verificaremos se o local onde o usuário clicou
estava dentro do retângulo atual da bola.
Quando o pygame detecta que um evento aconteceu, ele cria um objeto de
evento contendo muitos dados. Neste caso, só nos importamos com as
coordenadas x e y onde o evento aconteceu. Recuperamos a posição (x, y) do
clique usandoevent.pos,que fornece uma tupla de dois valores.

NOTA Se precisarmos separar as coordenadas x e y do clique, podemos descompactar a tupla e


armazenar os valores em duas variáveis como esta:

mouseX, mouseY = event.pos

Agora verificamos se o evento aconteceu dentro do retângulo da bola


usandoponto de colisão()6,cuja sintaxe é:

<booleanVariable>=<someRectangle>.collidepoint(<algumXYLocalização>)

104 capítulo 5
O método retorna um booleanoVerdadeirose o ponto dado está dentro do
retângulo. Se o usuário clicou na bola, selecionamos aleatoriamente novos valores para
bolaXebolaY.Usamos esses valores para criar um novo retângulo para a bola no
novo local aleatório.
A única mudança aqui é que sempre desenhamos a bola no local dado
pela tupla (bolaX, bolaY)7.O efeito é que sempre que o usuário clica dentro
do retângulo da bola, a bola parece se mover para algum novo local
aleatório na janela.

Manipulando o teclado
O próximo passo é permitir que o usuário controle algum aspecto do programa através do teclado.
Existem duas maneiras diferentes de lidar com as interações do teclado do usuário: quando uma
tecla individual é pressionada e quando um usuário mantém pressionada uma tecla para indicar que
uma ação deve ocorrer enquanto essa tecla estiver pressionada (conhecida comomodo contínuo).

Reconhecendo pressionamentos de teclas individuais

Assim como os cliques do mouse, cada pressionamento de tecla gera dois eventos: tecla para baixo e tecla
para cima. Os dois eventos têm diferentes tipos de eventos:pygame.KEYDOWNepygame.KEYUP.
A Listagem 5-4 mostra um pequeno programa de exemplo que permite ao
usuário mover a imagem da bola na janela usando o teclado. O programa também
mostra um retângulo de destino na janela. O objetivo do usuário é mover a imagem da
bola para que ela se sobreponha à imagem alvo.

Arquivo: PygameDemo3_MoveByKeyboard/PygameMoveByKeyboardOncePerKey.py

# pygame demo 3(a) - uma imagem, movida pelo teclado

#1 - Importar pacotes
importar pygame
de pygame.locals import *
import sys
importar aleatório

# 2 - Definir constantes
PRETO = (0, 0, 0)
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
FRAMES_PER_SECOND = 30
BALL_WIDTH_HEIGHT = 100
MAX_WIDTH = WINDOW_WIDTH - BALL_WIDTH_HEIGHT
MAX_HEIGHT = WINDOW_HEIGHT - BALL_WIDTH_HEIGHT 1
META_X = 400
TARGET_Y = 320
TARGET_WIDTH_HEIGHT = 120
N_PIXELS_TO_MOVE = 3

#3 - Inicialize o mundo
pygame.init()

Introdução ao Pygame 105


janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

# 4 - Carregar assets: imagem(s), som(s), etc. ballImage =


pygame.image.load('images/ball.png') 2targetImage =
pygame.image.load('images/target.jpg')

#5 - Inicialize as variáveis bolaX =


random.randrange(MAX_WIDTH) ballY =
random.randrange(MAX_HEIGHT)
targetRect = pygame.Rect(TARGET_X, TARGET_Y, TARGET_WIDTH_HEIGHT, TARGET_
WIDTH_HEIGHT)

# 6 - Loop para sempre


enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
# Clicou no botão fechar? Saia do pygame e termine o programa if
event.type == pygame.QUIT:
pygame.quit()
sys.exit()

# Veja se o usuário pressionou uma tecla 3


elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
bolaX = bolaX - N_PIXELS_TO_MOVE
elif event.key == pygame.K_RIGHT:
bolaX = bolaX + N_PIXELS_TO_MOVE
elif event.key == pygame.K_UP:
bolaY = bolaY - N_PIXELS_TO_MOVE
elif event.key == pygame.K_DOWN:
bolaY = bolaY + N_PIXELS_TO_MOVE

# 8 Faça qualquer ação "por quadro"


# Verifique se a bola está colidindo com o alvo 4
ballRect = pygame.Rect(ballX, ballY,
BALL_WIDTH_HEIGHT, BALL_WIDTH_HEIGHT)
5if ballRect.colliderect(targetRect):
print('A bola está tocando o alvo')

# 9 - Limpe a janela
window.fill(PRETO)

#10 - Desenhe todos os elementos da janela


6window.blit(targetImage, (TARGET_X, TARGET_Y))# desenha o alvo
window.blit(ballImage, (ballX, ballY))# saca a bola

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND) # faz o pygame esperar

Listagem 5-4: Detectando e agindo ao pressionar uma única tecla

106 capítulo 5
Primeiro adicionamos algumas novas constantes1para definir as coordenadas xey
do canto superior esquerdo do retângulo de destino e a largura e altura do destino. Em
seguida, carregamos a imagem do retângulo alvo2.
No loop em que procuramos por eventos, adicionamos um teste para um pressionamento de
tecla verificando um evento do tipopygame.KEYDOWN3.Se um evento de tecla pressionada for
detectado, analisamos o evento para descobrir qual tecla foi pressionada. Cada tecla tem uma
constante associada em pygame, então aqui verificamos se o usuário pressionou a seta para a
esquerda, para cima, para baixo ou para a direita. Para cada uma dessas chaves, modificamos o
valor da coordenada x ou y da bola apropriadamente por um pequeno número de pixels.

Em seguida, criamos um pygameretoobjeto para a bola com base em suas


coordenadas x e y e sua altura e largura4.Podemos verificar se dois retângulos se
sobrepõem com a seguinte chamada:

<booleanVariable>=<rect1>.colliderect(<rect2>)

Esta chamada compara dois retângulos e retornaVerdadeirose eles se


sobrepõem ouFalsose não. Comparamos o retângulo da bola com o retângulo alvo
5,e se eles se sobrepuserem, o programa imprime “A bola está tocando o alvo” na
janela do shell.
A última mudança é onde desenhamos o alvo e a bola. O alvo é
desenhado primeiro para que quando os dois se sobreponham, a bola
apareça sobre o alvo6.
Quando o programa é executado, se o retângulo da bola se sobrepuser
ao retângulo do alvo, a mensagem é gravada na janela do shell. Se você
mover a bola para longe do alvo, a mensagem para de ser escrita.

Lidando com Teclas Repetitivas no Modo Contínuo

A segunda maneira de lidar com as interações do teclado no pygame évotaçãoo


teclado. Isso envolve pedir ao pygame uma lista representando quais teclas estão
atualmente inativas em cada quadro usando a seguinte chamada:

<aTuplo>=pygame.key.get_pressed()

Essa chamada retorna uma tupla de 0s e 1s representando o estado de cada chave: 0 se


a chave estiver ativa, 1 se estiver inativa. Você pode então usar constantes definidas no
pygame como um índice na tupla retornada para ver se umespecialchave está abaixada. Por
exemplo, as seguintes linhas podem ser usadas para determinar o estado da tecla A:

keyPressedTuple = pygame.key.get_pressed()
# Agora use uma constante para obter o elemento apropriado da tupla
aIsDown = keyPressedTuple[pygame.K_a]

A lista completa de constantes representando todas as chaves definidas em pygame pode


ser encontrada emhttps://www.pygame.org/docs/ref/key.html.

Introdução ao Pygame 107


O código na Listagem 5-5 mostra como podemos usar essa técnica para mover
uma imagem continuamente, em vez de uma vez por pressionamento de tecla. Nesta
versão, movemos o manuseio do teclado da seção #7 para a seção #8. O restante do
código é idêntico à versão anterior na Listagem 5-4.

Arquivo: PygameDemo3_MoveByKeyboard/PygameMoveByKeyboardContinuous.py

# pygame demo 3(b) - uma imagem, modo contínuo, mova enquanto uma tecla estiver pressionada

- - - recorte ---
# 7 - Verifique e manipule eventos
para evento em pygame.event.get():
# Clicou no botão fechar? Saia do pygame e termine o programa if
event.type == pygame.QUIT:
pygame.quit()
sys.exit()

# 8 - Faça qualquer ação "por frame"


# Verifica se o usuário está pressionando as teclas

1keyPressedTuple = pygame.key.get_pressed()

if keyPressedTuple[pygame.K_LEFT]:# movendo para a esquerda


bolaX = bolaX - N_PIXELS_TO_MOVE

if keyPressedTuple[pygame.K_RIGHT]:# movendo para a direita


bolaX = bolaX + N_PIXELS_TO_MOVE

if keyPressedTuple[pygame.K_UP]:#subindo
bolaY = bolaY - N_PIXELS_TO_MOVE

if keyPressedTuple[pygame.K_DOWN]:# Movendo para baixo


bolaY = bolaY + N_PIXELS_TO_MOVE

# Verifique se a bola está colidindo com o alvo ballRect


= pygame.Rect(ballX, ballY,
BALL_WIDTH_HEIGHT, BALL_WIDTH_HEIGHT)
if ballRect.colliderect(targetRect):
print('A bola está tocando o alvo')
- - - recorte ---

Listagem 5-5: Manuseio de teclas pressionadas

O código de manipulação do teclado na Listagem 5-5 não depende de eventos,


então colocamos o novo código fora doporloop que itera por todos os eventos
retornados por pygame1.
Como estamos fazendo essa verificação em cada quadro, o movimento da bola
parecerá contínuo enquanto o usuário mantiver uma tecla pressionada. Por exemplo,
se o usuário pressionar e segurar a tecla de seta para a direita, este código adicionará 3
ao valor dabolaXcoordenar em cada quadro, e o usuário verá a bola movendo-se
suavemente para a direita. Quando eles param de pressionar a tecla, o movimento
para.

108 capítulo 5
A outra mudança é que essa abordagem permite verificar se várias chaves estão inativas
ao mesmo tempo. Por exemplo, se o usuário pressionar e segurar as teclas de seta para a
esquerda e para baixo, a bola se moverá diagonalmente para baixo e para a esquerda. Você
pode verificar quantas teclas estão sendo pressionadas conforme desejar. No entanto, o
número desimultâneopressionamentos de teclas que podem ser detectados são limitados
pelo sistema operacional, o hardware do teclado e muitos outros fatores. O limite típico é de
cerca de quatro chaves, mas sua milhagem pode variar.

Criando uma animação baseada em localização

Em seguida, construiremos uma animação baseada em localização. Este código nos


permitirá mover uma imagem diagonalmente e então fazê-la parecer saltar das bordas da
janela. Esta era uma técnica favorita de protetores de tela em monitores antigos baseados
em CRT, para evitar a queima em uma imagem estática.
Mudaremos ligeiramente a localização da nossa imagem em cada frame. Também
verificaremos se o resultado desse movimento colocaria qualquer parte da imagem fora
de um dos limites da janela e, nesse caso, reverteria o movimento nessa direção. Por
exemplo, se a imagem estivesse se movendo para baixo e cruzasse a parte inferior da
janela, inverteríamos a direção e faríamos a imagem começar a se mover para cima.

Usaremos novamente o mesmo modelo inicial. A Listagem 5-6 fornece o código-


fonte completo.

Arquivo: PygameDemo4_OneBallBounce/PygameOneBallBounceXY.py

# pygame demo 4(a) - uma imagem, salte pela janela usando (x, y) coordenadas

#1 - Importar pacotes
importar pygame
de pygame.locals import *
import sys
importar aleatório

# 2 - Definir constantes
PRETO = (0, 0, 0)
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
FRAMES_PER_SECOND = 30
BALL_WIDTH_HEIGHT = 100
N_PIXELS_PER_FRAME = 3

#3 - Inicialize o mundo
pygame.init()
janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

# 4 - Carregar assets: imagem(s), som(s), etc.


ballImage = pygame.image.load('images/ball.png')

#5 - Inicialize as variáveis

Introdução ao Pygame 109


MAX_WIDTH = WINDOW_WIDTH - BALL_WIDTH_HEIGHT
MAX_HEIGHT = WINDOW_HEIGHT - BALL_WIDTH_HEIGHT 1
bolaX = random.randrange(MAX_WIDTH)
ballY = random.randrange(MAX_HEIGHT)
xSpeed = N_PIXELS_PER_FRAME
yVelocidade = N_PIXELS_PER_FRAME

# 6 - Loop para sempre


enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
# Clicou no botão fechar? Saia do pygame e termine o programa if
event.type == pygame.QUIT:
pygame.quit()
sys.exit()

# 8 - Faça qualquer ação "por frame" 2se


(bolaX < 0) ou (bolaX >= MAX_WIDTH):
xVelocidade = -xVelocidade# inverta a direção X

se (bolaY < 0) ou (bolaY >= MAX_HEIGHT):


yVelocidade = -yVelocidade# inverta direção Y

# Atualize a localização da bola, usando a velocidade em duas direções 3


bolaX = bolaX + xVelocidade
bolaY = bolaY + yVelocidade

# 9 - Limpe a janela antes de desenhá-la novamente


window.fill(PRETO)

#10 - Desenhe os elementos da janela


window.blit(ballImage, (ballX, ballY))

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND)

Listagem 5-6: Uma animação baseada em localização, quicando uma bola pela janela

Começamos criando e inicializando as duas variáveisxVelocidadeeyVelocidade1, que


determinam quão longe e em que direção a imagem deve se mover em cada quadro.
Inicializamos ambas as variáveis com o número de pixels a serem movidos por quadro
(3), para que a imagem comece movendo três pixels para a direita (a direção x positiva) e
três pixels para baixo (a direção y positiva).
Na parte principal do programa, lidamos com as coordenadas x e y
separadamente2.Primeiro, verificamos se a coordenada x da bola é menor que zero,
o que significa que parte da imagem está fora da borda esquerda ou além da
LARGURA MÁXIMApixel e tão efetivamente fora da borda direita. Se qualquer um desses
for o caso, invertemos o sinal da velocidade na direção x, o que significa que ela irá na
direção oposta. Por exemplo, se a bola estava se movendo para a direita

110 capítulo 5
e saísse da borda direita, mudaríamos o valor dexVelocidadea partir de3para
- 3fazer com que a bola comece a se mover para a esquerda e vice-versa. Em seguida,

fazemos uma verificação semelhante para a coordenada y para fazer a bola quicar na
borda superior ou inferior, conforme necessário.
Finalmente, atualizamos a posição da bola adicionando oxVelocidadepara o
bolaXcoordenar e adicionar oyVelocidadepara obolacoordenada3.Isso posiciona a
bola em um novo local em ambos os eixos.
Na parte inferior do loop principal, desenhamos a bola. Como estamos
atualizando os valores debolaXebolaem cada quadro, a bola parece animar
suavemente. Experimente. Sempre que a bola atinge qualquer borda, ela parece
ricochetear.

Usando rects Pygame


A seguir apresentarei uma forma diferente de alcançar o mesmo resultado. Em vez de
acompanhar as coordenadas x e y atuais da bola em variáveis separadas, usaremos o
retoda bola, atualize oretocada quadro, e verifique se a execução da atualização faria
com que qualquer parte doretopara mover para fora de uma borda da janela. Isso
resulta em menos variáveis, e porque vamos começar fazendo uma chamada para
obter oretode uma imagem, ele funcionará com imagens de qualquer tamanho.

Quando você cria umretoobjeto, além de lembrar oesquerda, superior, largura,ealtura


como atributos do retângulo, esse objeto também calcula e mantém vários outros
atributos para você. Você pode acessar qualquer um desses atributos diretamente pelo
nome usandosintaxe de ponto, conforme mostrado na Tabela 5-1. (Fornecerei mais
detalhes sobre isso no Capítulo 8.)

Tabela 5-1:Acesso direto aos atributos de umreto

Atributo Descrição
<correto>.x A coordenada x da borda esquerda doreto
<correto>.y A coordenada y da borda superior doreto
<correto>.deixei A coordenada x da borda esquerda doreto (igual a
<correto>.x)
<correto>.topo A coordenada y da borda superior doreto (igual a
<correto>.y)
<correto>.certo A coordenada x da borda direita doreto
<correto>.fundo A coordenada y da borda inferior doreto
<correto>.topleft Uma tupla de dois inteiros: as coordenadas do canto superior esquerdo da
reto

<correto>.inferior esquerdo Uma tupla de dois inteiros: as coordenadas do canto inferior


esquerdo dareto

<correto>.canto superior direito Uma tupla de dois inteiros: as coordenadas do canto superior direito da
reto

<correto>.canto inferior direito Uma tupla de dois inteiros: as coordenadas do canto inferior direito da
reto

(contínuo)

Introdução ao Pygame 111


Tabela 5-1:Acesso direto aos atributos de umreto(contínuo)

Atributo Descrição
<correto>.midtop Uma tupla de dois inteiros: as coordenadas do ponto médio da borda
superior doreto

<correto>.midleft Uma tupla de dois inteiros: as coordenadas do ponto médio da borda


esquerda dareto

<correto>.midbottom Uma tupla de dois inteiros: as coordenadas do ponto médio da


borda inferior dareto

<correto>.midright Uma tupla de dois inteiros: as coordenadas do ponto médio da borda


direita dareto

<correto>.Centro Uma tupla de dois inteiros: as coordenadas no centro dareto

<correto>.centerx A coordenada x do centro da largura doreto


<correto>.centery A coordenada y do centro da altura doreto
<correto>.Tamanho Uma tupla de dois inteiros: a (largura, altura) doreto

<correto>.largura A largura doreto


<correto>.altura A altura doreto
<correto>.W A largura doreto (igual a<reto>.largura)
<correto>.h A altura doreto (igual a<reto>.altura)

Um pygameretotambém pode ser pensado e acessado como uma lista de quatro elementos.
Especificamente, você pode usar um índice para obter ou definir qualquer parte individual de um
reto.Por exemplo, usando obolaReto,os elementos individuais podem ser acessados como:

• ballRect[0]é o valor x (mas você também pode usarballRect.left)


• ballRect[1]é o valor de y (mas você também pode usarballRect.top)

• ballRect[2]é a largura (mas você também pode usarballRect.width)


• bolaRet[3]é a altura (mas você também pode usarballRect.height)

A Listagem 5-7 é uma versão alternativa do nosso programa de bola quicando


que mantém todas as informações sobre a bola em um objeto retangular.

Arquivo: PygameDemo4_OneBallBounce/PygameOneBallBounceRects.py

# pygame demo 4(b) - uma imagem, salte pela janela usando rects

#1 - Importar pacotes
importar pygame
de pygame.locals import *
import sys
importar aleatório

# 2 - Definir constantes
PRETO = (0, 0, 0)
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480

112 capítulo 5
FRAMES_PER_SECOND = 30
N_PIXELS_PER_FRAME = 3

#3 - Inicialize o mundo
pygame.init()
janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

# 4 - Carregar assets: imagem(s), som(s), etc.


ballImage = pygame.image.load('images/ball.png')

#5 - Inicialize as variáveis 1ballRect


= ballImage.get_rect()
MAX_WIDTH = WINDOW_WIDTH - ballRect.width
MAX_HEIGHT = WINDOW_HEIGHT - ballRect.height
ballRect.left = random.randrange(MAX_WIDTH)
ballRect.top = random.randrange(MAX_HEIGHT)
xSpeed = N_PIXELS_PER_FRAME
yVelocidade = N_PIXELS_PER_FRAME

# 6 - Loop para sempre


enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
# Clicou no botão fechar? Saia do pygame e termine o programa if
event.type == pygame.QUIT:
pygame.quit()
sys.exit()

# 8 - Faça qualquer ação "por frame"


2if (ballRect.left < 0) ou (ballRect.right >= WINDOW_WIDTH):
xVelocidade = -xVelocidade# inverta a direção X

if (ballRect.top < 0) ou (ballRect.bottom >= WINDOW_HEIGHT):


yVelocidade = -yVelocidade# inverta direção Y

# Atualize o retângulo da bola usando a velocidade em duas direções


ballRect.left = ballRect.left + xSpeed ballRect.top = ballRect.top +
ySpeed

# 9 - Limpe a janela antes de desenhá-la novamente


window.fill(PRETO)

#10 - Desenhe os elementos da janela


3window.blit(ballImage, ballRect)

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND)

Listagem 5-7: Uma animação baseada em localização, quicando uma bola pela janela, usandoretos

Introdução ao Pygame 113


Essa abordagem de usar umretoobjeto não é melhor nem pior do que usar
variáveis separadas. O programa resultante funciona exatamente da mesma forma
que o original. A lição importante aqui é como você pode usar e manipular atributos de
umretoobjeto.
Após carregar a imagem da bola, chamamos oget_rect()método1 para obter o
retângulo delimitador da imagem. Essa chamada retorna umretoobjeto, que
armazenamos em uma variável chamadabolaRet.Nós usamosballRect.widthe
ballRect.heightpara obter acesso direto à largura e altura da imagem da bola. (Na versão
anterior, usávamos uma constante de 100 para a largura e a altura.) Obter esses valores
da imagem que foi carregada torna nosso código muito mais adaptável porque significa
que podemos usar um gráfico de qualquer tamanho.
O código também usa os atributos do retângulo em vez de usar variáveis
separadas para verificar se alguma parte do retângulo da bola passa por cima
uma borda. Podemos usarballRect.lefteballRect.rightpara ver se obolaReté
fora das bordas esquerda ou direita2.Fazemos um teste semelhante com
ballRect.topeball-Rect.bottom.Em vez de atualizar variáveis de coordenadas x e y
individuais, atualizamos odeixeietopodobolaRet.
A outra mudança sutil, mas importante, está na chamada para sacar a bola3. O
segundo argumento na chamada parablit()pode ser uma tupla (x, y) ou uma
reto.O código dentroblit()usa a posição esquerda e superior naretocomo as
coordenadas x e y.

Tocando sons
Existem dois tipos de sons que você pode querer tocar em seus
programas: efeitos sonoros curtos e música de fundo.

Reproduzindo efeitos sonoros

Todos os efeitos sonoros devem estar em arquivos externos e devem estar em.ondaou
. oggformato. Tocar um efeito sonoro relativamente curto consiste em duas etapas: carregar o
som de um arquivo de som externo uma vez; então, no(s) momento(s) apropriado(s), toque
seu som.
Para carregar um efeito sonoro na memória, você usa uma linha como esta:

<soundVariable>=pygame.mixer.Sound(<caminho para o arquivo de som>)

Para reproduzir o efeito sonoro, você só precisa chamar seuToque()método:

<soundVariable>.Toque()

Modificaremos a Listagem 5-7 para adicionar um efeito sonoro “boing” sempre que
a bola quicar em um lado da janela. Existe umsonspasta na pasta do projeto no mesmo
nível do programa principal. Logo após carregar a imagem da bola, carregamos o
arquivo de som adicionando este código:

# 4 - Carregar assets: imagem(s), som(s), etc. ballImage =


pygame.image.load('images/ball.png') bounceSound =
pygame.mixer.Sound('sounds/boing.wav')

114 capítulo 5
Para reproduzir o efeito sonoro “boing” sempre que alteramos a direção
horizontal ou vertical da bola, modificamos a seção #8 para ficar assim:

# 8 - Faça qualquer ação "por frame"


if (ballRect.left < 0) ou (ballRect.right >= WINDOW_WIDTH):
xVelocidade = -xVelocidade# inverta a direção X
bounceSound.play()

if (ballRect.top < 0) ou (ballRect.bottom >= WINDOW_HEIGHT):


yVelocidade = -yVelocidade# inverta direção Y
bounceSound.play()

Quando você encontra uma condição que deve reproduzir um efeito sonoro, você
adiciona uma chamada aoToque()método do som. Existem muitas outras opções para controlar
os efeitos sonoros; você pode encontrar detalhes na documentação oficial emhttps://
www.pygame.org/docs/ref/mixer.html.

Tocando música de fundo


Tocar música de fundo envolve duas linhas de código usando chamadas para o
pygame.mixer.musicmódulo. Primeiro, você precisa disso para carregar o arquivo de som na

memória:

pygame.mixer.music.load(<caminho para o arquivo de som>)

o<caminho para o arquivo de som>é uma string de caminho onde o arquivo de som pode ser
encontrado. Você pode usar.mp3arquivos, que parecem funcionar melhor, bem como.ondaou.ogg
arquivos. Quando você deseja iniciar a reprodução da música, você precisa fazer esta chamada:

pygame.mixer.music.play(<número de voltas>,<posição inicial>)

Para reproduzir uma música de fundo repetidamente, você pode passar um -1por
<número de voltas>para executar a música para sempre. o<posição inicial>é normalmente
definido para0para indicar que você deseja reproduzir o som desde o início.
Existe uma versão modificada para download do programa quicando bola que
carrega corretamente o efeito sonoro e os arquivos de música de fundo e inicia a
reprodução do som de fundo. As únicas mudanças estão na seção #4, conforme
mostrado aqui.

Arquivo: PygameDemo4_OneBallBounce/PyGameOneBallBounceWithSound.py

# 4 - Carregar assets: imagem(s), som(s), etc. ballImage =


pygame.image.load('images/ball.png') bounceSound =
pygame.mixer.Sound('sounds/boing.wav')
pygame.mixer.music.load('sounds/background.mp3')
pygame. mixer.music.play(-1, 0.0)

O Pygame permite um manuseio muito mais complexo de sons de fundo. Você


encontra a documentação completa emhttps://www.pygame.org/docs/ref/
music.html#module-pygame.mixer.music.

Introdução ao Pygame 115


NOTA Para tornar os exemplos futuros mais claramente focados em POO, deixarei de fora
chamadas para tocar efeitos sonoros e música de fundo. Mas adicionar sons melhora muito
a experiência do usuário de um jogo, e eu encorajo fortemente incluí-los.

Formas de desenho
O Pygame oferece várias funções integradas que permitem desenhar certas formas
conhecidas comoprimitivos, que incluem linhas, círculos, elipses, arcos, polígonos e
retângulos. A Tabela 5-2 fornece uma lista dessas funções. Observe que existem duas
chamadas que atraemanti-aliaslinhas. Estas são linhas que incluem cores misturadas
nas bordas para fazer com que as linhas pareçam suaves e menos irregulares. Existem
duas vantagens principais em usar essas funções de desenho: elas são executadas com
extrema rapidez e permitem que você desenhe formas simples sem precisar criar ou
carregar imagens de arquivos externos.

Tabela 5-2:Funções para desenhar formas

Função Descrição
pygame.draw.aaline() Desenha uma linha anti-alias

pygame.draw.aalines() Desenha uma série de linhas com suavização de serrilhado

pygame.draw.arc() Desenha um arco

pygame.draw.circle() Desenha um círculo

pygame.draw.elipse() Desenha uma elipse

pygame.draw.line() Desenha uma linha

pygame.draw.lines() Desenha uma série de linhas

pygame.draw.polygon() Desenha um polígono

pygame.draw.rect() Desenha um retângulo

A Figura 5-7 mostra a saída de um programa de exemplo que demonstra chamadas


para essas funções primitivas de desenho.
A Listagem 5-8 é o código do programa de amostra, usando o mesmo modelo de
12 etapas que produziu a saída na Figura 5-7.

Arquivo: PygameDemo5_DrawingShapes.py

# pygame demo 5 - desenho

- - - recorte ---
enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
# Clicou no botão fechar? Saia do pygame e termine o programa if
event.type == pygame.QUIT:
pygame.quit()
sys.exit()

116 capítulo 5
# 8 - Faça qualquer ação "por frame"

# 9 - Limpe a janela
window.fill(CINZA)

1#10 - Desenhe todos os elementos da janela


# Desenha uma caixa

pygame.draw.line(janela, AZUL, (20, 20), (60, 20), 4) # topo


pygame.draw.line(janela, AZUL, (20, 20), (20, 60), 4) pygame. # deixei
draw.line(janela, AZUL, (20, 60), (60, 60), 4) # certo
pygame.draw.line(janela, AZUL, (60, 20), (60, 60), 4) # fundo
# Desenhe um X na caixa
pygame.draw.line(janela, AZUL, (20, 20), (60, 60), 1)
pygame.draw.line(janela, AZUL, (20, 60), (60, 20), 1)

# Desenhe um círculo preenchido e um círculo vazio pygame.draw.circle(janela,


VERDE, (250, 50), 30, 0)# preenchidas pygame.draw.circle(janela, VERDE, (400,
50), 30, 2)# 2 pixels de borda

# Desenha um retângulo preenchido e um retângulo vazio


pygame.draw.rect(janela, VERMELHO, (250, 150, 100, 50), 0)# preenchidas
pygame.draw.rect(janela, VERMELHO, (400, 150, 100, 50), 1)# 1 pixel de borda

# Desenha uma elipse preenchida e uma elipse vazia pygame.draw.ellipse(janela,


AMARELO, (250, 250, 80, 40), 0)# preenchidas pygame.draw.ellipse(janela, AMARELO,
(400, 250, 80, 40), 2)# 2 pixels de borda

# Desenha um polígono de seis lados pygame.draw.polygon(janela,


TEAL, ((240, 350), (350, 350),
(410, 410), (350, 470), (240,
470), (170, 410)))

# Desenha um arco
pygame.draw.arc(janela, AZUL, (20, 400, 100, 100), 0, 2, 5)

# Desenhe linhas com suavização de serrilhado: uma única linha, depois uma lista
de pontos pygame.draw.aaline(window, RED, (500, 400), (540, 470), 1)
pygame.draw.aalines(window, BLUE, True,
((580, 400), (587, 450), (595,
460), (600, 444)), 1)

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND) # faz o pygame esperar

Listagem 5-8: Um programa para demonstrar chamadas para funções primitivas de desenho em pygame

O desenho de todas as primitivas ocorre na seção #101.Chamamos as funções de


desenho do pygame para desenhar uma caixa com duas diagonais, círculos preenchidos e
vazios, retângulos preenchidos e vazios, ovais preenchidos e vazios, um polígono de seis
lados, um arco e duas linhas com suavização de serrilhado.

Introdução ao Pygame 117


Figura 5-7: Um programa de exemplo que demonstra o uso de chamadas para desenhar formas primitivas

Referência para formas primitivas


Para sua referência, aqui está a documentação dos métodos pygame para desenhar
esses primitivos. Em todos os itens a seguir, ocorO argumento espera que você passe
uma tupla de valores RGB:

Linha anti-alias

pygame.draw.aaline(window, color, startpos, endpos, blend=True)

Desenha uma linha de suavização de serrilhado na janela. SemisturaéVerdadeiro,as sombras


serão misturadas com sombras de pixel existentes em vez de sobrescrever pixels.

Linhas anti-alias

pygame.draw.aalines(window, color, closed, points, blend=True)

Desenha uma sequência de linhas com suavização de serrilhado na janela. ofechado


argumento é um booleano simples; se éVerdadeiro,uma linha será desenhada entre o
primeiro e o último ponto para completar a forma. opontosargumento é uma lista ou
tupla de coordenadas (x, y) a serem conectadas por segmentos de linha (deve haver pelo
menos duas). O Booleanomisturaargumento, se definido comoVerdadeiro,irá misturar os
tons com tons de pixel existentes em vez de sobrescrevê-los.

118 capítulo 5
Arco

pygame.draw.arc(window, color, rect, angle_start, angle_stop, width=0)

Desenha um arco na janela. O arco vai caber dentro do dadoreto.Os dois


ânguloos argumentos são os ângulos inicial e final (em radianos, com zero à
direita). olarguraargumento é a espessura para desenhar a borda externa.

Círculo

pygame.draw.circle(janela, cor, posição, raio, largura=0)

Desenha um círculo na janela. oposiçãoé o centro do círculo, e


raioé o raio. olarguraargumento é a espessura para desenhar a borda
externa. Selarguraé0,então o círculo será preenchido.

Elipse

pygame.draw.ellipse(janela, cor, retângulo, largura=0)

Desenha uma elipse na janela. O dadoretoé a área que a elipse irá


preencher. olarguraargumento é a espessura para desenhar a borda
externa. Selarguraé0,então a elipse será preenchida.

Linha

pygame.draw.line(window, color, startpos, endpos, width=1)

Desenha uma linha em uma janela. olarguraargumento é a espessura da linha.

Linhas

pygame.draw.lines(janela, cor, fechado, pontos, largura=1)

Desenha uma sequência de linhas na janela. ofechadoargumento é um booleano


simples; se éVerdadeiro,uma linha será desenhada entre o primeiro e o último ponto
para completar a forma. opontosargumento é uma lista ou tupla de coordenadas (x,
y) a serem conectadas por segmentos de linha (deve haver pelo menos duas). o
larguraargumento é a espessura da linha. Observe que especificar uma largura de
linha maior que 1 não preenche as lacunas entre as linhas. Portanto, linhas largas e
cantos afiados não serão unidos perfeitamente.

Polígono

pygame.draw.polygon(janela, cor, lista de pontos, largura=0)

Desenha um polígono na janela. olista de pontosespecifica os vértices do


polígono. olarguraargumento é a espessura para desenhar a borda externa.
Selarguraé0,então o polígono será preenchido.

Introdução ao Pygame 119


Retângulo

pygame.draw.rect(janela, cor, retângulo, largura=0)

Desenha um retângulo na janela. oretoé a área do retângulo. olargura


argumento é a espessura para desenhar a borda externa. Selarguraé0,
então o retângulo será preenchido.

NOTA Para obter informações adicionais, consultehttp://www.pygame.org/docs/ref/draw.html.

O conjunto de chamadas primitivas permite a flexibilidade de desenhar as formas que desejar.


Novamente, a ordem em que você faz as chamadas é importante. Pense na ordem de suas chamadas
como camadas; elementos que são desenhados antecipadamente podem ser sobrepostos por chamadas
posteriores para qualquer outra função primitiva de desenho.

Resumo
Neste capítulo, apresentei o básico do pygame. Você instalou o pygame em seu
computador, então aprendeu sobre o modelo de programação orientada a eventos e o
uso de eventos, que é muito diferente da codificação de programas baseados em texto.
Expliquei o sistema de coordenadas de pixels em uma janela e a forma como as cores
são representadas no código.
Para começar logo no início com o pygame, apresentei um modelo de 12 seções
que não faz nada além de abrir uma janela e pode ser usado para construir qualquer
programa baseado em pygame. Usando essa estrutura, construímos programas de
amostra que mostravam como desenhar uma imagem na janela (usandoblit()), como
detectar eventos do mouse e como lidar com a entrada do teclado. A próxima
demonstração explicou como construir uma animação baseada em localização.
Retângulos são muito importantes no pygame, então eu abordei como os atributos de
umretoobjeto pode ser usado. Também forneci alguns códigos de exemplo para mostrar como
reproduzir efeitos sonoros e música de fundo para aumentar o prazer do usuário com seus
programas. Por fim, apresentei como usar os métodos pygame para desenhar formas
primitivas em uma janela.
Embora eu tenha introduzido muitos conceitos dentro do pygame,
quase tudo que mostrei neste capítulo foi essencialmente procedural. oreto
object é um exemplo de código orientado a objetos construído diretamente no pygame. No
próximo capítulo, mostrarei como usar OOP no código para usar o pygame de forma mais
eficaz.

120 capítulo 5
OBJEC T- ORIENTED YG AME
6
Neste capítulo, demonstrarei como você pode usar
técnicas de POO efetivamente dentro do framework
pygame. Começaremos com um exemplo de código
procedural, depois dividiremos esse código em uma única
classe e algum código principal que chama os métodos dessa
classe. Depois disso, vamos construir duas classes,Botão Simplese
Texto Simples,que implementam widgets básicos da interface do

usuário: um botão e um campo para exibição de texto. Também


apresentarei o conceito de retorno de chamada.

Construindo o Screensaver Ball com OOP Pygame


No Capítulo 5, criamos um protetor de tela à moda antiga onde uma bola quicava
dentro de uma janela (Listagem 5-6, se você precisar refrescar sua memória).
Esse código funciona, mas os dados para a bola e o código para manipular a
bola estão interligados, o que significa que há muito código de inicialização, e o
código para atualizar e desenhar a bola está embutido na estrutura de 12 etapas.

Uma abordagem mais modular é dividir o código em umBolaclasse e um


programa principal que instancia umBolaobjeto e faz chamadas para seus métodos.
Nesta seção faremos essa divisão e mostrarei como criar várias bolas a partir do
Bolaclasse.

Criando uma classe de bola

Começaremos extraindo todo o código relacionado à bola do programa


principal e movendo-o para umBolaclasse. Olhando para o código original,
podemos ver que as seções que tratam da bola são:

• Seção #4, que carrega a imagem da bola


• Seção #5, que cria e inicializa todas as variáveis que têm algo a
ver com a bola
• Seção #8, que inclui código para mover a bola, detectar um salto de
borda e mudar a velocidade e direção
• Seção #10, que puxa a bola

A partir disso, podemos concluir que nossaBolaclasse exigirá os seguintes


métodos:

crio() Carrega uma imagem, define um local e inicializa todas as instâncias


variáveis
atualizar() Altera a localização da bola em cada quadro, com base na
velocidade x e y velocidade da bola
empate()Desenha a bola na janela

O primeiro passo é criar uma pasta de projeto, na qual você precisa de umBall.py para o
novoBolaclass, o arquivo de código principalMain_BallBounce.py, e umimagens pasta
contendo obola.pngarquivo de imagem.
A Listagem 6-1 mostra o código do novoBolaclasse.

Arquivo: PygameDemo6_BallBounceObjectOriented/Ball.py

importar pygame
de pygame.locals import *
import random

# aula de bola
class Bola():

1def __init__(self, window, windowWidth, windowHeight):


self.window = janela# lembra da janela, para podermos desenhar depois
self.windowWidth = windowWidth self.windowHeight = windowHeight

122 Capítulo 6
2self.image = pygame.image.load('images/ball.png')
# Um rect é composto por [x, y, largura, altura]
ballRect = self.image.get_rect() self.width =
ballRect.width self.height = ballRect.height
self.maxWidth = windowWidth - self.width
self.maxHeight = windowHeight - self.height

# Escolha uma posição inicial aleatória


3self.x = random.randrange(0, self.maxWidth)
self.y = random.randrange(0, self.maxHeight)

# Escolha uma velocidade aleatória entre -4 e 4, mas não zero,


# nas direções x e y
4lista de velocidades = [-4, -3, -2, -1, 1, 2, 3, 4]
self.xSpeed = random.choice(speedsList)
self.ySpeed = random.choice(speedsList)

5atualização def(self):
# Verifique se está batendo em uma parede. Se sim, mude essa
direção. if (self.x < 0) ou (self.x >= self.maxWidth):
self.xSpeed = -self.xSpeed

if (self.y < 0) ou (self.y >= self.maxHeight):


self.ySpeed = -self.ySpeed

# Atualize o x e y da bola, usando a velocidade em duas direções self.x


= self.x + self.xSpeed self.y = self.y + self.ySpeed

6def draw(self):
self.window.blit(self.image, (self.x, self.y))

Listagem 6-1: O novoBolaclasse

Quando instanciamos umBolaobjeto, o __iniciar__()O método recebe três partes


de dados: a janela para desenhar, a largura da janela e a altura da janela1.Nós
salvamos ojanelavariável na variável de instânciaself.windowpara que possamos
usá-lo mais tarde noempate()método, e fazemos o mesmo com oself.windowHeighte
self.windowWidthvariáveis de instância. Em seguida, carregamos a imagem da bola
usando o caminho para o arquivo e obtemos o
retodaquela imagem de bola2.Nós precisamos doretopara calcular os valores máximos
paraxeypara que a bola sempre apareça totalmente na janela. Em seguida, escolhemos
um local de partida aleatório para a bola3.Por fim, definimos a velocidade nas direções
x e y para um valor aleatório entre –4 e 4 (mas não 0), representando o número de
pixels a serem movidos por quadro4.Por causa desses números, a bola pode se mover
de forma diferente cada vez que executamos o programa. Todos esses valores são
salvos em variáveis de instância para serem usados por outros métodos.

No programa principal, chamaremos oatualizar()método em cada quadro do


loop principal, então é aqui que colocamos o código que verifica a bola

Pygame orientado a objetos 123


atingindo qualquer borda da janela5.Se atingir uma borda, invertemos a
velocidade nessa direção e modificamos as coordenadas x e y (self.xe
self.y)pela velocidade da corrente nas direções x e y.
Também chamaremos oempate()método, que simplesmente chamablit()desenhar a
bola em suas atuais coordenadas x e y6,em cada quadro do loop principal.

Usando a classe Ball


Agora todas as funcionalidades associadas a uma bola foram colocadas noBola
código de classe. Tudo o que o programa principal precisa fazer é criar a bola, então chamar
seuatualizar()eempate()métodos em cada quadro. A Listagem 6-2 mostra o código bastante
simplificado do programa principal.

Arquivo: PygameDemo6_BallBounceObjectOriented/Main_BallBounce.py

# pygame demo 6(a) - usando a classe Ball, quique uma bola

#1 - Importar pacotes
importar pygame
de pygame.locals import *
import sys
importar aleatório
1da importação de bola *# traz o código da classe Ball

# 2 - Definir constantes
PRETO = (0, 0, 0)
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
FRAMES_PER_SECOND = 30

#3 - Inicialize o mundo
pygame.init()
janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

# 4 - Carregar assets: imagem(s), som(s), etc.

#5 - Inicialize as variáveis
2oBola = Bola(janela, WINDOW_WIDTH, WINDOW_HEIGHT)

# 6 - Loop para sempre


enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

# 8 - Faça qualquer ação "por frame"


3oBall.update()# diz para a Bola se atualizar

124 Capítulo 6
# 9 - Limpe a janela antes de desenhá-la novamente
window.fill(PRETO)

#10 - Desenhe os elementos da janela


4oBall.draw()#dizera bola para se desenhar

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND)

Listagem 6-2: O novo programa principal que instancia umBolae faz chamadas para seus métodos

Se você comparar este novo programa principal com o código original na


Listagem 5-6, verá que é muito mais simples e claro. Usamos umimportar
declaração para trazerBolacódigo de classe1.Nós criamos umBolaobjeto,
passando a janela que criamos e a largura e altura dessa janela2, e
salvamos o resultadoBolaobjeto em uma variável chamadaoBola.
A responsabilidade de mover a bola está agora noBolacódigo de classe,
então aqui só precisamos chamar oatualizar()método dooBolaobjeto3.Desde o
Bolaobjeto sabe o quão grande é a janela, quão grande é a imagem da bola, e a
localização e a velocidade da bola, ele pode fazer todos os cálculos necessários
para mover a bola e quicá-la nas paredes.
O código principal chama oempate()método dooBolaobjeto4,mas o
desenho real é feito nooBolaobjeto.

Criando muitos objetos de bola

Agora vamos fazer uma pequena, mas importante modificação no programa


principal para criar váriosBolaobjetos. Este é um dos verdadeiros poderes da
orientação a objetos: para criar três bolas, basta instanciar trêsBolaobjetos doBola
classe. Aqui vamos usar uma abordagem básica e construir uma lista deBola
objetos. Em cada quadro, percorreremos a lista deBolaobjetos, diga a cada um para atualizar
sua localização e, em seguida, repita novamente para dizer a cada um para desenhar a si
mesmo. A Listagem 6-3 mostra um programa principal modificado que cria e atualiza três
Bolaobjetos.

Arquivo: PygameDemo6_BallBounceObjectOriented/Main_BallBounceManyBalls.py

# pygame demo 6(b) - usando a classe Ball, quique muitas bolas

- - - recorte ---
N_BOLAS = 3
- - - recorte ---

#5 - Inicialize as variáveis 1
bolaLista = []
para oBall no intervalo (0, N_BALLS):
# Cada vez que passar pelo loop, crie um objeto Ball oBola =
Bola(janela, WINDOW_WIDTH, WINDOW_HEIGHT)

Pygame orientado a objetos 125


ballList.append(oBall)# anexa a nova Bola à lista de Bolas

# 6 - Loop para sempre


enquanto Verdadeiro:

- - - recorte ---

# 8 - Faça qualquer ação "por frame"


2para oBall em ballList:
oBall.update()# diz a cada Ball para se atualizar

# 9 - Limpe a janela antes de desenhá-la novamente


window.fill(PRETO)

#10 - Desenhe os elementos da


janela 3para oBall em ballList:
oBall.draw() # diga a cada Bola para desenhar a si mesma

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND)

Listagem 6-3: Criando, movendo e exibindo três bolas

Começamos com uma lista vazia deBolaobjetos1.Então temos um loop que


cria trêsBolaobjetos, cada um dos quais anexamos à nossa lista deBola
objetos,ballList.CadaBolaobjeto escolhe e lembra um local de partida
aleatório e uma velocidade aleatória em ambas as direções x e y.
Dentro do loop principal, iteramos por todos osBolaobjetos e diga a cada
um para se atualizar2,alterando as coordenadas x e y de cadaBola
objeto para um novo local. Em seguida, iteramos a lista novamente, chamando o
empate()método de cadaBolaobjeto3.
Quando executamos o programa, vemos três bolas, cada uma começando em
um local aleatório e cada uma se movendo com uma velocidade aleatória x e y.
Cada bola quica corretamente fora dos limites da janela.
Usando essa abordagem orientada a objetos, não fizemos alterações noBola
class, mas apenas alteramos nosso programa principal para agora gerenciar uma lista deBola
objetos em vez de um únicoBolaobjeto. Esse é um efeito colateral comum e muito positivo
do código OOP: classes bem escritas geralmente podem ser reutilizadas sem alterações.

Criando muitos, muitos objetos bola


Podemos alterar o valor da constanteN_BOLASa partir de3para algum valor muito maior,
como300,para criar rapidamente essa quantidade de bolas (Figura 6-1). Alterando apenas
uma única constante, fazemos uma grande mudança no comportamento do programa.
Cada bola mantém sua própria velocidade e localização e se desenha.

126 Capítulo 6
Figura 6-1: Criando, atualizando e desenhando 300Bolaobjetos

O fato de podermos instanciar qualquer número de objetos de um único script será vital
não apenas na definição de objetos do jogo como naves espaciais, zumbis, balas, tesouros e
assim por diante, mas também na construção de controles GUI, como botões, caixas de
seleção, entrada de texto campos e saídas de texto.

Construindo um botão reutilizável orientado a objetos


O botão simples é um dos elementos mais reconhecíveis de uma interface
gráfica do usuário. O comportamento padrão de um botão consiste em o
usuário usar o mouse para clicar na imagem do botão e soltá-lo.
Os botões geralmente consistem em pelo menos duas imagens: uma para
representar oacima ou estado normal do botão e outro para representar obaixaou
estado pressionado do botão. A sequência de um clique pode ser dividida nas seguintes
etapas:

1. O usuário move o ponteiro do mouse sobre o botão


2. O usuário pressiona o botão do mouse
3. O programa reage alterando a imagem para o estado inativo
4. O usuário libera o botão do mouse
5. O programa reage mostrando a imagem do botão para cima
6. O programa executa alguma ação com base no clique do botão

Boas GUIs também permitem que o usuário clique em um botão, temporariamente


desative o botão, alterando o botão para o estado ativo e, em seguida, com o

Pygame orientado a objetos 127


botão do mouse ainda para baixo, role para trás sobre a imagem para que o botão mude
de volta para a imagem para baixo. Se o usuário clicar em um botão, mas depois rolar o
mouse e levantar o botão do mouse, isso não é considerado um clique. Isso significa que
o programa só entra em ação quando o usuário pressiona e solta enquanto o mouse
está posicionado sobre a imagem de um botão.

Criando uma classe de botão

O comportamento do botão deve ser comum e consistente para todos os botões usados em uma
GUI, portanto, construiremos uma classe que cuide dos detalhes do comportamento. Uma vez que
criamos uma classe de botão simples, podemos instanciar qualquer número de botões e todos eles
funcionarão exatamente da mesma maneira.
Vamos considerar quais comportamentos nossa classe de botão deve suportar. Vamos precisar de
métodos para:

• Carregue as imagens dos estados up e down e inicialize quaisquer variáveis de


instância necessárias para rastrear o estado do botão.

• Informe o botão sobre todos os eventos que o programa principal


detectou e verifique se há algum que o botão precise reagir.
• Desenhe a imagem atual representando o botão.

A Listagem 6-4 apresenta o código de umBotão Simplesclasse. (Construiremos uma


classe de botão mais complicada no Capítulo 7.) Essa classe tem três métodos,
__init__(), handleEvent(),eempate(),que implementam os comportamentos
mencionados. O código dohandleEvent()O método fica um pouco complicado,
mas quando você o faz funcionar, é incrivelmente fácil de usar. Sinta-se à
vontade para trabalhar com ele, mas saiba que a implementação do código
não é tão relevante. O importante aqui é entender o propósito e o uso dos
diferentes métodos.

Arquivo: PygameDemo7_SimpleButton/SimpleButton.py

# classe SimpleButton
#
# Usa uma abordagem de "máquina de estado"
#

importar pygame
da importação de pygame.locals *

class SimpleButton():
# Usado para rastrear o estado do botão
STATE_IDLE = 'ocioso'# botão está para cima, o mouse não está sobre o botão
STATE_ARMED = 'armado'# botão está pressionado, passe o mouse sobre o botão
STATE_DISARMED = 'desarmado'# clicou no botão, saiu

def __init__(self, window, loc, up, down):1


self.window = janela
self.loc = loc
self.surfaceUp = pygame.image.load(up)

128 Capítulo 6
self.surfaceDown = pygame.image.load(down)

# Pega o retículo do botão (usado para ver se o mouse está sobre o botão) self.rect
= self.surfaceUp.get_rect() self.rect[0] = loc[0]

self.rect[1] = loc[1]

self.state = SimpleButton.STATE_IDLE

def handleEvent(self, eventObj):2


# Este método retornará True se o usuário clicar no botão.
# Normalmente retorna False.

se eventObj.type não estiver em (MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN):3


# O botão só se preocupa com eventos relacionados ao mouse
retorna falso

eventPointInButtonRect = self.rect.collidepoint(eventObj.pos)

if self.state == SimpleButton.STATE_IDLE:
if (eventObj.type == MOUSEBUTTONDOWN) e eventPointInButtonRect:
self.state = SimpleButton.STATE_ARMED

elif self.state == SimpleButton.STATE_ARMED:


if (eventObj.type == MOUSEBUTTONUP) e eventPointInButtonRect:
self.state = SimpleButton.STATE_IDLE
return True#clicou!

if (eventObj.type == MOUSEMOTION) e (não eventPointInButtonRect):


self.state = SimpleButton.STATE_DISARMED

elif self.state == SimpleButton.STATE_DISARMED:


if eventPointInButtonRect:
self.state = SimpleButton.STATE_ARMED elif
eventObj.type == MOUSEBUTTONUP:
self.state = SimpleButton.STATE_IDLE

retorna falso

def draw(self):4
# Desenha a aparência atual do botão para a janela if
self.state == SimpleButton.STATE_ARMED:
self.window.blit(self.surfaceDown, self.loc)

senão:# OCIOSO ou DESARMADO


self.window.blit(self.surfaceUp, self.loc)

Listagem 6-4: OBotão Simplesclasse

O __iniciar__()método começa salvando todos os valores passados em variáveis de


instância1para usar em outros métodos. Em seguida, ele inicializa mais algumas
variáveis de instância.
Sempre que o programa principal detecta algum evento, ele chama ohandleEvent()
método2.Este método primeiro verifica se o evento é um dosMOUSEMOÇÃO,

Pygame orientado a objetos 129


BOTÃO DO MOUSE,ouBOTÃO DO MOUSE3.O resto do método é implementado como um
máquina de estado, uma técnica sobre a qual entrarei em mais detalhes no Capítulo 15. O
código é um pouco complicado e você deve se sentir à vontade para estudar como ele
funciona, mas por enquanto observe que ele usa a variável de instância
self.state (ao longo de várias chamadas) para detectar se o usuário clicou
no botão. ohandleEvent()método retornaVerdadeiroquando o usuário
completa um clique do mouse pressionando o botão e depois soltando o
mesmo botão. Em todos os outros casos,handleEvent()retorna
Falso.
finalmente, oempate()método usa o estado da variável de instância do objeto
self.statepara decidir qual imagem (para cima ou para baixo) desenhar4.

Código principal usando um SimpleButton

Para usar umBotão Simplesno código principal, primeiro instanciamos um do


Botão Simplesclass antes do loop principal começar com uma linha como esta:

oButton = SimpleButton(janela, (150, 30),


'imagens/buttonUp.png',
'imagens/buttonDown.png')

Esta linha cria umBotão Simplesobjeto, especificando um local para desenhá-lo (como
sempre, as coordenadas são para o canto superior esquerdo do retângulo delimitador) e
fornecendo os caminhos para as imagens para cima e para baixo do botão. No loop
principal, sempre que qualquer evento acontecer, precisamos chamar o
handleEvent()para
ver se o usuário clicou no botão. Se o usuário clicar no
botão, o programa deverá realizar alguma ação. Também no loop principal,
precisamos chamar oempate()método para fazer o botão aparecer na janela.

Construiremos um pequeno programa de teste, que gerará uma interface de usuário


como a Figura 6-2, para incorporar uma instância de umBotão Simples.

Figura 6-2: A interface do usuário de um programa com uma única instância de umBotão Simples

Sempre que o usuário completa um clique no botão, o programa exibe


uma linha de texto no shell dizendo que o botão foi clicado. A Listagem 6-5
contém o código do programa principal.

Arquivo: PygameDemo7_SimpleButton/Main_SimpleButton.py

# Pygame demo 7 - Teste SimpleButton

- - - recorte ---
#5 - Inicialize as variáveis
# Cria uma instância de um SimpleButton

130 Capítulo 6
1oButton = SimpleButton(janela, (150, 30),
'imagens/buttonUp.png',
'imagens/buttonDown.png')

# 6 - Loop para sempre


enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

# Passa o evento para o botão, veja se foi clicado 2if


oButton.handleEvent(evento):
3print('Usuário clicou no botão')

# 8 - Faça qualquer ação "por frame"

# 9 - Limpe a janela
window.fill(CINZA)

#10 - Desenhe todos os elementos da


janela 4oButton.draw()# desenha o botão

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND)

Listagem 6-5: O programa principal que cria e reage a umBotão Simples

Novamente, começamos com o modelo pygame padrão do Capítulo 5. Antes do loop


principal, criamos uma instância do nossoBotão Simples1,especificando uma janela para
desenhar, um local, um caminho para a imagem de cima e um caminho para a imagem de
baixo.
Toda vez que passar pelo loop principal, precisamos reagir aos eventos detectados no
programa principal. Para implementar isso, chamamos oBotão Simplesclasse
handleEvent()método2e passar noeventodo programa principal.
ohandleEvent()O método rastreia todas as ações do usuário no botão
(pressionar, soltar, rolar, rolar de volta). QuandohandleEvent()
retornaVerdadeiro,indicando que ocorreu um clique, executamos a ação
associada a clicar nesse botão. Aqui, apenas imprimimos uma mensagem3.
Finalmente chamamos o botãoempate()método4para desenhar uma imagem para
representar o estado apropriado do botão (para cima ou para baixo).

Criando um programa com vários botões


Com nossoBotão Simplesclasse, podemos instanciar quantos botões desejarmos. Por
exemplo, podemos modificar nosso programa principal para incorporar três
Botão Simplesinstâncias, como mostrado na Figura 6-3.

Pygame orientado a objetos 131


Figura 6-3: O programa principal com trêsBotão Simplesobjetos

Não precisamos fazer nenhuma alteração noBotão Simplesarquivo de classe para fazer isso.
Simplesmente modificamos nosso código principal para instanciar trêsBotão Simples
objetos em vez de um.

Arquivo: PygameDemo7_SimpleButton/Main_SimpleButton3Buttons.py

oButtonA = SimpleButton(janela, (25, 30),


'imagens/buttonAUp.png',
'imagens/buttonADown.png')
oButtonB = SimpleButton(janela, (150, 30),
'imagens/buttonBUp.png',
'imagens/buttonBDown.png')
oButtonC = SimpleButton(janela, (275, 30),
'imagens/buttonCup.png',
'imagens/buttonCDown.png')

Agora precisamos chamar ohandleEvent()método de todos os três botões:

# Passe o evento para cada botão, veja se algum foi clicado if


oButtonA.handleEvent(evento):
print('O usuário clicou no botão A.')
elif oButtonB.handleEvent(event):
print('O usuário clicou no botão B.')
elif oButtonC.handleEvent(event):
print('O usuário clicou no botão C.')

Por fim, dizemos a cada botão para desenhar a si mesmo:

oButtonA.draw()
oButtonB.draw()
oButtonC.draw()

Ao executar o programa, você verá uma janela com três botões. Clicar em
qualquer um dos botões imprime uma mensagem mostrando o nome do botão
que foi clicado.
A ideia chave aqui é que, como estamos usando três instâncias do mesmo
Botão Simplesclasse, o comportamento de cada botão será idêntico. Um benefício
importante dessa abordagem é que qualquer alteração no código no
Botão Simplesclasse afetará todos os botões instanciados da classe. O programa
principal não precisa se preocupar com nenhum detalhe do funcionamento
interno do código do botão, precisando apenas chamar ohandleEvent()método de
cada botão no loop principal. Cada botão retornaráVerdadeiroouFalsopara dizer
que foi ou não clicado.

132 Capítulo 6
Construindo uma exibição de texto reutilizável orientada a objetos

Existem dois tipos diferentes de texto em um programa pygame: texto de exibição e texto de
entrada. O texto de exibição é gerado pelo seu programa, equivalente a uma chamada para o
imprimir()função, exceto que é exibido em uma janela pygame. O texto de entrada é uma string
de entrada do usuário, equivalente a uma chamada paraentrada().Nesta seção, discutirei o texto
de exibição. Veremos como lidar com o texto de entrada no próximo capítulo.

Etapas para exibir o texto

A exibição de texto em uma janela é um processo bastante complicado no pygame


porque não é simplesmente exibido como uma string no shell, mas exige que você
escolha um local, fontes e tamanhos e outros atributos. Por exemplo, você pode usar
um código como o seguinte:

pygame.font.init()

myFont = pygame.font.SysFont('Comic Sans MS', 30) textSurface


= myfont.render('Algum texto', True, (0, 0, 0))
window.blit(textSurface, (10, 10))

Começamos inicializando o sistema de fontes dentro do pygame; fazemos isso antes do


loop principal começar. Então dizemos ao pygame para carregar uma fonte específica do
sistema pelo nome. Aqui, solicitamos Comic Sans com tamanho de fonte 30.
O próximo passo é o principal: usamos essa fonte pararenderizarnosso texto,
que cria uma imagem gráfica do texto, chamada desuperfícieem pygame.
Fornecemos o texto que queremos produzir, um booleano que diz se queremos
que nosso texto seja anti-alias e uma cor no formato RGB. Aqui, (0, 0, 0)indica que
queremos que nosso texto seja preto. Por fim, usandoblit(),desenhamos a imagem
do texto na janela em algum local (x, y).
Este código funciona bem para mostrar o texto fornecido na janela no local
fornecido. No entanto, se o texto não mudar, haverá muito trabalho desperdiçado para
recriar otextSurfaceem cada iteração através do loop principal. Há também muitos
detalhes a serem lembrados, e você deve acertar todos eles para desenhar o texto
corretamente. Podemos esconder a maior parte dessa complexidade construindo uma
classe.

Criando uma classe SimpleText

A ideia é construir um conjunto de métodos que cuidem do carregamento de fontes e


renderização de texto no pygame, ou seja, não precisamos mais lembrar os detalhes da
implementação. A Listagem 6-6 contém uma nova classe chamadaTexto Simples
que faz este trabalho.

Arquivo: PygameDemo8_SimpleTextDisplay/SimpleText.py

# classe SimpleText

importar pygame
da importação de pygame.locals *

Pygame orientado a objetos 133


class Texto Simples():

1def __init__(self, window, loc, value, textColor):


2pygame.font.init()
self.window = janela
self.loc = loc
3self.font = pygame.font.SysFont(Nenhum, 30)
self.textColor = textColor
self.text = Nenhum# para que a chamada para setText abaixo
# força a criação da imagem de texto
self.setValue(valor)# define o texto inicial para desenho

4def setValue(self, newText):


if self.text == newText:
Retorna# nada a mudar

self.text = newText# salva o novo texto


self.textSurface = self.font.render(self.text, True, self.textColor)

5def draw(self):
self.window.blit(self.textSurface, self.loc)

Listagem 6-6: OTexto Simplesclasse para exibir texto

Você pode pensar em umTexto Simplesobjeto como um campo na janela onde você
deseja que o texto seja exibido. Você pode usar um para exibir texto de etiqueta imutável ou
para exibir texto que muda ao longo de um programa.
oTexto Simplesclasse tem apenas três métodos. O __iniciar__()método1 espera que
a janela seja desenhada, o local no qual desenhar o texto na janela, qualquer texto
inicial que você deseja ver exibido no campo e uma cor de texto. Ligando
pygame.font.init()2inicia o sistema de fontes do pygame. A chamada na primeira
instanciadaTexto Simplesobjeto realmente faz a inicialização; qualquer adicionalTexto
Simplesobjetos também farão esta chamada, mas como as fontes já foram
inicializadas, a chamada retorna imediatamente. Criamos um novoFonte
objeto compygame.font.SysFont()3.Em vez de fornecer um nome de fonte específico,
Nenhumindica que usaremos qualquer que seja a fonte padrão do sistema.
osetValue()O método renderiza uma imagem do texto a ser exibido e salva essa
imagem noself.textSurfacevariável de instância4.À medida que o programa é
executado, sempre que você quiser alterar o texto exibido, você chama osetValue()
método, passando o novo texto a ser exibido. osetValue()O método também tem
uma otimização: ele lembra o último texto que renderizou e, antes de fazer
qualquer outra coisa, verifica se o novo texto é igual ao texto anterior. Se o texto
não mudou, não há nada a fazer e o método apenas retorna. Se houver novo texto,
ele renderiza o novo texto em uma superfície a ser desenhada.
oempate()método5desenha a imagem contida noself.textSurface
variável de instância na janela no local fornecido. Este método deve ser
chamado em cada frame.
Existem várias vantagens nessa abordagem:

• A classe oculta todos os detalhes da renderização de texto do pygame, portanto, o usuário


dessa classe nunca precisa saber quais chamadas específicas do pygame são necessárias para
mostrar o texto.

134 Capítulo 6
• CadaTexto SimplesO objeto lembra a janela na qual ele desenha, o local
onde o texto deve ser colocado e a cor do texto. Portanto, você só
precisa especificar esses valores uma vez, ao instanciar um
Texto Simplesobjeto, normalmente antes do início do loop principal.

• CadaTexto SimplesO objeto também é otimizado para lembrar tanto o texto que foi
solicitado a desenhar pela última vez quanto a imagem (self.textSurface)que fez a
partir do texto atual. Ele só precisa renderizar uma nova superfície quando o texto
muda.
• Para mostrar vários pedaços de texto em uma janela, você só precisa instanciar
váriosTexto Simplesobjetos. Este é um conceito chave da programação orientada a
objetos.

Bola de demonstração com SimpleText e SimpleButton


Para finalizar, modificaremos a Listagem 6-2 para usar oTexto SimpleseBotão Simples
Aulas. O programa atualizado na Listagem 6-7 registra o número de vezes
que passa pelo loop principal e relata essas informações na parte superior da
janela. Clicar no botão Reiniciar redefine o contador.

Arquivo: PygameDemo8_SimpleTextDisplay/Main_BallTextAndButton.py

# pygame demo 8 - SimpleText, SimpleButton e Ball

#1 - Importar pacotes
importar pygame
de pygame.locals import *
import sys
importar aleatório
1da importação de bola *# traz o código da classe Ball
da importação SimpleText * da
importação SimpleButton *

# 2 - Definir constantes
PRETO = (0, 0, 0) BRANCO =
(255, 255, 255)
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
FRAMES_PER_SECOND = 30

#3 - Inicialize o mundo
pygame.init()
janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

# 4 - Carregar assets: imagem(s), som(s), etc.

#5 - Inicialize as variáveis
2oBola = Bola(janela, WINDOW_WIDTH, WINDOW_HEIGHT)
oFrameCountLabel = SimpleText(janela, (60, 20),
'O programa passou por tantos loops: ', BRANCO)

Pygame orientado a objetos 135


oFrameCountDisplay = SimpleText(window, (500, 20), '', WHITE)
oRestartButton = SimpleButton(window, (280, 60),
'imagens/restartUp.png', 'imagens/restartDown.png')
contador de quadros = 0

# 6 - Loop para sempre


enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

3if oRestartButton.handleEvent(evento):
contador de quadros = 0# botão clicado, reset do contador

# 8 - Faça qualquer ação "por frame"


4oBall.update()# diz para a bola se atualizar
contador de quadros = contador de quadros + 1# incrementa cada
frame 5oFrameCountDisplay.setValue(str(frameCounter))

# 9 - Limpe a janela antes de desenhá-la novamente


window.fill(PRETO)

#10 - Desenhe os elementos da janela


6oBall.draw()# manda a bola desenhar ela mesma
oFrameCountLabel.draw()
oFrameCountDisplay.draw()
oRestartButton.draw()

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND)

Listagem 6-7: Um exemplo de programa principal para mostrarBola,Texto Simples, eBotão Simples

No topo do programa, importamos o código doBola, Texto Simples, eBotão


SimplesAulas1.Antes de nosso loop principal começar, criamos uma instância do
Bola2,duas instâncias doTexto Simplesclasse (oFrameCountLabelpara o rótulo da
mensagem imutável eoFrameCountDisplaypara a exibição de quadros alterados) e
uma instância doBotão Simplesclasse que armazenamos em
oBotão Reiniciar.Também inicializamos uma variávelcontador de quadrospara zero, que iremos
incrementar toda vez através do loop principal.
No loop principal, verificamos se o usuário pressionou o botão Reiniciar3.Se
Verdadeiro,nós redefinimos o contador de quadros.

Dizemos à bola para atualizar sua posição4.Incrementamos o contador de quadros e, em seguida,


chamamos osetValue()método do campo de texto para mostrar a nova contagem de quadros5.Finalmente,
dizemos à bola para desenhar a si mesma, dizemos aos campos de texto para desenharem a si mesmos e
dizemos ao botão Reiniciar para desenhar a si mesma, chamando oempate()
método de cada objeto6.

136 Capítulo 6
Na instanciação doTexto Simplesobjetos, o último argumento é uma cor de texto
e especificamos que os objetos devem ser renderizados emBRANCOpara que
possam ser vistos contra umPRETOfundo. No próximo capítulo, mostrarei como
expandir oTexto Simplesclass para incorporar mais atributos, sem complicar a
interface da classe. Construiremos um objeto de texto mais completo que tenha
valores padrão razoáveis para cada um desses atributos, mas que permita
substituir esses padrões.

Interface vs. Implementação


oBotão SimpleseTexto Simplesexemplos trazem à tona o importante tópico de
interface versus implementação. Conforme mencionado no Capítulo 4, a interface
se refere a como algo é usado, enquanto a implementação se refere a como algo
funciona (internamente).
Em um ambiente OOP, a interface é o conjunto de métodos em uma classe e seus
parâmetros relacionados - também conhecidos comointerface de programação de
aplicativos (API). A implementação é o código real de todos os métodos da classe.
Um pacote externo como pygame provavelmente virá com a documentação
da API que explica as chamadas que estão disponíveis e os argumentos que você
deve passar com cada chamada. A documentação completa da API pygame está
disponível emhttps://www.pygame.org/docs/.
Quando você escreve código que faz chamadas para pygame, não precisa se preocupar com a
implementação dos métodos que está usando. Por exemplo, quando você faz uma chamada para
blit()para desenhar a imagem, você realmente não se importaComo as
blit()faz o que faz; você só precisa sabero quea chamada faz e quais argumentos
precisam ser passados. Por outro lado, você pode confiar que o(s)
implementador(es) que escreveu oblit()método pensaram extensivamente sobre
como fazerblit()trabalhar com mais eficiência.
No mundo da programação, geralmente usamos dois papéis como implementador e
desenvolvedor de aplicativos, portanto, precisamos nos esforçar para projetar APIs que não
apenas façam sentido na situação atual, mas também sejam gerais o suficiente para serem
usadas por programas futuros nossos e por programas escritos por outras pessoas. Nosso
Botão SimpleseTexto Simplesas classes são bons exemplos, pois são escritas de maneira geral
para que possam ser reutilizadas facilmente. Falarei mais sobre interface versus
implementação no Capítulo 8, quando examinarmos o encapsulamento.

Retornos de chamada

Ao usar umBotão Simplesobjeto, tratamos de verificar e reagir a um clique de


botão como este:

if oButton.handleEvent(evento):
print('O botão foi clicado')

Pygame orientado a objetos 137


Essa abordagem para lidar com eventos funciona bem com oBotão Simplesclasse. No
entanto, alguns outros pacotes Python e muitas outras linguagens de programação tratam
eventos de uma maneira diferente: com umligue de volta.

ligue de volta Uma função ou método de um objeto que é chamado quando uma determinada ação, evento ou
condição acontece

Uma maneira fácil de entender isso é pensar no filme de sucesso de 1984 Caça-
fantasmas. O slogan do filme é “Quem você vai chamar?” No filme, os Caça-Fantasmas
veicularam um anúncio na TV que dizia às pessoas que, se vissem um fantasma (esse é o
evento a ser procurado), deveriam ligar para os Caça-Fantasmas (o retorno de chamada) para
se livrar dele. Ao receber a chamada, os Caça-Fantasmas tomam as medidas apropriadas
para eliminar o fantasma.
Como exemplo, considere um objeto de botão que é inicializado para ter um retorno
de chamada. Quando o usuário clicar no botão, o botão chamará a função ou método de
retorno de chamada. Essa função ou método executa qualquer código necessário para
reagir ao clique do botão.

Criando um retorno de chamada

Para configurar um retorno de chamada, ao criar um objeto ou chamar um dos métodos


de um objeto, você passa o nome de uma função ou um método de um objeto a ser
chamado. Como exemplo, existe um pacote GUI padrão para Python chamado
tkinter.O
código necessário para criar um botão com este pacote é muito
diferente do que mostrei - aqui está um exemplo:

importar tkinter

def minhaFunção():
print('myCallBackFunction foi chamado')

oButton = tkinter.Button(text='Clique em mim', command=myFunction)

Ao criar um botão comtkinter,você deve passar uma função (ou um método de


um objeto), que será chamado de volta quando o usuário clicar no botão. Aqui
estamos passandominhaFunçãocomo a função a ser chamada de volta. (Esta
chamada está usando parâmetros de palavras-chave, que serão discutidos
detalhadamente no Capítulo 7.) Otkinterbotão lembra essa função como o retorno
de chamada e, quando o usuário clica no botão resultante, ele chama a função
minhaFunção().
Você também pode usar um retorno de chamada ao iniciar alguma ação que pode
levar algum tempo. Em vez de aguardar a conclusão da ação e fazer com que o
programa pareça congelar por um período de tempo, você fornece um retorno de
chamada a ser chamado quando a ação for concluída. Por exemplo, imagine que você
deseja fazer uma solicitação pela Internet. Em vez de fazer uma chamada e esperar que
ela retorne os dados, o que pode levar muito tempo, existem pacotes que permitem
usar a abordagem de fazer a chamada e definir um retorno de chamada. Dessa forma, o
programa pode continuar em execução e o usuário não fica bloqueado

138 Capítulo 6
fora disso. Isso geralmente envolve vários encadeamentos Python e está além do escopo
deste livro, mas a técnica de usar um retorno de chamada é a maneira geral como isso é
feito.

Usando um retorno de chamada com SimpleButton

Para demonstrar esse conceito, faremos uma pequena modificação no


Botão Simplesclass para permitir que ele aceite um retorno de chamada. Como um parâmetro

opcional adicional, o chamador pode fornecer uma função ou método de um objeto a ser chamado
de volta quando um clique em umBotão Simplesobjeto acontece. Cada instância de
Botão Simpleslembra o retorno de chamada em uma variável de instância. Quando o usuário
completa um clique, a instância deBotão Simpleschama o retorno de chamada.
O programa principal na Listagem 6-8 cria três instâncias do
Botão Simplesclasse, cada uma das quais lida com o clique do botão de uma maneira diferente. O

primeiro botão,oBotãoA,não fornece retorno de chamada;oBotão Bfornece um retorno de chamada


para uma função; eoBotãoCespecifica um retorno de chamada para um método de um objeto.

Arquivo: PygameDemo9_SimpleButtonWithCallback/Main_SimpleButtonCallback.py

# pygame demo 9 - teste de 3 botões com callbacks

#1 - Importar pacotes
importar pygame
from pygame.locals import *
from SimpleButton import *
import sys

# # 2 - Definir constantes
CINZA = (200, 200, 200)
WINDOW_WIDTH = 400
WINDOW_HEIGHT = 100
FRAMES_PER_SECOND = 30

# Defina uma função a ser usada como "callback"


def myCallBackFunction():1
print('O usuário pressionou o botão B, chamado myCallBackFunction')

# Definir uma classe com um método a ser usado como


"callback" class CallBackTest():2
- - - cortou quaisquer outros métodos nesta classe ---

def meuMétodo(self):
print('O usuário pressionou ButtonC, chamado myMethod do objeto CallBackTest')

#3 - Inicialize o mundo
pygame.init()
janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
clock = pygame.time.Clock()

# 4 - Carregar assets: imagem(s), som(s), etc.

#5 - Inicialize as variáveis

Pygame orientado a objetos 139


oCallBackTest = CallBackTest()3
# Cria instâncias de SimpleButton
# Sem retorno
oButtonA = SimpleButton(janela, (25, 30),4
'imagens/buttonAUp.png',
'imagens/buttonADown.png')
# Especificando uma função para chamar de volta
oButtonB = SimpleButton(janela, (150, 30),
'images/buttonBUp.png', 'images/
buttonBDown.png',
callBack=myCallBackFunction)
# Especificando um método de um objeto para chamar de
volta oButtonC = SimpleButton(janela, (275, 30),
'images/buttonCup.png', 'images/
buttonCDown.png',
callBack=oCallBackTest.myMethod)
contador = 0

# 6 - Loop para sempre


enquanto Verdadeiro:

# 7 - Verifique e manipule eventos


para evento em pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

# Passa o evento para o botão, veja se foi clicado if


oButtonA.handleEvent(evento):5
print('O usuário pressionou o botão A, manipulado no loop principal')

# oButtonB e oButtonC têm retornos de chamada,


# não há necessidade de verificar o resultado dessas
chamadas oButtonB.handleEvent(evento)6

oButtonC.handleEvent(evento)7

# 8 - Faça qualquer ação "por frame"


contador = contador + 1

# 9 - Limpe a janela
window.fill(CINZA)

#10 - Desenhe todos os elementos da


janela oButtonA.draw()
oButtonB.draw()
oButtonC.draw()

#11 - Atualize a janela


pygame.display.update()

# 12 - Desacelere um pouco as coisas


clock.tick(FRAMES_PER_SECOND) # faz o pygame esperar

Listagem 6-8: Uma versão do programa principal que lida com cliques de botão de três maneiras diferentes

140 Capítulo 6
Começamos com uma função simples,myCallBackFunction()1,que apenas imprime uma
mensagem para anunciar que foi chamado. A seguir, temos umTeste de retorno de chamada
classe que contém o métodomeuMétodo()2,que imprime sua própria mensagem para anunciar que
foi chamado. Nós criamos umoCallBackTestobjeto doTeste de retorno de chamadaclasse3.Precisamos
deste objeto para que possamos configurar um retorno de chamada para
oCallBack.myMethod().
Então criamos trêsBotão Simplesobjetos, cada um usando uma abordagem diferente4.O
primeiro,oBotãoA,não tem retorno de chamada. O segundo,oBotãoB,define seu retorno de
chamada para a funçãomyCallBackFunction().O terceiro,oBotãoC,define seu
retorno de chamada paraoCallBack.myMethod().

No loop principal, verificamos se o usuário está clicando em qualquer um


dos três botões chamando ohandleEvent()método de cada botão. DesdeoBotãoA
não tem retorno de chamada, devemos verificar se o valor retornado éVerdadeiro5e, em
caso afirmativo, execute uma ação. QuandooBotão Bé clicado6,amyCallBackFunction()
será chamada e imprimirá sua mensagem. QuandooBotãoCé clicado7, a
meuMétodo()método dooCallBackTestobjeto será chamado e imprimirá sua
mensagem.
Alguns programadores preferem usar uma abordagem de retorno de chamada,
porque o destino a ser chamado é configurado quando você cria o objeto. É importante
entender essa técnica, especialmente se você estiver usando um pacote que a exija. No
entanto, usarei a abordagem original de verificar o valor retornado por uma chamada
parahandleEvent()em todo o meu código de demonstração.

Resumo
Neste capítulo, mostrei como você pode começar com um programa procedural e extrair
o código relacionado para construir uma classe. Nós criamos umBolaclass para
demonstrar isso, então modifiquei o código principal do nosso programa de
demonstração do capítulo anterior para chamar métodos da classe para informar oBola
objetoo quefazer, sem se preocuparComo asatinge o resultado. Com todo o código
relacionado em uma classe separada, é fácil criar uma lista de objetos e instanciar e
gerenciar quantos objetos quisermos.
Construímos então umBotão Simplesclasse e umTexto Simplesclasse que escondem a
complexidade dentro de sua implementação e criam código altamente reutilizável. No
próximo capítulo, desenvolverei essas classes para desenvolver classes de exibição de texto e
botões de “força profissional”.
Finalmente, apresentei o conceito de callback, onde você passa uma função ou método em uma
chamada para um objeto. O retorno de chamada é posteriormente chamado de volta quando um
evento acontece ou uma ação é concluída.

Pygame orientado a objetos 141


P YG AMEGUIWIDGETS
7
O Pygame permite que os programadores
peguem a linguagem baseada em texto do Python
e a usem para construir programas baseados em
GUI. Janelas, dispositivos apontadores, clicar, arrastar e
sons tornaram-se partes padrão de nossa experiência no uso
de computadores. Infelizmente, o pacote pygame não vem
com elementos básicos de interface de usuário embutidos,
então precisamos construí-los nós mesmos. Faremos isso com
pygwidgets,uma biblioteca de widgets GUI.
Este capítulo explica como widgets padrão, como imagens, botões e campos de entrada
ou saída, podem ser criados como classes e como o código do cliente os utiliza. Construir
cada elemento como uma classe permite que os programadores incorporem várias
instâncias de cada elemento ao criar uma GUI. Antes de começarmos a construir esses
widgets de GUI, no entanto, primeiro preciso discutir mais um recurso do Python: passar
dados em uma chamada para uma função ou método.
Passando argumentos para uma função ou método
Os argumentos em uma chamada para uma função e os parâmetros definidos na
função têm uma relação um-para-um, de modo que o valor do primeiro argumento é
dado ao primeiro parâmetro, o valor do segundo argumento é dado ao segundo
parâmetro, e assim por diante.
A Figura 7-1, duplicada do Capítulo 3, mostra que o mesmo acontece quando você faz
uma chamada para um método de um objeto. Podemos ver que o primeiro parâmetro, que é
sempreauto,é definido para o objeto na chamada.

def algumMétodo(self,<quaisquer outros parâmetros>):

oSomeObject.someMethod(<qualquer outro argumento>)

Figura 7-1: Como os argumentos passados para um método correspondem aos seus parâmetros

No entanto, o Python (e algumas outras linguagens) permite que você torne alguns dos
argumentos opcionais. Se um argumento opcional não for fornecido em uma chamada,
podemos fornecer um valor padrão para usar na função ou método. Vou explicar por meio de
uma analogia do mundo real.
Se você pedir um hambúrguer em um restaurante Burger King, seu
hambúrguer virá com ketchup, mostarda e picles. Mas o Burger King é
famoso por dizer: “Você pode fazer do seu jeito”. Caso queira alguma outra
combinação de condimentos, deve dizer o que quer (ou não quer) ao fazer
seu pedido.
Começaremos escrevendo umpedirBurgers()função que simula fazer um
pedido de hambúrguer da maneira normal que definimos funções, sem
implementar valores padrão:

def orderBurgers(nBurgers, ketchup, mostarda, picles):

Você deve especificar o número de hambúrgueres que deseja pedir, mas,


idealmente, se desejar os padrões deVerdadeiropara adicionar ketchup, mostarda e
picles, você não precisa passar mais argumentos. Então, para pedir dois
hambúrgueres com os padrões padrão, você pode pensar que sua chamada deve
ser assim:

pedirHambúrgueres(2)# com ketchup, mostarda e picles

No entanto, em Python, isso acionará um erro porque há uma


incompatibilidade entre o número de argumentos na chamada e o número
de parâmetros especificados na função:

TypeError: orderBurgers() faltando 3 argumentos posicionais necessários: 'ketchup',


'mostard' e 'picles'

Vamos ver como o Python nos permite configurar parâmetros opcionais que podem
receber valores padrão se nada for especificado.

144 Capítulo 7
Parâmetros posicionais e de palavra-chave

Python tem dois tipos diferentes de parâmetros: parâmetros posicionais e parâmetros de


palavras-chave.Parâmetros posicionaissão o tipo com o qual já estamos familiarizados,
onde cada argumento em uma chamada tem um parâmetro correspondente na definição
de função ou método.
UMAparâmetro de palavra-chavepermite especificar um valor padrão. Você escreve um
parâmetro de palavra-chave como um nome de variável, um sinal de igual e um valor padrão, assim:

def algumaFunção(<keywordParameter>=<valor padrão>):

Você pode ter vários parâmetros de palavra-chave, cada um com um nome e um


valor padrão.
Uma função ou método pode ter parâmetros posicionais e parâmetros de palavras-
chave; nesse caso, você deve especificar todos os parâmetros posicionais antes daquaisquer
parâmetros de palavra-chave:

def someOtherFunction(positionalParam1, positionalParam2, ...


<keywordParameter1>=<valor padrão 1>,
<keywordParameter2>=<valor padrão 2>, ...):

Vamos reescreverpedirBurgers()para usar um parâmetro posicional e três


parâmetros de palavra-chave com valores padrão, assim:

def orderBurgers(nBurgers, ketchup=Verdadeiro, mostarda=Verdadeiro, picles=Verdadeiro):

Quando fazemos uma chamada para esta função,nHambúrgueresé um parâmetro


posicional e, portanto, deve ser especificado como um argumento em cada chamada. Os
outros três são parâmetros de palavras-chave. Se nenhum valor for passado paracatchup,
mostarda,epicles,a função usará o valor padrão deVerdadeiropara cada uma dessas variáveis de
parâmetro. Agora podemos pedir dois hambúrgueres com todos os condimentos assim:

pedirHambúrgueres(2)

Se quisermos algo diferente de um valor padrão, podemos especificar o nome do parâmetro da


palavra-chave e um valor diferente em nossa chamada. Por exemplo, se queremos apenas ketchup
em nossos dois hambúrgueres, podemos fazer a chamada desta maneira:

orderBurgers(2, mostarda=Falso, picles=Falso)

Quando a função é executada, os valores domostardaepiclesvariáveis são


definidas paraFalso.Como não especificamos um valor paraketchup,é dado o padrão
deVerdadeiro.
Você também pode fazer a chamada especificando todos os argumentos
posicionalmente, incluindo aqueles escritos como parâmetros de palavras-chave. O Python
usará a ordem de seus argumentos para atribuir a cada parâmetro o valor correto:

orderBurgers(2, True, False, False)

Nesta chamada, estamos novamente especificando dois hambúrgueres com ketchup, sem
mostarda e sem picles.

Widgets da GUI do Pygame 145


Observações adicionais sobre parâmetros de palavras-chave

Vamos examinar rapidamente algumas convenções e dicas para usar parâmetros de


palavras-chave. Como convenção do Python, quando você usa parâmetros de palavras-
chave e palavras-chave com argumentos, o sinal de igual entre a palavra-chave e o valor
devenãotem espaços ao redor, para mostrar que essas não são instruções de atribuição
típicas. Estas linhas estão formatadas corretamente:

def orderBurgers(nBurgers, ketchup=Verdadeiro, mostarda=Verdadeiro, picles=Verdadeiro):

orderBurgers(2, mostarda=Falso)

Essas linhas também funcionarão bem, mas não seguem a convenção de


formatação e são menos legíveis:

def orderBurgers(nBurgers, ketchup = True, mostarda = True, picles = True):

orderBurgers(2, mostarda = False)

Ao chamar uma função que possui parâmetros posicionais e parâmetros de palavra-


chave, você deve fornecer valores para todos os parâmetros posicionais primeiro, antes de
qualquer parâmetro de palavra-chave opcional.
Os argumentos de palavra-chave em chamadas podem ser especificados em qualquer ordem. Liga para o nosso

pedirBurgers()função pode ser feita de várias maneiras, tais como:

orderBurgers(2, mostarda=Falso, picles=Falso)#somente catchup

ou:

orderBurgers(2, picles=Falso, mostarda=Falso, ketchup=Falso)# avião

Todos os parâmetros de palavras-chave receberão os valores apropriados,


independentemente da ordem dos argumentos.
Embora todos os valores padrão nopedirBurgers()Por exemplo, se fossem valores booleanos, um
parâmetro de palavra-chave pode ter um valor padrão de qualquer tipo de dados. Por exemplo,
poderíamos escrever uma função para permitir que um cliente faça um pedido de sorvete como
este:

def orderIceCream(flavor, nScoops=1, coneOrCup='cone', granulado=Falso):

O chamador deve especificar um sabor, mas por padrão receberá uma colher em um
cone sem granulado. O chamador pode substituir esses padrões com valores de palavra-
chave diferentes.

Usando Nenhum como Valor Padrão

Às vezes é útil saber se o chamador passou um valor para um parâmetro de palavra-


chave ou não. Para este exemplo, o chamador pede uma pizza. No mínimo, o chamador
deve especificar um tamanho. O segundo parâmetro será um estilo que tem como
padrão 'regular'mas pode ser 'prato fundo'.Como terceiro parâmetro,

146 Capítulo 7
o chamador pode opcionalmente passar em uma única cobertura desejada. Se o chamador
quiser uma cobertura, devemos cobrar extra.
Na Listagem 7-1, usaremos um parâmetro posicional para oTamanhoe
parâmetros de palavras-chave para oestiloecobertura.O padrão paraestiloé a corda
'regular'.Como a escolha de cobertura é opcional, usaremos o valor especial do
Python deNenhumcomo o padrão, mas o chamador pode passar a cobertura de sua
escolha.

Arquivo: OrderPizzaWithNone.py

def orderPizza(tamanho, estilo='regular', cobertura=Nenhum):


# Faça alguns cálculos com base no tamanho e estilo
# Verifica se uma cobertura foi especificada
PRICE_OF_TOPPING = 1,50# preço para qualquer cobertura

se tamanho == 'pequeno':
preço = 10,00
elif tamanho == 'médio':
preço = 14,00
senão:# ampla
preço = 18,00

if estilo == 'deepdish':
preço = preço + 2,00# cobra extra pelo deepdish

line = 'Você pediu uma ' + tamanho + ' ' + estilo + ' pizza com ' 1se a
cobertura for Nenhum:# verifica se nenhuma cobertura foi passada
print(linha + 'sem cobertura')
else:
print(linha + cobertura)
preço = preço + PRICE_OF_TOPPING

print('O preço é $', preço) print()

# Você pode pedir uma pizza das seguintes maneiras:


2pedidoPizza('grande') # grande, padrão para regular, sem cobertura

orderPizza('grande', estilo='regular')# o mesmo que acima

3orderPizza('medium', style='deepdish', topping='cogumelos')

orderPizza('pequeno', cobertura='cogumelos')# padrão de estilo para regular

Listagem 7-1: Uma função com um parâmetro de palavra-chave padronizado paraNenhum

A primeira e a segunda chamadas seriam vistas como iguais, com o valor da


variávelcoberturadefinido comoNenhum2.Na terceira e quarta chamadas, o valor de
coberturaestá configurado para 'cogumelos'3.Porque 'cogumelos'não éNenhum,nessas
ligações, o código adicionaria uma taxa extra para uma cobertura nas pizzas1.
UsandoNenhumcomo um valor padrão para um parâmetro de palavra-chave fornece uma
maneira de ver se o chamador forneceu um valor na chamada. Este pode ser um uso muito
sutil de parâmetros de palavras-chave, mas será muito útil em nossa próxima discussão.

Widgets da GUI do Pygame 147


Escolhendo palavras-chave e valores padrão

O uso de valores padrão torna a chamada de funções e métodos mais simples, mas há uma
desvantagem. Sua escolha de cada palavra-chave para parâmetros de palavras-chave é muito
importante. Depois que os programadores começam a fazer chamadas que substituem os
valores padrão, é muito difícil alterar o nome de um parâmetro de palavra-chave porque esse
nome deve ser alterado emtudochamadas para a função ou método em lockstep. Caso
contrário, o código que estava funcionando irá quebrar. Para código mais amplamente
distribuído, isso pode causar muita dor aos programadores que usam seu código. Resumindo,
não altere o nome de um parâmetro de palavra-chave a menos que seja absolutamente
necessário. Então, escolha sabiamente!
Também é muito importante usar valores padrão que devem atender a maior variedade
possível de usuários. (Em uma nota pessoal, euodiarmostarda! Sempre que vou ao Burger
King, tenho que me lembrar de não especificar mostarda ou compro o que considero um
hambúrguer intragável. Eu acho que eles fizeram uma má escolha padrão.)

Valores padrão em widgets de GUI

Na próxima seção, apresentarei uma coleção de classes que você pode usar para criar
facilmente elementos GUI, como botões e campos de texto dentro do pygame. Essas classes
serão inicializadas usando alguns parâmetros posicionais, mas também terão vários
parâmetros de palavras-chave opcionais, todos com padrões razoáveis para permitir que os
programadores criem widgets GUI especificando apenas alguns argumentos posicionais. Um
controle mais preciso pode ser obtido especificando valores para substituir os valores padrão
dos parâmetros de palavras-chave.
Para um exemplo aprofundado, veremos um widget para exibir texto na janela do
aplicativo. O texto pode ser mostrado em uma variedade de fontes, tamanhos de fonte,
cores, cores de fundo e assim por diante. Nós vamos construir umTexto de exibiçãoclasse que
terá valores padrão para todos esses atributos, mas dará ao código do cliente a opção de
especificar valores diferentes.

O pacote pygwidgets
O restante deste capítulo se concentrará napygwidgets (pronunciado “pig
wijits”), que foi escrito com dois objetivos em mente:

1. Demonstrar muitas técnicas diferentes de programação orientada a objetos


2. Para permitir que os programadores criem e usem facilmente widgets GUI em programas
pygame

opygwidgetspacote contém as seguintes classes:


TextButton

Botão construído com arte padrão, usando uma string de texto

Botão personalizado

Botão com arte personalizada

148 Capítulo 7
TextCheckBox

Caixa de seleção com arte padrão, construída a partir de uma string de texto

Caixa de seleção personalizada

Caixa de seleção com arte personalizada

TextRadioButton

Botões de rádio com arte padrão, construídos a partir de uma string de texto

Botão de rádio personalizado

Botões de opção com arte personalizada

Texto de exibição

Campo usado para exibir o texto de saída

Entrada de texto

Campo onde o usuário pode digitar texto

Arrasta

Permite que o usuário arraste uma imagem

Imagem

Exibe uma imagem em um local

Coleção de imagens

Exibe uma de uma coleção de imagens em um local

Animação

Exibe uma sequência de imagens

SpriteSheetAnimation

Exibe uma sequência de imagens de uma única imagem maior

Configurando

Para instalarpygwidgets,abra a linha de comando e digite o seguinte:

python3 -m pip install -U pip --user python3 -m


pip install -U pygwidgets --user

Esses comandos baixam e instalam a versão mais recente dopygwidgets


do Python Package Index (PyPI). Ele é colocado em uma pasta (chamada pacotes
de sites) que está disponível para todos os seus programas Python. Uma vez
instalado, você pode usarpygwidgetsincluindo a seguinte declaração no início de
seus programas:

importar widgets

Isso importa o pacote inteiro. Após a importação, você pode instanciar


objetos de suas classes e chamar os métodos desses objetos.

Widgets da GUI do Pygame 149


A documentação mais atual depygwidgetsestá emhttps://pygwidgets
. readthedocs.io/en/latest/. Se você quiser ver o código-fonte do pacote, ele está
disponível no meu repositório GitHub emhttps://github.com/IrvKalb/pygwidgets/.

Abordagem geral do projeto

Conforme mostrado no Capítulo 5, uma das primeiras coisas que você faz em todo
programa pygame é definir a janela do aplicativo. A linha a seguir cria uma janela
do aplicativo e salva uma referência a ela em uma variável chamadajanela:

janela = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))

Como veremos em breve, sempre que instanciarmos qualquer widget,


precisaremos passar nojanelavariável para que o widget possa se desenhar na janela do
aplicativo.
A maioria dos widgets empygwidgetsfuncionam de maneira semelhante, geralmente envolvendo
estas três etapas:

1. Antes do principalenquantoloop iniciar, crie uma instância do widget, passando


quaisquer argumentos de inicialização.
2. No loop principal, sempre que ocorrer algum evento, chame ohandleEvent()
método do widget (passando no objeto de evento).
3. Na parte inferior do loop principal, chame oempate()método do widget.

O passo 1 para usar qualquer widget é instanciar um com uma linha como esta:

oWidget = pygwidgets.<SomeWidgetClass>(janela, local,<outros argumentos conforme necessário>)

O primeiro argumento é sempre a janela do aplicativo. O segundo


argumento é sempre o local na janela em que o widget será exibido, dado
como uma tupla: (x, y).
O passo 2 é lidar com qualquer evento que possa afetar o widget chamando a função do objeto
handleEvent()método dentro do loop de eventos. Se algum evento (como um clique do mouse ou um

botão pressionado) acontecer e o widget manipular o evento, esta chamada retornaráVerdadeiro.O


código no topo da página principalenquantoloop geralmente se parece com isso:

enquanto Verdadeiro:

para evento em pygame.event.get():


if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

if oWidget.handleEvent(evento):
# O usuário fez algo no oWidget que devemos responder
# Adicione o código aqui

O passo 3 é adicionar uma linha perto da parte inferior doenquantoloop para chamar o
empate()método do widget, para fazê-lo aparecer na janela:

oWidget.draw()

150 Capítulo 7
Como especificamos a janela para desenhar, o local e todos os detalhes
que afetam a aparência do widget na etapa 1, não passamos nada na
chamada paraempate().

Adicionando uma imagem

Nosso primeiro exemplo será o widget mais simples: usaremos oImagemclasse para
exibir uma imagem em uma janela. Ao instanciar umImagemobjeto, os únicos
argumentos necessários são a janela, o local na janela para desenhar a imagem e o
caminho para o arquivo de imagem. Crie oImagemobject antes do loop principal
começar, assim:

oImage = pygwidgets.Image(window, (100, 200), 'images/SomeImage.png')

O caminho usado aqui assume que a pasta do projeto que contém o programa
principal também contém uma pasta chamadaimagens, dentro do qual está o
SomeImage.pngArquivo. Então, no loop principal você só precisa chamar o objeto
empate()método:

oImage.draw()

oempate()método doImagemclasse contém uma chamada parablit()para realmente


desenhar a imagem, para que você nunca precise chamarblit()diretamente. Para mover a
imagem, você pode chamar seusetLoc()(abreviação de set location), especificando as novas
coordenadas x e y como uma tupla:

oImage.setLoc((novoX, novoY))

Na próxima vez que a imagem for desenhada, ela aparecerá nas novas coordenadas. A
documentação lista muitos métodos adicionais que você pode chamar para inverter, girar,
dimensionar, obter a localização e o retângulo da imagem e assim por diante.

O MÓDULO SPRITE

O Pygame possui um módulo embutido para mostrar imagens em uma janela, chamado de
módulo sprite . Tais imagens são chamadasduendes . O módulo sprite fornece umSprite
classe para lidar com sprites individuais e umGrupoclasse para lidar com vários
Spriteobjetos. Juntas, essas classes fornecem excelente funcionalidade e, se você
pretende fazer programação pygame pesada, provavelmente vale a pena dar uma
olhada nelas. No entanto, para explicar os conceitos de POO subjacentes, optei por não
usar essas classes. Em vez disso, continuarei com os elementos gerais da GUI para que
possam ser usados em qualquer ambiente e linguagem Se você quiser aprender mais
sobre o módulo sprite, veja o tutorial emhttps://www.pygame.org/docs/tut/
SpriteIntro.html

Widgets da GUI do Pygame 151


Adicionando botões, caixas de seleção e botões de opção

Quando você instancia um botão, caixa de seleção ou widget de botão de opção empygwidgets, você
tem duas opções: instanciar uma versão de texto que desenha sua própria arte e adiciona um rótulo
de texto com base em uma string que você passa ou instanciar uma versão personalizada onde você
fornece a arte. A Tabela 7-1 mostra as diferentes classes de botões disponíveis.

Tabela 7-1:Texto e classes de botões personalizados empygwidgets

Versão de texto (cria arte em Versão personalizada (usa

tempo real) sua arte)


Botão TextButton Botão personalizado

Caixa de seleção TextCheckBox Caixa de seleção personalizada

Botao de radio TextRadioButton Botão de rádio personalizado

As diferenças entre as versões de texto e personalizadas dessas classes são


relevantes apenas durante a instanciação. Depois de criar um objeto de uma classe de
texto ou botão personalizado, todos os métodos restantes do par de classes são
idênticos. Para deixar isso claro, vamos dar uma olhada noTextButton
eBotão personalizadoAulas.

Botões de texto

Aqui está a definição real do __iniciar__()método doTextButtonclasse


dentropygwidgets:

def __init__(self, window, loc, text,


largura=Nenhum,
altura = 40,
textColor=PYGWIDGETS_BLACK,
upColor=PYGWIDGETS_NORMAL_GRAY,
overColor=PYGWIDGETS_OVER_GRAY,
downColor=PYGWIDGETS_DOWN_GRAY,
fontName=DEFAULT_FONT_NAME,
fontSize=DEFAULT_FONT_SIZE,
soundOnClick=Nenhum,
enterToActivate=Falso,
callback=Nenhum
apelido=Nenhum):

No entanto, em vez de ler o código de uma classe, um programador normalmente


consulta sua documentação. Como mencionado anteriormente, você pode encontrar a
documentação completa parapygwidgetsnohttps://pygwidgets.readthedocs.io/en/latest/.

Você também pode visualizar a documentação de uma classe chamando o built-inajuda()


função no shell Python assim:
> > > ajuda(pygwidgets.TextButton)

152 Capítulo 7
Quando você cria uma instância de umTextButton,você só precisa passar na
janela, o local na janela e o texto a ser mostrado no botão. Se você especificar
apenas esses parâmetros posicionais, seu botão usará padrões razoáveis para
largura e altura, as cores de fundo para os quatro estados do botão (diferentes
tons de cinza), a fonte e o tamanho da fonte. Por padrão, nenhum efeito sonoro
será reproduzido quando o usuário clicar no botão.
O código para criar umTextButtonusando todos os padrões fica assim:

oButton = pygwidgets.TextButton(window, (50, 50), 'Text Button')

O código no __iniciar__()método doTextButtonA classe usa os métodos de


desenho pygame para construir sua própria arte para todos os quatro estados
(up, down, over e disabled). A linha anterior cria uma versão “up” de um botão
que se parece com a Figura 7-2.

Figura 7-2: ATextButton


usando padrões

Você pode substituir qualquer um ou todos os parâmetros padrão com valores de palavra-
chave como:

oButton = pygwidgets.TextButton(window, (50, 50), 'Text Button',


largura = 200,
altura = 30,
textColor=(255, 255, 128),
upColor=(128, 0, 0),
fontName='Courier',
fontSize=14,
soundOnClick='sounds/blip.wav',
enterToActivate=True)

Essa instanciação criará um botão que se parece com a Figura 7-3.

Figura 7-3: ATextButtonusando argumentos de palavra-


chave para fonte, tamanho, cores e assim por diante

O comportamento de troca de imagem desses dois botões funcionaria exatamente


da mesma maneira; as únicas diferenças estariam na aparência das imagens.

Botões personalizados

oBotão personalizadoclass permite que você use sua própria arte para um botão. Para
instanciar umbotão personalizado,você só precisa passar uma janela, um local e um
caminho para a imagem do estado ativo do botão. Aqui está um exemplo:

restartButton = pygwidgets.CustomButton(janela, (100, 430),


'imagens/RestartButtonUp.png')

Widgets da GUI do Pygame 153


opara baixo, sobre,eDesativadoestados são argumentos de palavras-chave opcionais e,
para qualquer um deles, onde nenhum valor é passado,Botão personalizadousará uma
cópia doacimaimagem. É mais típico (e fortemente sugerido) passar caminhos para as
imagens opcionais, assim:

restartButton = pygwidgets.CustomButton(janela, (100, 430),


'images/RestartButtonUp.png', down='images/
RestartButtonDown.png', over='images/
RestartButtonOver.png', disabled='images/
RestartButtonDisabled.png',
soundOnClick='sounds/blip.wav',
apelido='reiniciar')

Aqui também especificamos um efeito sonoro que deve ser reproduzido quando o
usuário clica no botão e fornecemos um apelido interno que podemos usar mais tarde.

Usando botões

Após a instanciação, aqui está um código típico para usar um objeto de botão,oBotão,
independente de ser umTextButtonou umBotão personalizado:

enquanto Verdadeiro:

para evento em pygame.event.get():


if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

if oButton.handleEvent(evento):
# O usuário clicou neste botão
<Qualquer código que você queira executar aqui quando o botão for clicado>
- - - recorte ---
oButton.draw()# na parte inferior do loop while, diga para desenhar

Toda vez que detectamos um evento, precisamos chamar ohandleEvent()método do


botão para permitir que ele reaja às ações do usuário. Esta chamada normalmente
retornaFalsomas vai voltarVerdadeiroquando o usuário completa um clique no botão. Na
parte inferior do principalenquantoloop, precisamos chamar oempate()
método do botão para permitir que ele se desenhe.

Saída e entrada de texto


Como vimos no Capítulo 6, lidar com entrada e saída de texto no pygame é complicado, mas
aqui apresentarei novas classes para um campo de exibição de texto e um campo de texto de
entrada. Ambas as classes têm parâmetros mínimos necessários (posicionais) e têm padrões
razoáveis para outros atributos (fonte, tamanho da fonte, cor e assim por diante) que são
facilmente substituídos.

Saída de texto

opygwidgetspacote contém umTexto de exibiçãoclasse para mostrar o texto que é


uma versão mais completa doTexto Simplesclasse do Capítulo 6. Quando você

154 Capítulo 7
instanciar umTexto de exibiçãocampo, os únicos argumentos necessários são a janela e o local.
O primeiro parâmetro de palavra-chave évalor,que pode ser especificado com uma string
como texto inicial a ser mostrado no campo. Isso normalmente é usado para um valor de
usuário final padrão ou para texto que nunca muda, como um rótulo ou instruções. Desde
valoré o primeiro parâmetro de palavra-chave, ele pode ser fornecido como um argumento
posicional ou de palavra-chave. Por exemplo, isso:

oTextField = pygwidgets.DisplayText(window, (10, 400), 'Hello World')

funcionará da mesma forma que isso:

oTextField = pygwidgets.DisplayText(window, (10, 400), value='Hello World')

Você também pode personalizar a aparência do texto de saída especificando qualquer um ou


todos os parâmetros de palavra-chave opcionais. Por exemplo:

oTextField = pygwidgets.DisplayText(janela, (10, 400),


value='Algum texto do título',
fontName='Courier',
fontSize=40,
largura = 150,
justificado='centro',
textColor=(255, 255, 0))

oTexto de exibiçãoclasse tem vários métodos adicionais, o mais importante


dos quais ésetValue(),que você chama para alterar o texto desenhado no
campo:

oTextField.setValue('Qualquer novo texto que você deseja ver')

Na parte inferior do principalenquantoloop, você precisa chamar o objeto


empate()método:

oTextField.draw()

E, claro, você pode criar quantosTexto de exibiçãoobjetos como desejar, cada um exibindo
um texto diferente e cada um com sua própria fonte, tamanho, cor e assim por diante.

Entrada de texto

Em um programa Python típico baseado em texto, para obter a entrada do usuário,


você faria uma chamada para oentrada()função, que interrompe o programa até que o
usuário insira texto na janela do shell. Mas no mundo dos programas GUI orientados a
eventos, o loop principal nunca para. Portanto, devemos usar uma abordagem
diferente.
Para entrada de texto do usuário, um programa GUI normalmente apresenta um campo que o
usuário pode digitar. Um campo de entrada deve lidar com todas as teclas do teclado, algumas das
quais são exibidas enquanto outras são usadas para edição ou movimento do cursor dentro do
campo. Ele também deve permitir que o usuário mantenha pressionada uma tecla para repeti-lo. o
pygwidgets InputTextclasse fornece toda essa funcionalidade.

Widgets da GUI do Pygame 155


Os únicos argumentos necessários para instanciar umEntrada de textoobjeto
são a janela e um local:

oInputField = pygwidgets.InputText(janela, (10, 100))

No entanto, você pode personalizar os atributos de texto de umEntrada de textoobjeto


especificando argumentos de palavra-chave opcionais:

oInputField = pygwidgets.InputText(janela, (10, 400),


value='Texto inicial',
fontName='Helvetica',
fontSize=40,
largura = 150,
textColor=(255, 255, 0))

Após instanciar umEntrada de textocampo, o código típico no loop


principal ficaria assim:

enquanto Verdadeiro:

para evento em pygame.event.get():


if event.type == pygame.QUIT:
pygame.quit()
sys.exit()

if oInputField.handleEvent(evento):
# O usuário pressionou Enter ou Return
userText = oInputField.getValue() # pega o texto que o usuário digitou
<Qualquer código que você deseja executar usando a entrada do usuário>
- - - recorte ---
oInputField.draw()# na parte inferior do loop while principal

Para cada evento, precisamos chamar ohandleEvent()método doEntrada de texto


campo para permitir que ele reaja a pressionamentos de tecla e cliques do mouse. Esta chamada
normalmente retornaFalso,mas quando o usuário pressiona ENTER ou RETURN, ele retornaVerdadeiro.
Podemos então recuperar o texto que o usuário digitou chamando o métodoObter valor()
método do objeto.
Na parte inferior do principalenquantoloop, precisamos chamar oempate()método para
permitir que o campo se desenhe.
Se uma janela contiver vários campos de entrada, as teclas pressionadas serão tratadas
pelo campo com o foco atual do teclado, que é alterado quando um usuário clica em um
campo diferente. Se você deseja permitir que um campo tenha o foco inicial do teclado, você
pode definir ofoco inicialparâmetro de palavra-chave paraVerdadeironoEntrada de texto
objeto de sua escolha ao criar esse objeto. Além disso, se você tiver váriosEntrada de
textocampos em uma janela, uma abordagem típica de design de interface do
usuário é incluir um botão OK ou Enviar. Quando este botão é clicado, você pode
chamar oObter valor()método de cada campo.

156 Capítulo 7
NOTA No momento da redação, oEntrada de textoclass não lida com o realce de vários caracteres arrastando o
mouse. Se essa funcionalidade for adicionada em uma versão posterior, nenhuma alteração será
necessária nos programas que usamEntrada de textoporque o código estará inteiramente dentro dessa
classe. Qualquer novo comportamento será suportado automaticamente em todos
Entrada de textoobjetos.

Outras classes pygwidgets


Como você viu no início desta seção,pygwidgetscontém várias outras
classes.
oColeção de imagensA classe permite que você mostre qualquer imagem única de uma coleção de
imagens. Por exemplo, suponha que você tenha imagens de um personagem virado para frente, para a
esquerda, para trás e para a direita. Para representar todas as imagens em potencial, você pode construir
um dicionário como este:

imageDict = {'front':'images/front.png', 'left':'images/left.png',


'back':'images/back.png', 'right':'images/right.png'}

Você pode então criar umColeção de imagensobjeto, especificando este dicionário e a chave
da imagem com a qual você deseja começar. Para mudar para uma imagem diferente, você
chama osubstituir()método e passar uma chave diferente. Chamando o
empate()O método na parte inferior do loop sempre mostra a imagem atual.
oArrastaclass exibe uma única imagem, mas permite que o usuário arraste a
imagem para qualquer lugar na janela. Você deve chamar seuhandleEvent()método no
loop de eventos. Quando o usuário terminar de arrastar,handleEvent()retornaVerdadeiro,
e você pode ligar para oArrastado objetogetMouseUpLoc()método para obter o local onde
o usuário soltou o botão do mouse.
oAnimaçãoeSpriteSheetAnimationclasses lidam com a construção e exibição de
uma animação. Ambos requerem um conjunto de imagens para iterar. oAnimação
classe obtém as imagens de arquivos individuais, enquanto a classe
SpriteSheetAnimationclasse requer uma única imagem com imagens internas uniformemente
espaçadas. Exploraremos essas classes mais detalhadamente no Capítulo 14.

Programa de exemplo pygwidgets

A Figura 7-4 mostra uma captura de tela de um programa de exemplo que demonstra
objetos instanciados de muitas das classes empygwidgets,IncluindoImagem,
DisplayText, InputText, TextButton, CustomButton, TextRadioButton, CustomRadioButton,
TextCheckBox, CustomCheckBox, ImageCollection,eArrastador.
A fonte deste programa de exemplo pode ser encontrada nopygwidgets_test pasta
no meu repositório GitHub,https://github.com/IrvKalb/pygwidgets/.

Widgets da GUI do Pygame 157


Figura 7-4: A janela de um programa que demonstra objetos instanciados de
uma variedade depygwidgetsAulas

A importância de uma API consistente


Uma observação final sobre a construção de uma API para um conjunto de classes:
sempre que possível, é uma boa ideia criar consistência nos parâmetros de métodos em
classes diferentes, mas semelhantes. Como um bom exemplo, os dois primeiros
parâmetros para o __iniciar__()método de cada classe empygwidgetssãojanelaelocal,naquela
ordem. Se estes estivessem em uma ordem diferente em alguns atendimentos, usar o
pacote como um todo seria muito mais difícil.
Além disso, se classes diferentes implementarem a mesma funcionalidade, é uma
boa ideia usar os mesmos nomes de método. Por exemplo, muitas das classes de
pygwidgetstem um método chamadosetValue()e outro chamadoObter valor(). Falarei mais
sobre por que esse tipo de consistência é tão importante nos próximos dois capítulos.

Resumo
Este capítulo forneceu uma introdução à orientação a objetospygwidgets
pacote de widgets de interface gráfica do usuário. Começamos discutindo valores padrão para
parâmetros em métodos e expliquei que um parâmetro de palavra-chave permite que um
valor padrão seja usado se nenhum valor de argumento correspondente for especificado em
uma chamada.

158 Capítulo 7
Em seguida, apresentei-lhe opygwidgetsmódulo, que contém várias classes
de widgets GUI pré-construídas e mostrou como usar várias delas. Por fim,
mostrei um programa de amostra que fornece exemplos da maioria desses
widgets.
Há duas vantagens principais em escrever aulas como as depygwidgets. Primeiro, as
classes podem ocultar a complexidade dos métodos. Depois de ter sua aula funcionando
corretamente, você nunca mais precisará se preocupar com os detalhes internos. Segundo,
você pode reutilizar o código criando quantas instâncias de uma classe forem necessárias.
Suas classes podem fornecer funcionalidade básica incluindo parâmetros de palavras-chave
com valores padrão bem escolhidos. No entanto, os valores padrão podem ser facilmente
substituídos para permitir a personalização.
Você pode publicar as interfaces de suas classes para que outros
programadores (e você mesmo) aproveitem em diferentes projetos. Uma boa
documentação e consistência ajudam bastante a tornar esses tipos de classes
altamente utilizáveis.

Widgets da GUI do Pygame 159

Você também pode gostar