Desviando só um pouquinho do
tema deste blog, gostaria de compartilhar este tutorial que montei.
1 - Introdução
Pygame é uma biblioteca da
linguagem Python para desenvolvimento de games.
O download desta pode ser feito em:
http://www.pygame.org/download.shtml
Neste tutorial, vamos utilizá-la
para criar um jogo de plataforma básico (demo).
Além disso, vamos utilizar para a
criação do mapa da tela, o software Tiled, e uma lib para
manipular o arquivo do mapa criado por este.
O download do Tiled pode ser feito
em: http://www.mapeditor.org
O arquivo do mapa gerado pelo Tiled,
é um xml com uma extensão chamada tmx.
Existem algumas
bibliotecas
que foram criadas para manipular este tipo arquivo, e neste caso,
escolhemos trabalhar com a biblioteca do
Richard Jones. Para
isso podemos baixar o arquivo tmx.py do link:
https://github.com/renfredxh/tmx
- Baixe e
instale o Pygame e o Tiled. Após isto, crie um diretório chamado
“demo“ e coloque dentro
dele o arquivo tmx.py.
2 – Criação do
mapa da tela
- Abra a linha de comando do Linux e digite
tiled para abrir o Tiled. Crie um novo arquivo e salve-o no
diretório “demo”.
O
Tiled possui dois tipos de camadas:
Camadas
de tiles
Onde
é desenhado o cenário do mapa através de imagens.
Por
padrão, o mapa já virá com uma camada de tiles criada. Para
adicionar imagens nesta camada, clique no menu Mapa > Novo
Tileset e selecione a imagem
desejada. Isso deve ser feito para cada
imagem.
As
imagens carregadas ficarão disponíveis na lista de tilesets à
direita da tela. Ao clicar em uma imagem após carregada, você vai
perceber que se passar o mouse em cima do mapa, ela estará
disponível para ser carimbada
nele conforme seus cliques.
Além disso, você não precisa carimbar a imagem inteira. Você pode
escolher quais tiles (pedacinhos)
da imagem você quer selecionar.
Camada de objetos
Onde
são adicionados os objetos que ocupam espaço físico ou representam
triggers no mapa. Esta camada normalmente deve ficar invisível
(para isso, basta desmarcar o checkbox dela).
- Para criar uma camada de objetos, clique no menu
Camada > Adicionar camada de objetos, e renomeie a camada
criada como “triggers”.
A
camada de objetos é onde você define paredes, chão, teto,
plataformas, indivíduos, itens etc.
Se
você quer criar, por exemplo, uma parede lateral, você deve
desenhá-la na camada de tiles, e na camada de abjetos adicionar um
retângulo sobre ela.
Ou
seja, o desenho em si não representará nada para o pygame a não
ser uma imagem a ser renderizada, de modo que somente os
objetos (alinhados com a imagem) poderão interagir com o
código.
Os
objetos dessa camada serão enxergados e manipulados
pela biblioteca tmx.
Através
do botão Inserir Objeto (O) no menu principal do topo, crie
os objetos retangulares desejados que representem as paredes, teto,
chão e plataformas do seu jogo. Clique com o botão direito em cada
objeto, selecione Propriedades do objeto e insira uma nova
propriedade com nome “blockers” e valor “tlrb”.
Além
dos objetos retangulares, você pode inserir ainda objetos
carregados a partir de imagens externas. Isso serve para
representar os personagens e itens da tela.
Todavia,
não vamos carregar ainda a imagem diretamente no mapa. Vamos
carregar uma imagem auxiliar onde cada quadradinho da imagem
seja aproximadamente do tamanho de um tile (32x32 px).
Ex.:
Esses
quadradinhos, de quantidade a definir, devem representar cada tipo de
item, personagem ou trigger da tela.
Crie
uma imagem auxiliar que represente os itens do seu jogo em um editor
qualquer e clique em Mapa > Novo Tileset
para adicioná-la.
Clique com o botão direito em cada tile do tileset e
adicione uma propriedade que nomeie o item representado por cada
tile. Ex.: nome “player” e value “yes”.
3 - Codificação
pygame.init()
pygame.joystick.init()
- Crie o objeto que representa a tela (passando como parâmetro o tamanho desta): screen = pygame.display.set_mode((768, 576))
- Defina um temporizador Para atualizar os frames
da imagem a ser renderizada: clock = pygame.time.Clock()
- Carregue o mapa criado no Tiled:
tilemap =
tmx.load('map.tmx', screen.get_size())
Sprites são a
representação de indivíduos, itens etc no jogo, e devem ser
criados para manipular os mesmos.
Ex.1 –
criação de um sprite que representa o jogador (como este é apenas
um único, deve-se escolher o índice 0 do array):
sprites =
tmx.SpriteLayer()
start_cell =
tilemap.layers['triggers'].find('player')[0]
player =
Player((start_cell.px, start_cell.py), sprites)
tilemap.layers.append(sprites)
Ex.2 –
criação do grupo de sprites de inimigos (como são vários, é
necessário inserir em um loop) :
enemies =
tmx.SpriteLayer()
for enemy in
self.tilemap.layers['triggers'].find('enemy'):
Enemy1((enemy.px,
enemy.py), enemies)
tilemap.layers.append(enemies)
Podemos notar que: o nome “triggers”
que é citado agora, é o mesmo que foi designado no mapa do
Tiled para a camada de objetos; o nome passado como parâmetro para a
função find, também foi designado no mapa do Tiled como
propriedade de cada tile representando um item ou indivíduo; a
função find retorna a posição do item no mapa em
relação aos eixos x e y;
Crie grupos
de sprites para todos os indivíduos e itens do seu jogo na tela do
mapa criado. Ex.: player, enemies, bullets, guns, lifes etc.
O jogo precisa
ter um loop principal que manterá o jogo rodando e
sendo atualizado.
Neste loop, você
deve:
-
limitar os frames a serem atualizados por segundo: dt =
clock.tick(30)
-
capturar os eventos
gerados no pygame com o sub
loop:
for event in pygame.event.get():
# fecha o jogo caso haja clique no
“x” da janela
if event.type == pygame.QUIT:
return
- preencher a tela com uma cor
solida, de modo a evitar blurring dos itens renderizados:
screen.fill((250,250,250))
- atualizar o
objeto do mapa conforme frames: tilemap.update(dt / 1000., self)
- desenhar os
itens do objeto do mapa na tela: tilemap.draw(screen)
- atualizar a
tela: pygame.display.flip()
- verificar a
life do player de modo a tomar a ação adequada caso a life tenha
acabado (como por exemplo sair do jogo ou apresentar uma mensagem de
game over).
Para todos os
sprites criados anteriormente, é necessário ter uma classe
com as definições dos mesmos.
O objeto tilemap
deverá manipular os objetos das classes de cada sprite
automaticamente, a partir de algumas funções e atributos
padrão.
Ex.1 –
Classe de indivíduos/itens
- Crie a classe
do sprite desejado passando a classe sprite como parâmetro:
(pygame.sprite.Sprite).
- Carregue a
imagem do indivíduo/item no atributo image: image =
pygame.image.load('...')
A função principal da classe deve ter o seguinte formato básico:
def __init__(self, location,
*groups):
super(NOME,
self).__init__(*groups)
O parâmetro “location” se refere à posição inicial do
indivíduo/item e o parâmetro “*groups” faz referência ao
objeto de sprites localizado na chamada da classe.
A posição
do indivíduo deve ser marcada no atributo “rect”.
Para inicializar
o atributo de localização
do indivíduo/item use:
self.rect =
pygame.rect.Rect(location, self.image.get_size())
É necessário
ter uma função chamada “update” para atualizar os objetos da
classe:
def update(self, dt, game):
- Programe o
incremento ou decremento do atributo “rect” para mover o
indivíduo/item na horizontal, ex.: self.rect.x += 100
* dt
Neste caso o
número 100 é o salto e dt é a ponderação pela velocidade
de atualização de frames, configurada anteriormente.
- Verifique
uma possível colisão
do objeto atual com outros objetos da seguinte forma:
for cell in
game.tilemap.layers['triggers'].collide(self.rect, 'reverse'):
# aqui vão as ações cabíveis em caso de colisão.
No exemplo
acima, estamos verificando a colisão do objeto atual com objetos do
tipo 'reverse'. Lembrando que esses tipos são definidos nas
propriedades do mapa criado no Tiled.
- Verifique se o
objeto atual colidiu com o player:
if
self.rect.colliderect(game.player.rect):
# aqui vão as ações cabíveis em caso de colisão.
Ex.2 –
Classe do player
A classe do
player deve conter quase as mesmas coisas que a classe do exemplo
anterior, pois ele também é um indivíduo/item.
Todavia, haverá
provavelmente um conjunto maior de ações e verificações a serem
feitas.
Exemplos:
Em descanso: self.resting = False
Velocidade: self.dy = 0
Marcação da
life do player.
Leitura do
joystick e teclado:
joystick =
pygame.joystick.Joystick(0)
joystick.init()
key =
pygame.key.get_pressed()
# anda
para frente
if key[pygame.K_LEFT] or
joystick.get_axis(0)<0:
self.rect.x -= 300 *
dt
# anda
para trás
if key[pygame.K_RIGHT] or
joystick.get_axis(0)>0:
self.rect.x += 300 *
dt
# pulo e queda
if self.resting and (
key[pygame.K_SPACE] or int(joystick.get_button(2)) == 1) :
self.dy = -500
self.dy = min(400,
self.dy + 40)
self.rect.y +=
self.dy * dt
Verificação de
colisão com “blockers” (chão, parede ou plataformas):
new = self.rect
self.resting = False
for cell in
game.tilemap.layers['triggers'].collide(new, 'blockers'):
blockers =
cell['blockers']
# limite à
direita
if 'l' in blockers
and last.right <= cell.left and new.right > cell.left:
new.right =
cell.left
#
limite à esquerda
if 'r' in blockers
and last.left >= cell.right and new.left < cell.right:
new.left =
cell.right
# limite do
chão
if 't' in blockers
and last.bottom <= cell.top and new.bottom > cell.top:
self.resting =
True
new.bottom =
cell.top
self.dy = 0
# limite do teto
if 'b' in blockers
and last.top >= cell.bottom and new.top < cell.bottom:
new.top =
cell.bottom
self.dy = 0
É importante
forçar que a janela mantenha o foco no player como item central:
game.tilemap.set_focus(new.x, new.y)
Um vídeo tutorial de Richard
Jones (mais detalhado, porém em inglês) está
disponível no link:
http://pyvideo.org/video/2620/introduction-to-game-programming
Abraços miningnoobs !