Desenvolvimento - Python

Introdução ao PyGame

Nesse artigo vamos criar um jogo bem simples com PyGame, levemente baseado no clássico "Arkanoid".

por Rodrigo Vieira



PyGame (http://www.pygame.org) é uma biblioteca para Python que estende o SDL (biblioteca multimídia multiplataforma, escrita em C) com uma série de módulos para criação de jogos.

Nesse artigo vamos criar um jogo bem simples com PyGame, levemente baseado no clássico "Arkanoid", ou seja, um objeto que quica pela tela e que devemos rebater com uma plataforma. Quando pronto, o jogo deverá parecer assim:

1) Elementos básicos

Como você pode ver acima, o nosso jogo terá 3 elementos principais: o "bouncer", que é a plataforma vermelha no fundo da tela, que rebate a "bola" (bem, o hambúrguer, por falta de imagem melhor!). O terceiro elemento é a tela do jogo em si, onde a ação ocorre.

2) Classe "Bouncer"

Nossa primeira classe é muito simples, herdada da classe Sprite nativa do PyGame. Um sprite é um elemento gráfico do jogo, e possui 2 propriedades importantes: image e rect. Image é a imagem em si, carregada do disco (os principais formatos são suportados, tais como JPG, GIF, PNG e BMP) e rect representa o retângulo virtual que contém a imagem (imagine um retângulo circunscrito à imagem). Alterando as propriedades centerx, centery, top, bottom, left e right de rect podemos posicionar o sprite onde quisermos na tela.

O método update é executado a cada loop do nosso jogo para mover o sprite para esquerda ou para direita, através da função move_ip, que aceita 2 parâmetros, x e y, indicando quantos pixels queremos mover o objeto no eixo x (horizontal) e y (vertical). Vale lembrar que no nosso caso a origem do eixo está no canto superior esquerdo da tela, logo, ao executarmos move_ip(1,2) estaremos movendo o sprite 1 pixel para a direita e 2 pixes para baixo.

class Bouncer(pygame.sprite.Sprite):
    def __init__(self, startpos):
        pygame.sprite.Sprite.__init__(self)
        #direcao: 1=direita, -1=esquerda
        self.direction = 1
	  #carrega a imagem e a posiciona na tela
        self.image, self.rect = load_image("bouncer.gif")
        self.rect.centerx = startpos[0]
        self.rect.centery = startpos[1]
    
    def update(self):
        #multiplicamos x por 3 pro bouncer mover-se
        #um pouco mais rápido!
        self.rect.move_ip((self.direction*3,0))
        #se o bouncer atingir os limites da tela,
        #invertemos a sua direcao
        if self.rect.left < 0:
            self.direction = 1
        elif self.rect.right > width:
            self.direction = -1

3) Classe "Ball"

A nossa segunda classe será bem similar. A única diferença é no método update, que faz com que a bola "quique" pela tela, quando atingir o topo ou os lados. Caso a bola atinja a parte inferior da tela, ela "morre" e é reinicializada na posição inicial.

class Ball(pygame.sprite.Sprite):
    """classe para a bola"""
    def __init__(self, startpos):
        pygame.sprite.Sprite.__init__(self)
        self.speed = [2,2]
        #carrega a imagem e a posiciona na tela
        self.image, self.rect = load_image("ball.gif")
        self.rect.centerx = startpos[0]
        self.rect.centery = startpos[1]
        #salva a posicao inicial para ser reutilizada
        #quando a bola sair da tela pelo fundo
        self.init_pos = startpos
    
    def update(self):
        self.rect.move_ip(self.speed)
        #se a bola atingir os lados da tela, inverte a 
        #direcao horizontal (x)
        if self.rect.left < 0 or self.rect.right > width:
            self.speed[0] = -self.speed[0]
        #se a bola atingir o topo da tela, inverte a
        #posicao vertical (y)
        if self.rect.top < 0: 
            self.speed[1] = -self.speed[1]
        #se a bola atingir o fundo da tela, reseta
        #a sua posicao
        if self.rect.bottom > height:
            self.rect.centerx = self.init_pos[0]
            self.rect.centery = self.init_pos[1]

4) Detectando e reagindo a eventos

O nosso jogo também precisará detectar quando o jogador apertar as setas para direita e esquerda do teclado, para alterar a direção do bouncer, bem como detectar se o jogador fechou a janela, para terminar o jogo. Isso é feito através do seguinte trecho de código:

#checa eventos de teclado
for event in pygame.event.get():
    if event.type == pygame.QUIT: 
        sys.exit()
    elif event.type == pygame.KEYDOWN:
        if event.key == pygame.K_LEFT:
           bouncer.direction = -1
        if event.key == pygame.K_RIGHT:
           bouncer.direction = 1
5) Detectando colisões

Finalmente, precisamos detectar se a bola atingiu o nosso bouncer, e nesse caso inverter a direção vertical da bola. Existem várias formas de detectarmos colisões de sprites, nesse caso usaremos a mais simples de todas, sprite.rect.colliderect(sprite.rect), que retorna "1" caso os retângulos dos 2 sprites estejam se sobrepondo (ou seja, colidindo).

