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.
5
INT RO DUCTIONTOP YG AME

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

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-in entrada()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 de entrada()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 simples enquanto 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ável ballImageagora 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 tipo pygame.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áveis xVelocidadeeyVelocidade1, 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 o retode 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 um reto

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 um reto(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, usando retos

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 um retoobjeto, que
armazenamos em uma variável chamadabolaRet.Nós usamosballRect.width e
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 seu Toque()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.music mó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, o corO 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. o fechado


argumento é um booleano simples; se éVerdadeiro,uma linha será desenhada entre o
primeiro e o último ponto para completar a forma. o pontosargumento é 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. o posiçã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. o fechadoargumento é 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. o retoé 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 (usando blit()), 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
6
OBJEC T- ORIENTED YG AME

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 um Bolaclasse 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 nossa Bolaclasse 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 do retopara 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 um Bolae 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 um importar
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 de Bolaobjetos, 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 300 Bolaobjetos

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 um Botã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 o handleEvent()
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 um Botã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 nosso Botã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 o Botã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 no Botão Simplesarquivo de classe para fazer isso.
Simplesmente modificamos nosso código principal para instanciar três Botã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 para entrada().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, usando blit(),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 chamada Texto 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 novo Fonte
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ável contador 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 o empate()

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 em BRANCOpara
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 o Botã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 um Teste 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. Quando oBotão Bé clicado6,amyCallBackFunction()
será chamada e imprimirá sua mensagem. Quando oBotã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


7
P YG AMEGUIWIDGETS

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 para catchup,
mostarda,epicles,a função usará o valor padrão de Verdadeiropara 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 um Texto 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 chamada janela:

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 o handleEvent()
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 do enquantoloop 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 o Imagemclasse


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 chamar blit()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 um Grupoclasse 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 em pygwidgets,
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-in ajuda()
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 um Entrada 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étodo Obter 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ários Entrada
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 seu handleEvent()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 em pygwidgets,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 de pygwidgets. 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