Pygame - Roteiro da apresentação na Latinoware 2013

Latinoware 2013
Python & Pygame
Roteiro de apresentação

Apresentação no Prezi
Mais em http://brodtec.com/programarte

Exemplo:
http://brodtec.com/cap-tulo-17-pong-remix
Ou buscar no Google: Pong Remix Brod (ou só Pong Brod)

0. Introdução

Todos somos programadores
Mercado de jogos
Histórico e características do Pygame
Exemplos de jogos

1. Python em cinco minutos ou menos

Verificar com a plateia quem conhece Python e quem assistiu à palestra sobre Scratch

Python como calculadora

>>> a=9
>>> a ** 0.5
3.0
>>> b=3
>>> a/b
3

2. Importando bibliotecas (módulos)

>>> import math # o comando import serve para carregar uma biblioteca externa
>>> math.sqrt(81) # coloco o nome da biblioteca, um ponto, e a função desejada
9.0
>>> # a função math.sqrt(x) retorna a raiz quadrada de x
...
>>>

Agora você viu também que qualquer coisa que segue o símbolo da cerquilha (#) é ignorada pelo Python. Dessa forma, se quisermos escrever algum comentário após um comando, basta precedê-lo pela cerquilha.

Experimente outras funções da biblioteca math:
>>> math.log10(1000)
3.0
Essa função retornou o logaritmo de 1000 na base 10.
>>> math.sin(math.radians(90))
1.0

Você se lembra da fórmula da área de um círculo cujo raio é 10 centímetros? A área de um círculo é dada pela fórmula:

A = ¶r2

em que A é a área, r é o raio e ¶ é a constante definida pela divisão da circunferência de um círculo pelo seu diâmetro. Lembrou-se das aulinhas de matemática? Veja como esse cálculo pode ser feito com o Python (todas as funções matemáticas da biblioteca math podem ser encontradas aqui: http://docs.python.org/2/library/math.html):

>>> r=10
>>> math.pi*(r**2)
314.1592653589793

[Logo - se der tempo!]

>>> import turtle
>>> turtle.showturtle()
>>> turtle.fd(100)
>>> turtle.lt(120)
>>> turtle.fd(100)
>>> turtle.lt(120)
>>> turtle.fd(100)
>>> turtle.lt(120)

Dentro do Python você pode usar repetições. Para fazer um quadrado você poderia adotar a seguinte solução:

>>> turtle.reset()
>>> contador = 0
>>> while contador < 4:
... contador = contador + 1
... turtle.fd(100)
... turtle.lt(90)

O Zen do Python!

import this

3. Pygame

Ver quem conhece orientação a objetos e falar sobre os ingredientes da receita de um bolo (a parábola do fermento biológico).

wget http://goo.gl/njNFk5 -O bola.png

A primeira animação, linha a linha:

>>> import pygame
>>> pygame.init()
(6, 0)
>>>
>>> tamanho = largura, altura = 500, 420
>>> velocidade = [1, 1]
>>> preto = 0, 0, 0
>>>
>>> cenario = pygame.display.set_mode(tamanho) # aqui aparece a janela para o jogo
>>>
>>> bola = pygame.image.load("bola.png")
>>> bolarect = bola.get_rect()
>>>
>>> while 1: (Falar sobre identação)
... bolarect = bolarect.move(velocidade) # desloca a bola de acordo com a velocidade
... if bolarect.left == 0 or bolarect.right == largura:
... velocidade[0] = -velocidade[0]
... if bolarect.top < 0 or bolarect.bottom > altura:
... velocidade[1] = -velocidade[1]
... cenario.fill(preto)
... cenario.blit(bola, bolarect)
... pygame.display.flip()

Tudo isso em um programa.

4. Esqueleto

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# esqueleto.py - versão 0.99 - Cesar Brod
#
# Para executar esse programa, você deve ter a linguagem Python
# instalada em seu sistema, assim como a biblioteca Pygame.
#
# Usuários de sistemas Debian e seus derivados podem usar seu
# gerenciador de pacotes e instalar python e python-pygame ou, na linha
# de comandos digitar:
#
# sudo apt-get install python python-pygame
#
# Este programa foi desenvolvido para o livro "De Tartaruga à Cobra -
# programação e arte", de Cesar Brod, Editora Novatec, São Paulo, 2013.
#

import sys # reconhece eventos do sistema
import pygame
from pygame.locals import * # constantes para a interação com o sistema
pygame.init() # inicializa as funções da biblioteca Pygame
tamanho = largura, altura = 500, 420 # define o tamanho da tela
preto = 0, 0, 0 # RGB para a cor preta
cenario = pygame.display.set_mode(tamanho) # cria o cenário do jogo
quadros_por_segundo = pygame.time.Clock() # velocidade da animação

while 1: # Repetição infinita, qualquer condição que seja sempre verdadeira
# As linhas abaixo verificam se a ação de fechamento da janela (QUIT) foi
# chamada e, caso positivo, fecha o programa
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()

# *** PAUSA ***
# A linha abaixo introduz uma pausa que pode ser interessante em casos de
# teste. Para que a pausa aconteça, remova o # do começo da linha a seguir.
#
# raw_input("Presione Enter para continuar...")
#
# Durante uma pausa, ou a qualquer momento da execução do programa, pode
# ser importante exibir, na tela, suas variáveis, a fim de testar a lógica
# do programa e os resultados esperados
#
# print 'variavel_1 = ' + str(variavel_1) + ' | variavel_2 = ' + str(variavel_2)
tecla = pygame.key.get_pressed()
cenario.fill(preto) # pinta o cenário de preto
pygame.display.flip() # imprime o cenário atualizado
quadros_por_segundo.tick(60) # animações rodam a 60 quadros por segundo

5. Pong

Cenário

pygame.draw.line(onde, cor, (x_inicial, y_inicial), (x_final, y_final), grossura)

Draw line significa, em português, desenhar linha. Em nosso programa, a coordenada x inicial será a mesma que a final, correspondendo à metade da largura de nossa tela: largura/2. A posição y inicial será o início da tela (y = 0), indo até o total de sua altura (lembre-se de que as coordenadas do eixo y são contadas de cima para baixo) y = altura. Independentemente do tamanho da tela, a grossura da linha será 1/200 avos da largura da tela, mantendo a proporcionalidade caso o programa rode em tela cheia ou em uma janela. Veja, próximo ao final de nosso programa, como fica esta linha.

cenario.fill(preto) # pinta o cenário de preto

# Linha divisória
pygame.draw.line(cenario, branco, (largura/2, 0), (largura/2, altura), largura/200)

pygame.display.flip() # imprime o cenário atualizado

Personagem genérico

Quem conhece orientação a objetos?

[Posicionar acima da repetição infinita]

# Classe básica para todos os personagens do Pong
class personagem(pygame.sprite.Sprite):
def __init__(self, l_personagem, a_personagem):
pygame.sprite.Sprite.__init__(self)
self.l_personagem = l_personagem
self.a_personagem = a_personagem
self.image = pygame.Surface([self.l_personagem, self.a_personagem])
self.image.fill(branco)
self.rect = self.image.get_rect()

Primeiro personagem: a bola

Primeiro, instanciamos a bola e inicializamos suas variáveis, logo antes da repetição infinita:

# Instancia a bola a partir de personagem e inicializa suas variáveis
bola = personagem(largura/40, largura/40)
coordenadas_bola = [largura/2, altura/2]
velocidade_bola = [1, 1]
# Inicializa um grupo de sprites para a animação
personagens = pygame.sprite.Group()
# Adiciona a bola ao grupo de sprites personagens
personagens.add(bola)

A seguir, logo após o teste de condição que verifica se a janela foi fechada, colocamos o código que faz com que nossa bola fique percorrendo a tela.

for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()

# Movimento da boola
bola.rect.center = (coordenadas_bola[0], coordenadas_bola[1])
coordenadas_bola[0] = coordenadas_bola[0] + velocidade_bola[0]
coordenadas_bola[1] = coordenadas_bola[1] + velocidade_bola[1]
if coordenadas_bola[0] > largura or coordenadas_bola[0] < 0:
print 'A bola mudou de direção no eixo x'
velocidade_bola[0] = -velocidade_bola[0]
if coordenadas_bola[1] > altura or coordenadas_bola[1] < 0:
print 'A bola mudou de direção no eixo y'
velocidade_bola[1] = -velocidade_bola[1]

Por fim, temos de garantir que a bola seja impressa na tela antes da atualização do cenário.

personagens.draw(cenario) # imprime os sprites no cenário
pygame.display.flip() # imprime o cenário atualizado

Um som para ajudar!

[Mostrar como encontrar sons no Linux]

if coordenadas_bola[0] > largura or coordenadas_bola[0] < 0:
print 'A bola mudou de direção no eixo x'
velocidade_bola[0] = -velocidade_bola[0]
pygame.mixer.Sound('/usr/share/pyshared/pygame/examples/data/boom.wav').play()

if coordenadas_bola[1] > altura or coordenadas_bola[1] < 0:
print 'A bola mudou de direção no eixo y'
velocidade_bola[1] = -velocidade_bola[1]
pygame.mixer.Sound('/usr/share/scratch/Media/Sounds/Effects/Pop.wav').play()

Raquetes

Com as raquetes, o processo é semelhante:

# Instancia a raquete da esquerda a partir de personagem e inicializa suas variáveis
raquete_e = personagem(largura/40, altura/10)
coordenadas_raquete_e = [largura/25, altura/2]

# Instancia a raquete da direita a partir de personagem e inicializa suas variáveis
raquete_d = personagem(largura/40, altura/10)
coordenadas_raquete_d = [largura - largura/25, altura/2]

# A velocidade será a mesma para ambas as raquetes
velocidade_raquete = [altura/420, altura/420]

# Inicializa um grupo de sprites para a animação
personagens = pygame.sprite.Group()

# Adiciona a bola ao grupo de sprites personagens
personagens.add(bola)

# Adiciona as raquetes ao grupo de sprites personagens
personagens.add(raquete_e)
personagens.add(raquete_d)

O movimento das raquetes

# Movimento das raquetes

# Habilita a verificação de teclas pressionadas
tecla = pygame.key.get_pressed()

# Usa as setas para mover a raquete direita
raquete_d.rect.center = (coordenadas_raquete_d[0], coordenadas_raquete_d[1])
if (tecla[K_UP]):
print 'A seta para cima pressionada'
coordenadas_raquete_d[1] = coordenadas_raquete_d[1] - velocidade_raquete[1]
elif (tecla[K_DOWN]):
print 'A seta para a baixo foi pressionada'
coordenadas_raquete_d[1] = coordenadas_raquete_d[1] + velocidade_raquete[1]

# Usa as teclas A e Z para mover a raquete direita
raquete_e.rect.center = (coordenadas_raquete_e[0], coordenadas_raquete_e[1])
if (tecla[K_a]):
print 'A tecla A foi pressionada'
coordenadas_raquete_e[1] = coordenadas_raquete_e[1] - velocidade_raquete[1]
elif (tecla[K_z]):
print 'A tecla Z foi pressionada'
coordenadas_raquete_e[1] = coordenadas_raquete_e[1] + velocidade_raquete[1]

Teste de colisão (posicionar após os movimentos da bola e da raquete)

# Teste de colisão
if pygame.sprite.collide_rect(bola, raquete_e) or pygame.sprite.collide_rect(bola, raquete_d) == True:
print 'Detectei uma colisão entre bola e raquete! '
pygame.mixer.Sound('/usr/lib/libreoffice/share/gallery/sounds/laser.wav').play()
velocidade_bola[0] = -velocidade_bola[0]

Qual é o problema?

Como funcionou o seu teste? Ainda não muito bem, certo? A questão é que estamos movimentando nossas bolas e raquetes pelas suas coordenadas centrais (rect.center) e a colisão é detectada no momento em que os retângulos se tocam. Ou seja, mesmo que mudemos a velocidade da bola, nos mantemos no estado de colisão e assim ficamos indefinidamente. Uma solução é alterar as coordenadas do eixo x da bola para que, no momento exato da colisão, ela seja posicionada metade da bola, na direção contrária à que a bola está se movimentando. Antes de fazer qualquer modificação, porém, perceba exatamente o que está acontecendo zerando a velocidade da bola no eixo y (assim ela só irá para a frente e para trás), alterando a seguinte linha, logo quando a bola é instanciada em seu programa:

velocidade_bola = [largura/500, largura/500]
para
velocidade_bola = [largura/500, 0]

# Teste de colisão
if pygame.sprite.collide_rect(bola, raquete_e) or pygame.sprite.collide_rect(bola, raquete_d) == True:
print 'Detectei uma colisão entre bola e raquete! '
pygame.mixer.Sound('/usr/lib/libreoffice/share/gallery/sounds/laser.wav').play()
velocidade_bola[0] = -velocidade_bola[0]
if velocidade_bola[0] > 0:
coordenadas_bola[0] = coordenadas_bola[0] + largura/80
if velocidade_bola[0] < 0:
coordenadas_bola[0] = coordenadas_bola[0] - largura/80

O Placar

Inicializa o placar e a fonte (junto com as demais variáveis do programa):

placar_e = placar_d = 0 # o placar inicia zerado
fonte = pygame.font.Font(None, altura/7)

O placar será impresso ao final de nosso programa, após o desenho da linha divisória.

# Placar
placar_e_texto = fonte.render(str(placar_e), 1, branco)
placar_d_texto = fonte.render(str(placar_d), 1, branco)
cenario.blit(placar_e_texto, (largura/2 - largura/5, altura/20))
cenario.blit(placar_d_texto, (largura/2 + largura/10, altura/20))

Agora vamos tratar da atualização do placar e do posicionamento da bola após cada ponto marcado. A modificação será feita na porção do programa que trata do movimento da bola.

# Movimento da bola
bola.rect.center = (coordenadas_bola[0], coordenadas_bola[1])

coordenadas_bola[0] = coordenadas_bola[0] + velocidade_bola[0]
coordenadas_bola[1] = coordenadas_bola[1] + velocidade_bola[1]

if coordenadas_bola[0] > largura or coordenadas_bola[0] < 0:
print 'A bola mudou de direção no eixo x'
pygame.mixer.Sound('/usr/share/pyshared/pygame/examples/data/boom.wav').play()
if velocidade_bola[0] > 0: # Ponto para o jogador da esquerda
placar_e += 1
else: # Ponto para o jogador da direita
placar_d += 1
velocidade_bola[0] = -velocidade_bola[0]
coordenadas_bola[0] = largura/2
Ao mudarmos a posição da bola no eixo x para a metade do cenário (coordenadas_bola[0] = largura/2), é como se teleportássemos a bola, sem alterar a sua velocidade no eixo y, o que adiciona um fator interessante de jogabilidade, já que o jogador que marcou o ponto terá menos tempo para pensar até que a bola atinja, agora, a sua raquete.

6. O programa completo

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# latinoware.py - versão 0.99 - Cesar Brod
#
# Para executar esse programa, você deve ter a linguagem Python
# instalada em seu sistema, assim como a biblioteca Pygame.
#
# Usuários de sistemas Debian e seus derivados podem usar seu
# gerenciador de pacotes e instalar python e python-pygame ou, na linha
# de comandos digitar:
#
# sudo apt-get install python python-pygame
#
# Este programa foi desenvolvido para o livro "De Tartaruga à Cobra -
# programação e arte", de Cesar Brod, Editora Novatec, São Paulo, 2013.
#

import sys # reconhece eventos do sistema
import pygame
from pygame.locals import * # constantes para a interação com o sistema
pygame.init() # inicializa as funções da biblioteca Pygame
tamanho = largura, altura = 500, 420 # define o tamanho da tela
preto = 0, 0, 0 # RGB para a cor preta
branco = 255, 255, 255 # RGB para a cor branca
placar_e = placar_d = 0 # o placar inicia zerado
fonte = pygame.font.Font(None, altura/7)
cenario = pygame.display.set_mode(tamanho) # cria o cenário do jogo
quadros_por_segundo = pygame.time.Clock() # velocidade da animação

# Classe básica para todos os personagens do Pong
class personagem(pygame.sprite.Sprite):
def __init__(self, l_personagem, a_personagem):
pygame.sprite.Sprite.__init__(self)
self.l_personagem = l_personagem
self.a_personagem = a_personagem
self.image = pygame.Surface([self.l_personagem, self.a_personagem])
self.image.fill(branco)
self.rect = self.image.get_rect()

# Instancia a bola a partir de personagem e inicializa suas variáveis
bola = personagem(largura/40, largura/40)
coordenadas_bola = [largura/2, altura/2]
velocidade_bola = [1, 1]

# Inicializa um grupo de sprites para a animação
personagens = pygame.sprite.Group()

# Adiciona a bola ao grupo de sprites personagens
personagens.add(bola)

# Instancia a raquete da esquerda a partir de personagem e inicializa suas variáveis
raquete_e = personagem(largura/40, altura/10)
coordenadas_raquete_e = [largura/25, altura/2]

# Instancia a raquete da direita a partir de personagem e inicializa suas variáveis
raquete_d = personagem(largura/40, altura/10)
coordenadas_raquete_d = [largura - largura/25, altura/2]

# A velocidade será a mesma para ambas as raquetes
velocidade_raquete = [altura/420, altura/420]

# Inicializa um grupo de sprites para a animação
personagens = pygame.sprite.Group()

# Adiciona a bola ao grupo de sprites personagens
personagens.add(bola)

# Adiciona as raquetes ao grupo de sprites personagens
personagens.add(raquete_e)
personagens.add(raquete_d)

while 1: # Repetição infinita, qualquer condição que seja sempre verdadeira
# As linhas abaixo verificam se a ação de fechamento da janela (QUIT) foi
# chamada e, caso positivo, fecha o programa
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()

# Movimento da bola
bola.rect.center = (coordenadas_bola[0], coordenadas_bola[1])
coordenadas_bola[0] = coordenadas_bola[0] + velocidade_bola[0]
coordenadas_bola[1] = coordenadas_bola[1] + velocidade_bola[1]

if coordenadas_bola[0] > largura or coordenadas_bola[0] < 0:
print 'A bola mudou de direção no eixo x'
velocidade_bola[0] = -velocidade_bola[0]
pygame.mixer.Sound('/usr/share/pyshared/pygame/examples/data/boom.wav').play()

# Incrementa o placar
if velocidade_bola[0] > 0: # Ponto para o jogador da esquerda
placar_e += 1
else: # Ponto para o jogador da direita
placar_d += 1
velocidade_bola[0] = -velocidade_bola[0]
coordenadas_bola[0] = largura/2

if coordenadas_bola[1] > altura or coordenadas_bola[1] < 0:
print 'A bola mudou de direção no eixo y'
velocidade_bola[1] = -velocidade_bola[1]
pygame.mixer.Sound('/usr/share/scratch/Media/Sounds/Effects/Pop.wav').play()

# Movimento das raquetes

# Habilita a verificação de teclas pressionadas
tecla = pygame.key.get_pressed()

# Usa as setas para mover a raquete direita
raquete_d.rect.center = (coordenadas_raquete_d[0], coordenadas_raquete_d[1])
if (tecla[K_UP]):
print 'A seta para cima pressionada'
coordenadas_raquete_d[1] = coordenadas_raquete_d[1] - velocidade_raquete[1]
elif (tecla[K_DOWN]):
print 'A seta para a baixo foi pressionada'
coordenadas_raquete_d[1] = coordenadas_raquete_d[1] + velocidade_raquete[1]

# Usa as teclas A e Z para mover a raquete direita
raquete_e.rect.center = (coordenadas_raquete_e[0], coordenadas_raquete_e[1])
if (tecla[K_a]):
print 'A tecla A foi pressionada'
coordenadas_raquete_e[1] = coordenadas_raquete_e[1] - velocidade_raquete[1]
elif (tecla[K_z]):
print 'A tecla Z foi pressionada'
coordenadas_raquete_e[1] = coordenadas_raquete_e[1] + velocidade_raquete[1]

# Teste de colisão
if pygame.sprite.collide_rect(bola, raquete_e) or pygame.sprite.collide_rect(bola, raquete_d) == True:
print 'Detectei uma colisão entre bola e raquete! '
pygame.mixer.Sound('/usr/lib/libreoffice/share/gallery/sounds/laser.wav').play()
velocidade_bola[0] = -velocidade_bola[0]
if velocidade_bola[0] > 0:
coordenadas_bola[0] = coordenadas_bola[0] + largura/80
if velocidade_bola[0] < 0:
coordenadas_bola[0] = coordenadas_bola[0] - largura/80

# *** PAUSA ***
# A linha abaixo introduz uma pausa que pode ser interessante em casos de
# teste. Para que a pausa aconteça, remova o # do começo da linha a seguir.
#
# raw_input("Presione Enter para continuar...")
#
# Durante uma pausa, ou a qualquer momento da execução do programa, pode
# ser importante exibir, na tela, suas variáveis, a fim de testar a lógica
# do programa e os resultados esperados
#
# print 'variavel_1 = ' + str(variavel_1) + ' | variavel_2 = ' + str(variavel_2)

cenario.fill(preto) # pinta o cenário de preto

personagens.draw(cenario) # imprime os sprites no cenário

# Linha divisória
pygame.draw.line(cenario, branco, (largura/2, 0), (largura/2, altura), largura/200)

# Placar
placar_e_texto = fonte.render(str(placar_e), 1, branco)
placar_d_texto = fonte.render(str(placar_d), 1, branco)
cenario.blit(placar_e_texto, (largura/2 - largura/5, altura/20))
cenario.blit(placar_d_texto, (largura/2 + largura/10, altura/20))

pygame.display.flip() # imprime o cenário atualizado
quadros_por_segundo.tick(60) # animações rodam a 60 quadros por segundo



Design: Dobro Comunicação. Desenvolvimento: Brod Tecnologia. Powered by Drupal