#checa se a bola colidiu no bouncer, e caso sim inverte a #direcao vertical da bola

if bouncer.rect.colliderect(ball.rect):
    if ball.speed[1]> 0:
        ball.speed[1] = -ball.speed[1]

6) Desenhando tudo: "blitting"

A última parte do nosso programa desenhará todos os objetos na tela a cada loop do jogo, de acordo com suas posições atuais (atualizadas através do método update() de cada um).

Após calculadas as novas posições, a forma mais eficiente de atualizarmos a tela é preenchê-la com o fundo preto, para que os objetos antigos (da iteração anterior) sejam apagados: isso é muito mais simples do que ter que apagá-los um a um. O método blit renderiza cada objeto na tela, e quando todos estiverem prontos o método flip irá efetivamente atualizar a tela para o jogador.

#atualiza os objetos
ball.update()
bouncer.update()
    
#redesenha a tela
screen.fill(black)
screen.blit(ball.image, ball.rect)
screen.blit(bouncer.image, bouncer.rect)
pygame.display.flip()

7) Finalizando

Pronto! Agora já temos todos componentes do nosso jogo. Abaixo temos o código completo, que também pode ser baixado aqui. Sinta-se livre para terminar o jogo! Se você quiser checar um jogo um pouco mais complexo (mas não muito), dê uma olhada aqui.

import sys, pygame, os
pygame.init()

size = width, height = 600, 400
screen = pygame.display.set_mode(size)
black = 0, 0, 0

class Bouncer(pygame.sprite.Sprite):
    def __init__(self, startpos):
        pygame.sprite.Sprite.__init__(self)
        #direcao: 1=direita, -1=esquerda
        self.direction = 1
	  #carrega a imagem e a posiciona na tela
        self.image, self.rect = load_image("bouncer.gif")
        self.rect.centerx = startpos[0]
        self.rect.centery = startpos[1]
    
    def update(self):
        #multiplicamos x por 3 pro bouncer mover-se
        #um pouco mais rápido!
        self.rect.move_ip((self.direction*3,0))
        #se o bouncer atingir os limites da tela,
        #invertemos a sua direcao
        if self.rect.left < 0:
            self.direction = 1
        elif self.rect.right > width:

class Ball(pygame.sprite.Sprite):
    """classe para a bola"""
    def __init__(self, startpos):
        pygame.sprite.Sprite.__init__(self)
        self.speed = [2,2]
        #carrega a imagem e a posiciona na tela
        self.image, self.rect = load_image("ball.gif")
        self.rect.centerx = startpos[0]
        self.rect.centery = startpos[1]
        #salva a posicao inicial para ser reutilizada
        #quando a bola sair da tela pelo fundo
        self.init_pos = startpos
    
    def update(self):
        self.rect.move_ip(self.speed)
        #se a bola atingir os lados da tela, inverte a 
        #direcao horizontal (x)
        if self.rect.left < 0 or self.rect.right > width:
            self.speed[0] = -self.speed[0]
        #se a bola atingir o topo da tela, inverte a
        #posicao vertical (y)
        if self.rect.top < 0: 
            self.speed[1] = -self.speed[1]
        #se a bola atingir o fundo da tela, reseta
        #a sua posicao
        if self.rect.bottom > height:
            self.rect.centerx = self.init_pos[0]
            self.rect.centery = self.init_pos[1]

def load_image(name):
    """carrega uma imagem na memoria"""
    fullname = os.path.join("images", name)
    try:
        image = pygame.image.load(fullname)
    except pygame.error, message:
        print "Cannot load image:", fullname
        raise SystemExit, message
    return image, image.get_rect()

def main():
    #cria os nossos objetos (bola e bouncer)
    ball = Ball([100,100])
    bouncer = Bouncer([20,395])
    pygame.display.set_caption("Bouncer!")
    clock = pygame.time.Clock()
    
    while 1:
        #garante que o programa nao vai rodar a mais que 120fps
        clock.tick(120)
        
        #checa eventos de teclado
        for event in pygame.event.get():
            if event.type == pygame.QUIT: 
               sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    bouncer.direction = -1
                if event.key == pygame.K_RIGHT:
                    bouncer.direction = 1
        
        #checa se a bola colidiu no bouncer, e caso
        #sim inverte a direcao vertical da bola
        if bouncer.rect.colliderect(ball.rect):
           if ball.speed[1] > 0:
              ball.speed[1] = -ball.speed[1]
    
        #atualiza os objetos
        ball.update()
        bouncer.update()
    
        #redesenha a tela
        screen.fill(black)
        screen.blit(ball.image, ball.rect)
        screen.blit(bouncer.image, bouncer.rect)
        pygame.display.flip()
    
if __name__ == "__main__":
    main()
Rodrigo Vieira

Rodrigo Vieira - MCSD e MCAD, formado em Ciência da Computacão, trabalhando há 5 anos em uma empresa de telecomunicacões em Oslo, Noruega, desenvolvendo aplicativos para Intranet nas plataformas .Net e Oracle. Entusiasta de Python, Mono, Linux e software livre em geral.
Blog The Spoke:
http://br.thespoke.net/MyBlog/rodviking/MyBlog.aspx