Professional Documents
Culture Documents
1
Construindo um jogo MMORPG com Python
Abstract
The electronic game industry have been increasing in the last years, specially
concerning online games. In order to develop a game, different knowledges are
required. This chapter main goal is to create a 2D client of a MMORPG game showing
some of these knowledges as well as coding each part of the game client. Some game
development and network techniques are shown in this chapter. Python language is
introduced and PyGame game library is used to make the proposed MMORPG client
game.
Resumo
O mercado de jogos eletrônicos tem sido expandido nos últimos anos, sendo que jogos
online têm apresentado um aumento ainda maior. A criação de um jogo envolve
diversas áreas de conhecimento. O objetivo deste capítulo consiste na construção de
um cliente 2D para um jogo MMORPG, para tal foi utilizada uma abordagem prática
que descreve a construção do projeto. O capítulo introduz o participante a técnicas de
desenvolvimento de jogos e de desenvolvimento de aplicações que utilizam redes de
computadores. Introduz também a linguagem Python, e utiliza a biblioteca PyGame
para a construção do jogo.
1.1. Introdução
A criação de um jogo é interessante. E não apenas pela diversão que um jogo pode
proporcionar ao jogador, mas por diversos motivos, como o esforço e desafio nas
diversas áreas da computação que são exigidos, o valor cultural que um jogo pode
conter dos desenvolvedores ou no qual os desenvolvedores focam e a maneira de
entretenimento e treinamento de habilidades do jogador.
Os jogos eletrônicos, ou games, têm evoluído em sua complexidade e esforço de
produção, sendo que diversos jogos comerciais atualmente demandam esforço maior do
que a produção de filmes de Hollywood. Esforço que pode ser maior para a modalidade
de jogos MMOG – Multiplayer Massive Online Games, na qual muitos jogadores
participam jogando em um mesmo ambiente, criando mundos virtuais para milhares de
jogadores.
Este capítulo trata da produção de um jogo MMORPG - Multiplayer Massive
Online Role Playing Game, gênero de jogos que criam mundos virtuais para que os
jogadores vivenciem personagens nesses mundos. O jogo é composto de cliente e
servidor, sendo tratada no capítulo apenas a construção do cliente, pois é a que mais
utiliza recursos de desenvolvimento de jogos.
A linguagem Python possui uma sintaxe simplificada que facilita sua
aprendizagem, sendo uma linguagem completa e de escrita rápida. A Pygame é uma
biblioteca em Python que contém módulos para auxiliar na construção de jogos.
As tecnologias utilizadas neste capítulo são Software Livre, um aspecto
interessante para a produção de jogos e desenvolvimento de aplicações. O uso de
Software Livre em jogos sofre algumas resistências, pois em geral o lucro financeiro das
empresas de jogos são exclusivamente dos usuários com a venda de Software, enquanto
que empresas de aplicações em Software Livre têm seu lucro distribuído entre usuários
e outras empresas, além dele nem sempre estar vinculado à venda do produto, mas sim
ao suporte ou à manutenção.
Analisado sob a perspectiva de compartilhamento de conhecimento, o uso de
Software Livre para jogos é positivo, pois a demanda de tecnologias e algoritmos
necessários para a produção de um jogo é muito elevada. As tecnologias livres têm
grande importância para reduzir o custo da produção de um jogo, para melhorar a
qualidade com tecnologias modernas e para compartilhar o conhecimento e as técnicas
de criação de jogos.
Outra motivação para uso de Software Livre é que a produção de jogos
comerciais do gênero MMORPG são de alto custo, passando dos 10 milhões de dólares
(Carpenter, 2003). Investimentos de alto custo para produção de jogos, considerando o
cenário brasileiro, não são comuns, sendo então um dos caminhos para a produção de
jogos com qualidade e com custo reduzido o uso de tecnologias livres.
A criação de um cliente MMORPG será exposta neste capítulo, precedendo a
Seção 1.2 com uma introdução à linguagem Python, com os tópicos necessários para
entendimento do capítulo. A Seção 1.3 descreve mundos virtuais e o conceito de
MMORPG. A partir da Seção 1.4 inicia-se a explicação sobre como produzir jogos,
sendo primeiro introduzida a Pygame, seguida por bibliotecas de jogos em Python e
então é apresentada a estrutura básica do jogo a ser criado. A Seção 1.5 cria interação do
usuário, ou jogador, com o jogo. A Seção 1.6 traz uma introdução ao conceito de Redes
computacionais, com a implementação da conectividade do cliente ao servidor,
encerrando o jogo. Todos os passos do cliente bem como o servidor do jogo estão
disponíveis no link <http://code.google.com/p/pygame-mmorpg/>.
1.2. Conhecendo Python
Muitas linguagens de programação demandam muito tempo de aprendizado, devido à
alta complexidade que as mesmas possuem. Normalmente o aprendizado de algoritmos
escritos em linguagem natural antecede o de linguagens de programação. A sintaxe da
linguagem Python é muito semelhante a uma linguagem natural em formato de
algoritmo, já que dispensa a utilização de chaves para demarcação de blocos, o uso de
ponto-e-vírgula para fim de linha, entre outros fatores que serão expostos nesta seção.
Python é uma plataforma de programação voltada para a agilidade de produção
de aplicações, contando com uma sintaxe simplificada e orientada a objetos (Python
Foundation, 2009). Muitas vezes é classificada como uma linguagem de script1 por ser
ágil, entretanto possui todos os recursos para a construção de grandes aplicações (Reis,
2004). A utilização de uma linguagem como Python para a construção de jogos e de
pequenas aplicações é interessante tanto para contextos educacionais como
profissionais, e durante esse capítulo será possível observar a facilidade e agilidade para
certas rotinas. Ainda, a plataforma bem como a maior parte das bibliotecas é Software
Livre, o que garante a economia e a qualidade do código.
Devido a sintaxe próxima a de algoritmos, alguns programadores habituados
com as linguagens C e Java podem achar o código fonte em Python confuso a primeira
vista, já que o mesmo possui uma estrutura obrigatoriamente padrão, como a identação,
enquanto que possui liberdades como a ausência de declaração de variável.
Lutz & Acher (2003, p.3) citam algumas vantagens da linguagem Python que
motivam os desevolvedores a utilizarem-na, dentre elas destacam-se:
– Qualidade de Software: O código Python foi desenvolvido para ser legível por
humanos, além de ser simplificado. Por isso, a linguagem se torna fácil para
manutenção e alterações.
– Produtividade: O código fica em tamanho reduzido se comparado a código Java ou
C, por consequência é necessário menos esforço para escrever, corrigir erros ou
mantê-lo.
– Portabilidade: Assim como outras linguagens multiplataforma, um código Python
funciona em diferentes plataformas, sendo possível rodar um mesmo código em
Windows, Linux ou outros.
– Bibliotecas: Uma das vantagens em relação a outras linguagens ágeis (ou de script)
é a grande coleção de bibliotecas que a plataforma Python tem. É possível encontrar
bibliotecas que fazem quase todas as tarefas que são necessárias para uma aplicação,
como é o caso de bibliotecas para jogos por exemplo. Muitas bibliotecas são apenas
bindings, ou seja, apenas mapeiam funções para bibliotecas em código nativo do
sistema operacional, garantindo ótimo desempenho.
– Componentes de integração: É possível integrar código Python com outras
linguagens e tecnologias, como C, C++, Java, .NET, Corba ou também utilizar
interfaces como SOAP. Muitas aplicações utilizam abordagens híbridas, como em
jogos, em que muitas vezes o desenvolvimento da parte crítica do jogo, que
consome de fato muito processamento, é realizada com linguagens compiladas
1 Linguagens de script são normalmente assim chamadas por serem utilizadas para conectar aplicações
ou automatizar tarefas.
nativamente como C e C++, e todo o restante que não utiliza grande processamento
é feito com linguagens ágeis, como Python.
A identação, ou recuo que é realizado no código fonte para demarcar um bloco, é
necessária em Python pois apenas ela demarca quando inicia e termina um bloco de
alguma estrutura, como um if ou um for, bem como funções.
Não há necessidade também de declaração de variáveis, ou seja, não é necessário
determinar previamente o tipo das mesmas. O tipo da variável é definido no momento
de sua atribuição, de acordo com o tipo do valor a ser recebido (se é um texto, a variável
é então do tipo texto, se número inteiro, é do tipo inteiro, e assim por diante).
Diferentemente de outras linguagens, essas variáveis tratam-se apenas de ponteiros para
objetos, o que permite que a uma mesma variável possa ser atribuído um texto em um
momento e em outro ser atribuído um número inteiro.
O objetivo desta seção é preparar o leitor com noções básicas de Python. Para
um aprendizado completo, é recomendada a leitura de um livro específico sobre a
plataforma Python. Nas subseções a seguir serão introduzidos conceitos básicos da
linguagem, operadores, estruturas de dados, funções e estruturas de controle, encerrando
com alguns conceitos mais avançados.
Assim que uma linha de código é inserida e encerrada por um símbolo indicador
de fim de linha, o terminal responde com a impressão de algo e disponibiliza para
entrada novamente assim que a execução da linha for encerrada. É possível também
utilizar como calculadora, com soma, multiplicação, e divisão, sendo que / (barra) é
utilizado para divisão comum, como 7/2 resulta em 3 por ser divisão de inteiros, e 7/2.0
resulta em 3.5 por ter um número de ponto flutuante em alguma parte da operação, além
dos símbolos tradicionais de multiplicação (*), soma (+), subtração (-) e módulo (%):
>>> 7+2
9
>>> 8*4-2
30
>>> 7/2
3
>>> 7/2.0
3.5
>>> 7%2
1
Note que na atribuição não houve impressão na tela do valor resultante, pois o
valor foi para a variável, e não para o terminal como na segunda linha. Para melhor
entendimento, pode-se imaginar por hora que sempre há uma função print antes do
código digitado.
A comparação de variáveis é realizada através do operador == ou da expressão
is, e dos símbolos > (maior), < (menor), >= (maior ou igual), <= (menor ou igual). A
diferença entre os dois primeiros é que o operador == retorna a comparação se os
valores são iguais, enquanto que a expressão is retorna se as variáveis apontam para o
mesmo objeto, e essa diferença só será observada quando tratar objetos:
>>> a = 4
>>> b = 7*4
>>> a == b
False
>>> a > b
False
>>> 0 <= 0
True
>>> a == b/7
True
>>> "abc" == "abC"
False
>>> "abc" == "abc"
True
Os métodos que estão entre quatro underscores (__ __) são privados, não devem
ser utilizados por chamadas, mas é possível de qualquer modo. Para chamar funções e
métodos com mais de um parâmetro, basta separá-los por vírgula. Para invocar um
método, utiliza-se um ponto entre o objeto que o possui e o método com os parâmetros,
se houverem, como no uso do método find de uma string:
>>> "abc".find("c")
2
As listas podem conter qualquer tipo de objeto, mesmo misturados. Elas também
são dinâmicas, e permitem que operações de adição de elementos (append), remoção
(remove), multiplicação (*) e concatenação (+) possam ser realizadas:
>>> lista1 = [1,2]
>>> lista2 = [3,4,5]
>>> lista1 + lista2
[1, 2, 3, 4, 5]
>>> lista1*2
[1, 2, 1, 2]
Além das listas, existem também as tuplas (tuples), as quais funcionam também
como vetores, mas estáticos, ou seja, não podem sofrer alterações, mas podem ser
utilizadas para resultarem em uma nova tupla:
>>> tupla = (1,2,3)
>>> tupla
(1, 2, 3)
>>> tupla + (1,2)
(1, 2, 3, 1, 2)
>>> tupla[2]
3
As tuplas podem ser utilizadas sem parênteses também, ou seja, quando houver
valores separados por vírgula, o compilador Python já assume que trata-se de uma tupla:
>>> a, b = 1, 2
>>> a
1
>>> b
2
>>> print 1, 2
1 2
As strings também podem ser vistas como tuplas de caracteres, mas possuem
algumas funções a mais. Isso significa que ambas, quando são concatenadas, por
exemplo, são na realidade instanciadas em um novo objeto, enquanto que em listas, uma
se altera localmente, sem instanciar um novo objeto por exemplo nesse caso:
>>> lista
[1, 'dois', 3.0, [4, 5], 9]
>>> lista += [1,2,3]
>>> lista
[1, 'dois', 3.0, [4, 5], 9, 1, 2, 3]
A função len utilizada na primeira linha retorna o tamanho da lista, e pode ser
utilizada com qualquer objeto que seja de alguma variação de lista, como tuplas e
dicionários. A maneira de acesso aos elementos com apenas um inteiro sempre retorna o
elemento da posição, mas com o uso de : (dois pontos) é possível criar sublistas, sendo
o primeiro elemento referente à posição inicial e o segundo à posição final (note que o
elemento da posição final não é colocado na sublista). É possível ainda utilizar índices
negativos para percorrer a lista do final para o começo ou utilizar espaço vazio para
demarcar começo e fim:
>>> lista[0:-2]
[1, 'dois', 3.0]
>>> lista[-5:-2]
[1, 'dois', 3.0]
>>> lista[:]
[1, 'dois', 3.0, [4, 5], 9]
>>> lista[:-1]
[1, 'dois', 3.0, [4, 5]]
Outra estrutura muito utilizada é o dicionário, que funciona como uma lista, mas
ao invés de índices numéricos, o acesso é por qualquer tipo de índice, por meio de uma
chave, como strings e outros objetos. Para criar um dicionário, basta enumerar entre
vírgulas pares chave:valor (entre dois pontos) entre chaves ({ }):
>>> dicionario = {1:2, "dois":4, 5.0:"cinco ponto zero"}
>>> dicionario[1]
2
>>> dicionario["dois"]
4
Para utilizar mais de uma condição, utilizam-se os operadores not, and e or:
>>> if 1 and 2 and not False:
... print "ok"
...
ok
A estrutura for sempre se baseia em uma variável dentro de uma lista, em que o
código aninhado executa uma variável assumindo cada objeto da lista. Por esse motivo,
o mais básico for necessita da função range que retorna uma lista de números
consecutivos, mas a maior vantagem é utilizar elementos em listas, nas quais a variável
que segue a palavra for é a variável que recebe o valor da lista a cada passo:
>>> range(5)
[0, 1, 2, 3, 4]
>>> for i in range(5):
... print i
...
0
1
2
3
4
>>> lista = ["abc", "def", "qualquer", "coisa"]
>>> for elemento in lista:
... print elemento
...
abc
def
qualquer
coisa
1.2.6. Tratamento de erros
O tratamento de erros em Python é um recurso poderoso, que possibilita tanto encontrar
falhas no código como tratar possíveis erros diretamente no código. O terminal
interativo exibe as possíveis mensagens de erro quando alguma linha de código é
executada, como utilização de uma variável sem valor atribuído:
>>> variavel
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'variavel' is not defined
É possível fazer diversos except com a classe de erro em seguida, para tratar
diferentes erros que uma chamada pode realizar. Caso não se especifique uma classe, o
except será executado em qualquer erro, caso não tenha sido tratado por um except
anterior.
O parâmetro 'w' define modo de escrita, mas poderia ser utilizado 'a' para
escrever no final de um arquivo já existente ou criar um novo caso não exista. Quando o
segundo parâmetro é omitido, o arquivo é aberto como leitura, mas poderia ser utilizado
também 'r'. Em Python, quando omitimos algum parâmetro de uma função ou de um
método, é assumido o valor padrão do mesmo. Para mais detalhes dos parâmetros, a
função help pode ser utilizada.
Dentre os diversos métodos que o objeto retornado pela função file têm, os
métodos read e readlines são utilizados para leitura do conteúdo do arquivo e write para
escrita.
Para a criação de uma função, utiliza-se a palavra def para demarcar que é um
bloco de uma função, seguido do nome da função a ser dado e, entre parênteses, uma
lista dos parâmetros a serem dados, que são nomes para variáveis:
>>> def funcao(var1, var2):
... print var1, var2
...
>>> funcao(1,2)
1 2
>>> funcao("oi", "tchau")
oi tchau
2 http://www.pygame.org/
para o mesmo aparecer. É recomendado que, quando importar o módulo pygame
também seja invocado a função init(), que inicializa os módulos da Pygame:
>>> import pygame
>>> pygame.init()
(6, 0)
O módulo possui diversas funções para trabalhar com a tela em que o jogo está
sendo executado. Por hora, apenas a função set_mode será explicada e utilizada. Essa
função cria uma superfície de desenho da Pygame em uma janela (caso não seja em tela
cheia), de acordo com os parâmetros passados, sendo o primeiro uma tupla de dois
valores com a resolução da tela (em pixels):
>>> screen = pygame.display.set_mode((640, 480))
>>> screen
<Surface(640x480x32 SW)>
3 http://www.pygame.org/docs/ref/display.html#pygame.display.set_mode
>>> clock = pygame.time.Clock()
>>> type(clock)
<type 'Clock'>
Uma observação é que, quando executado o código, deverá ser fechado como
aplicação “travada”. Agora a estrutura principal do jogo está funcionando. Apesar de ser
apenas uma tela preta, o loop já está em funcionamento e controlado.
Entretanto, para este exemplo será utilizado o módulo cPickle, um binding da biblioteca
Pickle construído em C, que possui em média desempenho superior ao módulo pickle
que utiliza apenas código Python.
O módulo cPickle armazena e carrega objetos Python em arquivos. Além dos
tipos básicos de Python, como listas e dicionários, esse módulo também armazena
objetos de classes novas, tornando-o útil para ser utilizado para salvar e carregar o
estado atual dos jogos. Dois métodos serão úteis neste capítulo: dump e load (Doll,
2002). O método load carrega as informações de um arquivo dado como parâmetro,
enquanto que o método dump armazena no segundo parâmetro (arquivo) o objeto
passado como primero parâmetro:
>>> lista = [1.0, 2, "tres"]
>>> import cPickle
>>> cPickle.dump(lista, file("arquivo.dat", "w"))
>>> nova_lista = cPickle.load(file("arquivo.dat"))
>>> nova_lista
[1.0, 2, 'tres']
>>> nova_lista is lista
False
>>> nova_lista == lista
True
4 Pixels são pontos de uma imagem, na qual cada ponto assume uma cor a fim de gerar com vários
pontos de várias cores.
O mapa presente no arquivo “tilemap.txt” possui uma matriz de inteiros
variando de 0 a 23, na qual cada número é o correspondente a imagem “tileN.png” do
diretório “imagens”. Dessa maneira, o arquivo tile0.png é o que representa o tile de
código 0 da matriz, e assim por diante.
Apesar de facilitar o entendimento, seria mais eficiente se a matriz fosse
representada como uma cadeia de caracteres por exemplo, na qual cada letra, número ou
componente da cadeia representasse uma imagem, pois listas são objetos Python muito
mais custosos para serem criados e manipulados do que cadeias de caracteres.
Para representar a posição de árvores, casas e pedras, é utilizado um outro
arquivo de mapa, o “objmap.txt”. Também de dimensão 500 por 500 quadros, esse
arquivo representa se há algum objeto em cada posição:
>>> objmap = cPickle.load(file("data/objmap.txt"))
>>> objmap[0][0]
255
>>> len(objmap)
500
5 Renderizar é o ato de transformar os dados em imagem final, imprimindo-o na tela por exemplo.
>>> import pygame
>>> pygame.image.load(file('imagens/tile0.png'))
<Surface(40x40x24 SW)>
O que acontece no código exposto é que uma lista é inicializada vazia (tile = [] e
obj = []) e o laço é executado com a variável i variando de 0 a 23. Cada execução
concatena a lista anterior com uma nova lista contendo um elemento, em que cada
interação do laço carrega o arquivo com nome “tileN.png” ou “objN.png”, onde N é o
número que utiliza a tag “%s”. Tal tag faz com que o valor nessa posição seja da
variável i, presente logo após a string conectada com um sinal de módulo (%).
Uma vez carregada uma imagem em um objeto de superfície (Surface), este pode
ser combinado com outras superfícies, como a tela principal do jogo ou outras imagens
carregadas, através do método blit. O método coloca a superfície passada no primeiro
parâmetro sobre a própria superfície, na posição fornecida pelo segundo parâmetro em
pixels:
>>> pygame.init()
(6, 0)
>>> screen = pygame.display.set_mode((640,480))
>>> screen.blit(tile[0], (0,0))
<rect(0, 0, 40, 40)>
É possível que nenhuma alteração seja exibida pelo terminal Python já que o
processo do jogo não está em execução ainda. Portanto é preciso editar em um arquivo
fonte para execução em modo cliente, também pelo tamanho e pela complexidade atuais
do código. Pode-se também fazer um laço infinito no terminal para funcionar, pois
quando o terminal é bloqueado para um laço de atualizar a tela, esta é atualizada
corretamente.
Como o segundo parâmetro do método blit das superfícies é a posição, é
necessário fazer no loop do jogo uma maneira de atualizar sempre toda a tela, cada
objeto em sua posição correta. Faz-se uso então de três constantes:
>>> WIDTH, HEIGHT, TILE = 16, 11, 40
No caso, em um loop de jogo, o que o trecho acima faz é imprimir o tile[1] (uma
textura de gramado) em todas as posições da tela, e a função flip do módulo display
atualiza toda a tela, ou seja, renderiza todos os objetos na tela a cada chamada. Para
finalizar esse loop infinito a combinação de teclas “CTRL + C” finaliza o loop. Nota
para a posição “(x*TILE, y*TILE)”, que retorna a posição (x,y) da matriz 16x11
posições, e multiplica o tamanho do quadro para obter-se o valor da posição em pixels
na tela.
Idealmente os objetos poderiam ser renderizados também em tiles, ou seja, em
quadros. Entretanto os objetos estão em imagens inteiras, o que é um pouco mais lógico
do ponto de vista de armazenamento das imagens e de renderização na tela já que o
objeto é manipulado por inteiro.
O método blit renderiza uma superfície na outra considerando o ponto de
coordenada (0,0) a parte superior esquerda da superfície de parâmetro, ou seja, para
imagens maiores do que um tile o restante da imagem se sobrepõe aos quadros abaixo e
da direita ao quadro que se passou de parâmetro para renderização. A Figura 4 mostra o
maior objeto presente no jogo, um pedaço de uma casa, que ocupa 4 por 6 quadros.
Figura 4: Pedaço de uma casa do jogo, o objeto possui o
tamanho de 4 quadros horizontais e 6 verticais
Dessa maneira, o loop do jogo deve começar em posições negativas apenas para
os objetos, além de só renderizar objetos com ID menor que 24, já que só existem
imagens de objetos até o número 23 (poderiam haver mais objetos no jogo). O loop
então fica da seguinte maneira:
while running:
clock.tick(7)
for y in range(HEIGHT):
for x in range(WIDTH):
screen.blit(tile[tilemap[y][x]], (x*TILE, y*TILE))
for y in range(-6, HEIGHT):
for x in range(-4, WIDTH):
if objmap[y][x] < 24:
screen.blit(obj[objmap[y][x]], (x*TILE, y*TILE))
pygame.display.flip()
for i in range(24):
tile += [pygame.image.load(file('imagens/tile%s.png'%i))]
obj += [pygame.image.load(file('imagens/obj%s.png'%i))]
while running:
clock.tick(7)
for y in range(HEIGHT):
for x in range(WIDTH):
screen.blit(tile[tilemap[y][x]], (x*TILE, y*TILE))
for y in range(-6, HEIGHT):
for x in range(-4, WIDTH):
if objmap[y][x] < 24:
screen.blit(obj[objmap[y][x]], (x*TILE, y*TILE))
pygame.display.flip()
Uma imagem estática do mapa com algumas árvores será exibida em uma janela,
que não irá se fechar sem ser manualmente.
No caso, pelo terminal interativo inicializado, virá uma lista vazia pois não há nem
sequer uma janela criada. Então, inicializando a janela e pressionando algumas letras do
teclado na janela, o comando retornará as teclas pressionadas bem como ações do
mouse:
>>> screen = pygame.display.set_mode((640,480))
>>> pygame.event.get()
[<Event(3-KeyUp {'scancode': 0, 'key': 300, 'mod': 4096})>,
<Event(1-ActiveEvent {'state': 2, 'gain': 1})>, <Event(2-KeyDown
{'scancode': 38, 'key': 97, 'unicode': u'a', 'mod': 4096})>,
<Event(3-KeyUp {'scancode': 38, 'key': 97, 'mod': 4096})>, <Event(2-
KeyDown {'scancode': 56, 'key': 98, 'unicode': u'b', 'mod': 4096})>,
<Event(3-KeyUp {'scancode': 56, 'key': 98, 'mod': 4096})>, <Event(2-
KeyDown {'scancode': 54, 'key': 99, 'unicode': u'c', 'mod': 4096})>,
<Event(3-KeyUp {'scancode': 54, 'key': 99, 'mod': 4096})>, <Event(1-
ActiveEvent {'state': 2, 'gain': 0})>, <Event(3-KeyUp {'scancode':
0, 'key': 300, 'mod': 4096})>]
A lista contém objetos do tipo Event, uma classe da Pygame que simboliza um
evento. Cada objeto dessa classe tem atributos, os quais possuem um tipo e atributos
variáveis de acordo com o tipo de evento. O atributo type determina o tipo, um valor
numérico que é representado por uma constante conforme listagem na documentação
oficial (Pygame, 2008b) :
>>> eventos = pygame.event.get()
>>> evento = eventos[0]
>>> evento
<Event(1-ActiveEvent {'state': 4, 'gain': 1})>
>>> evento.type
1
>>> evento.state
4
>>> evento.gain
1
O primeiro parâmetro de um evento é o tipo, que pode ser utilizado por uma das
constantes ou um valor numérico, e o segundo parâmetro é um dicionário com os
atributos e seus valores. Dessa maneira é possível criar eventos personalizados:
>>> e = pygame.event.Event(25, {'ano': 2009})
>>> pygame.event.post(e)
>>> pygame.event.get(25)
[<Event(25-UserEvent {'ano': 2009})>]
>>> e.ano
2009
Em que uma estrutura for cuida de cada evento retirado da fila, passando pelo
teste do if/else em relação ao tipo do evento e tratando-o. Com o loop acima, finalmente
o jogo fecha quando se deseja fechá-lo. É possível também utilizar diversas funções get
com diferentes parâmetros, e isso torna possível tratar tipos de eventos em momentos
diferentes, como por exemplo eventos de ação do jogador em 7 quadros por segundo
mas tratar fechamento da janela instantaneamente (seria necessário um loop em paralelo
ou contagem distinta de quadros por segundo).
Existem ainda alguns eventos personalizados que serão utilizados no jogo:
GOTO = 24
MESSAGE = 25
SETOBJECT = 26
SYSTEM = 27
GETINFO = 28
6 Alguns programadores são contra o uso de variáveis e constantes no plural para simbolizar listas e
dicionários, mas o plural pode evidenciar que se trata de uma lista ou um dicionário.
pygame.event.post(e)
Se a velocidade não é (0,0), cria um novo evento GOTO, que possui dois
atributos: x e y. Cada atributo representa a coordenada de destino, e portanto é
necessário somar a velocidade ao deslocamento da tela (slide_x ou slide_y) e o
deslocamento do personagem em relação ao canto superior esquerdo da tela, em cada
um dos eixos horizontal e vertical. Criado o evento, a função post adiciona o evento na
fila da Pygame.
Começando com valor nulo para o personagem permanecer parado. Então, cria-
se uma função apenas para tratar eventos, ao invés de deixar o tratamento de eventos no
loop e poluir o código. A função handle deverá cuidar de cada tipo de evento que vier,
dos que forem interessante para serem processados:
def handle():
global running, moving
for e in pygame.event.get():
if e.type == QUIT:
running = False
elif e.type == KEYDOWN:
if e.key in MOVES.keys():
moving = (moving[0] + MOVES[e.key][0], moving[1] +
MOVES[e.key][1])
elif e.type == KEYUP:
if e.key in MOVES.keys():
moving = (moving[0] - MOVES[e.key][0], moving[1] –
MOVES[e.key][1])
elif e.type == GOTO:
goto(e.x, e.y)
Foi necessário utilizar a função tuple, que retorna uma tupla vazia, para passar
que não há parâmetros na função. O retornado antes da impressão do texto, o número
que varia a cada chamada, é o código da thread, que pode ser utilizado para manipulá-
la.
O método listen inicializa o socket para que ele comece a esperar por conexões
na porta fornecida. Agora, será definida uma função para rodar em uma thread, que
receberá a conexão do cliente e imprimirá os textos recebidos na tela. Primeiro é
necessário aceitar a conexão com o método accept, que retornará um objeto com a
conexão gerada pelo cliente e um objeto com informações (não será utilizado), e então é
possível imprimir mensagens com o método recv:
>>> def server_loop():
... connection, info = server.accept()
... msg = ""
... while msg != "fechar":
... msg = connection.recv(9999)
... print "server " + msg
...
>>>
>>> thread.start_new_thread(server_loop, tuple())
-1211667568
Basta agora utilizar o método send para enviar mensagens de texto. O método
precisa como parâmetro apenas uma cadeia de caracteres, e retorna o número de bytes
enviados:
>>> client = socket.socket()
>>> client.connect(("localhost", 5050))
>>> client.send("olá!")
5
server olá!
>>> client.send("testando denovo!")
server testando denovo!
16
7 agora coloca-se “localhost” como endereço, para conexão local. Poderia ser o IP de um servidor.
O uso de threads pode possibilitar ainda o servidor aceitar diversos clientes
simultaneamente, bem como conexões P2P. Para o jogo em construção, será feita
apenas uma conexão, mas que irá receber e enviar mensagens ao servidor.
Como o servidor não prevê alteração de nome e ID, pode ser uma constante.
A movimentação do personagem agora deverá ser validada pelo servidor. Isso
ocorre para facilitar a criação do código e, principalmente, para que a validação dos
movimentos dos jogadores seja controlada pelo servidor, que funciona como uma
entidade supervisora para que não hajam movimentos fraudulentos, como por exemplo
um jogador andar mais do que o possível ou atravessar paredes.
Alguns jogos utilizam as duas abordagens, a validação de movimentos tanto no
servidor quanto no cliente, a fim de evitar o envio de mensagens desnecessárias ao
servidor (por exemplo, se um personagem tenta andar para cima de uma árvore, esse
movimento nem precisa ser enviado ao servidor, pois será negado).
O cliente apenas enviará o evento do tipo GOTO para o servidor, ao invés de
processar localmente. O que será processado pela função goto local serão apenas as
mensagens recebidas pelo servidor, que darão as coordenadas para onde o personagem
de fato deve ir:
45| def move((inc_x, inc_y)):
46| if inc_x != 0 or inc_y != 0:
47| e = Event(GOTO, {'x': slide_x + inc_x + MIDDLE[0], 'y':
slide_y + inc_y + MIDDLE[1]})
48| send(str(e))
A função send, a ser construída, envia mensagens do tipo string para outro
socket, pois sockets em Python trabalham com strings. Por isso, é preciso transformar o
objeto do tipo Event da Pygame em uma cadeia de caracteres, com a função str.
É necessário então criar a conexão com o servidor, mas antes disso deve-se criar
um laço para receber mensagens do servidor, pois o recebimento de mensagens trava o
processo do jogo e nem sempre é previsível quando o servidor vai mandar uma
mensagem (como por exemplo se houvesse um recurso de bate-papo).
A função listener será criada para ser executada em uma thread e irá receber
mensagens vindas do servidor. O servidor só envia dois tipos de mensagens: ou são
mensagens de texto de algum problema do sistema (até mesmo erros de conexão) ou são
eventos da Pygame, em uma lista de eventos. Por isso, a cada mensagem, deve ser
processada tal lista em formato de cadeia de caracteres para uma lista Python (já que
sockets de Python se comunicam por cadeias de caracteres), e colocado cada evento da
lista na fila da Pygame:
50| def listener():
51| while running:
52| msg = conn.recv(999999)
53| if msg != "":
54| try:
55| for m in eval(msg):
56| pygame.event.post(m)
57| except:
58| print "Servidor: ",msg
Tratar os eventos do tipo MESSAGE com a função settext fará com que as
mensagens sejam apresentadas na tela do jogador, na faixa preta da tela. O atributo pos
possui as coordenadas em pixels do clique do mouse, que devem ser divididas pelo valor
de TILE para se obter a posição na matriz de renderização, e somar-se ao deslocamento
do mapa para ter a posição do objeto no mapa global. É preciso tratar ainda mensagens
do tipo SYSTEM, que se referem a mensagens ao sistema não interessantes ao jogador
mas sim ao desenvolvedor, como informações de conexão ou tentativas de burlar o
servidor:
95| elif e.type == SYSTEM:
96| print e.msg
Referências
ANDROUTSELLIS-THEOTOKIS, S. and SPINELLIS, D. A survey of peer-to-peer
content distribution technologies. ACM Computing Surveys, 2004, vol. 36, p. 335–
371.
ACHTERBOSCH, L., PIERCE, R., and SIMMONS, G.. Massively multiplayer online
role-playing games: the past, present, and future. Computers in Entertainment, 2008,
vol. 5, p. 1-33.
Blizzard. 2008. [On line]. World of Warcraft. Blizzard Enterteinement. Available from:
<http://www.worldofwarcraft.com>. Access in: 2009 March 9.
CARPENTER, A., 2006. [On line]. Applying risk analysis to play-balance RPGs. In
Gamasutra features. Applying risk analysis to play-balance RPGs. Available
from:<http://www.gamasutra.com/features/20030611/carpenter_01.shtml>. Access
in: 2009 March 6.
DOLL, S., 2002. [On line] Get yourself into a Python cPickle. Available from:
<http://articles.techrepublic.com.com/5100-10878_11-1052190.html>. Access in:
2009 February 19.
Eletronic Arts Inc. 2008. [On line]. Ultima Online. Available from: <
http://www.uoherald.com/news/>. Access in: 2009 March 7.
HASSEY, P., 2007. [On line]. Phil's pyGame utilities. Available from:
<http://www.imitationpickles.org/pgu/wiki/index>. Access in: 2009 March 8.
HOLKNER, A., 2008. [On line] Pyglet. Available from: <http://www.pyglet.org/>.
Access in: 2009 March 8.
KUROSE, JF. and ROSS, KW. Redes de computadores e a Internet: uma abordagem
top-down. São Paulo: Person Addison Wesley, 2005. 656p.
Lucasfilm Ltda. 2002. [On line]. Take Part in a Thriving Star Wars Community. In Star
Wars Galaxies. Available from: <
http://starwarsgalaxies.station.sony.com/players/index.vm>. Access in: 2009 March
8.
LUTZ, M. and ASCHER, D. Learning python. Sebastopol, CA: O'Reilly \&
Associates, Inc., 2003. 593p.
PYGAME. 2008a. [On line]. Pygame documentation: display. Available from:
<http://www.pygame.org/docs/ref/display.html>. Access in: 2009 March 8.
PYGAME. 2008b. [On line]. Pygame documentation: event. [cited 8 March 2009].
Available from: <http://www.pygame.org/docs/ref/event.html>. Access in: 2009
March 8.
PYGAME. 2008c. [On line]. Pygame documentation: font. Available from:
<http://www.pygame.org/docs/ref/font.html>. Access in: 2009 March 8.
PYGAME. 2008d. [On line]. Pygame documentation: image. Available from:
<http://www.pygame.org/docs/ref/image.html>. Access in: 2009 March 8.
PYGAME. 2008e. [On line]. Python game development. Available from:
<http://www.pygame.org/news.html>. Access in: 2008 November 29.
PYODE. 2007. [On line]. Available from: <http://pyode.sourceforge.net/>. Access in:
2009 March 8.
Python Foundation. 2009. [On line]. Python programming language. Available from:
<http://www.python.org/>. Access in: 2009 February 19.
PYTHON-OGRE. 2009. [On line]. Available from: <http://www.python-ogre.org/>.
Access in: 2009 March 8.
REIS, CR. 2004. [On line]. Python na prática: um curso objetivo de programação em
Python. Available from: <http://www.async.com.br/projects/python/pnp/>. Access
in: 2008 November 29.
ROCHA, HV. and BARANAUSKAS, MCC. Design e avaliação de interfaces humano-
computador. Campinas: Universidade Estadual de Campinas - Unicamp, 2003.
Sony. 2008. [On line]. EverQuest – Massively Multiplayer Online Fantasy Role-Playing
Game. Available from: <http://everquest.station.sony.com/>. Access in: 2009 March
8.
SOYA3D. 2008. [On line]. Available from:
<http://home.gna.org/oomadness/en/soya3d/index.html>. Access in: 2009 March 8.
Square-Enix. 2008. [On line]. Final Fantasy IX. Available from: <http://na.square-
enix.com/games/FFIX-gamesite/>. Access in: 2009 March 8.
TANENBAUM, A. and WOODHULL, A. Operating Systems design and
implementation. [S.L.]: Pearson; Prentice Hall, 2006.
The Panda3D development team. 2008. [On line]. Panda3D. Available from:
<http://panda3d.org/>. Access in: 2009 March 8.