Tutorial de Chimp Linea por Linea
Fuentes
Este documento es una traducción del documento "Line By Line Chimp" por Pete Shinners. El original se encuentra en: http://www.pygame.org/docs/tut/chimp/ChimpLineByLine.html
Introduccion
En los ejemplos de pygame hay uno que se llama chimp. este ejemplo simula un mono que se puede pinchar con el mouse que se mueve por una pequeña pantalla con promesas de riquezas y recompensas,el ejemplo por si mismo es muy simple, y un poco suave en cuanto a chequeo de errores, el programa de ejemplo ilustra muchas de las habilidades de pygame, como crear una ventana grafica, cargar imagenes y archivos de sonido, rederizar texto TTf, y manejo basico de eventos del mouse.
El programa y las imagenes pueden ser encontradas dentro de la distribucion estandar de codigo de pygame. Para la version 1.3 de pygame este ejemplo fue totalmente reescrito para añadir mas caracteristicas y corregir el chequeo de errores. Esto casi dobla el tamaño del ejemplo original, pero nos da mucho que mirar, asi como codigos que recomiendo usar para sus propios proyectos.
Este tutorial ira por el codigo de bloque en bloque. explicando como trabaja el codigo, tambien se mencionara como puede ser mejorado el codigo y que chequeo de errores puede ayudar.
Este es un exelente tutorial para personas que dan su primera mirada al codigo de pygame.una vez que pygame sea totalmente instalado puedes encontrar y correr el demo de chimp en el directorio de ejemplos.
Modulos Importantes
Este es el codigo que importa todosa los modulos necesarios en su programa, tmabien prueba la disponibilidad de algunos de los modulos opcionales de pygame.
1 import os, sys
2 import pygame
3 from pygame.locals import *
4
5 if not pygame.font: print 'Warning, fonts disabled'
6 if not pygame.mixer: print 'Warning, sound disabled'
Primero importamos los modulos estandart "os" y "sys" de python.Estos nos permiten hacer cosas como crear rutas de archivos independientes de la plataforma. En la siguiente linea, importamos el paquete de pygame.Cuando pygame es importado importa todos los modulos que pertenecen a pygame.Algunos modulos de pygame son adicionales, y si no se encuentran su valos se pone como cero.
Hay un modulo especial de pygame llamado "locales" este modulo contiene un subconjunto de pygame.los miembros de este modulo son constantes comunmente usadas y funcuiones que han probado ser utiles para poner en el espacionombre global de su programa.Este modulo local incluye funciones como "rect" para crear un objeto rectangular, y muchas constantes como "QUIT, HWSURFACE" que son usadas para interactuar con el resto de pygme.Importando el modulo de locales en el espacionombre global asi es enrteramente opcional, si tu decides no importarlo, todos los miembros de las locales estan siempre disponibles en el modulo de pygame.
Finalmente decidimos imprimir un lindo mensaje de precaucion si los modulos de fuentes o sonidos en pygame no estan disponibles.
Cargando los Recursos
Aca tenemos dos funciones que podemos usar para cargar imagenes y sonidos, miraremos a cada funcion individualmente en esta seccion
1 def load_image(name, colorkey=None):
2 fullname = os.path.join('data', name)
3 try:
4 image = pygame.image.load(fullname)
5 except pygame.error, message:
6 print 'Cannot load image:', name
7 raise SystemExit, message
8 image = image.convert()
9 if colorkey is not None:
10 if colorkey is -1:
11 colorkey = image.get_at((0,0))
12 image.set_colorkey(colorkey, RLEACCEL)
13 return image, image.get_rect()
Esta funcion toma el nombre de una imagen a cargar, tambien opcionalmente toma un argumento que puede usar para configurar una clave de color.Una clave de color es usada en graficos para representar un color de imagen que es tranparente.
La primera cosa que esta fincion hace es crear un nombre completo de ruta al archivo.En este ejemplo todos los recursos estan en el subdirectorio "data", usando la funcion os.path.join, una ruta sera creada para que trabaje en cualquier plataforma en la cual el juego este corriendo.
Luego cargamos la imagen usando la funcion pygame.image.load. envolvemos esta funcion en un bloque prueba/exepcion, entonces si hay un problema cargando la imagen podremos salir facilmente.Despues de que la imagen sea cargada, hacemos una importante llamada a la funcion convert(), esto hace una nueva copia de una superficie y convierte su formato de color y profundidad para que se ajuste al display.Esto significa que la imagen se ajustara a la pantalla lo mas rapido posible. Por ultimo hacemos la clave de color para la imagen. si el usuario da un argumento para el argumento de clave de color usamos ese valor como la clave de color para la imagen.Usualmente este sera un valor RGB comun, como (255,255,255) para blanco. tambien puedes pasar un valor de -1 como clave de imagen. en este caso la funcion mirara el color en el pixel superior izquierdo de la imagen y usara ese color para la clave de color.
1 def load_sound(name):
2 class NoneSound:
3 def play(self): pass
4 if not pygame.mixer:
5 return NoneSound()
6 fullname = os.path.join('data', name)
7 try:
8 sound = pygame.mixer.Sound(fullname)
9 except pygame.error, message:
10 print 'Cannot load sound:', wav
11 raise SystemExit, message
12 return sound
Luego esta la funcion para cargar un archivo de sonido: la primera cosa que esta funcion hace es probar a ver si el modulo pygame.mixer fue importado correctamente. si no devuelve una instancia de clase pequeña de clase que tiene un metodo de "play" bobo.ESto actuara lo suficiente como un objeto normal de sonido para que este juego corra sin un chequeo de error extra.
Esta funcion es similar a la funcion de cargado de imagenes, pero trata algunos problemas diferentes. primero creamos una ruta completa a la imagen del sonido, y cargamos el archivo de sonido dentro de un bloque de prueba/exepcion. luego simplemente retornamos al objeto de sonido cargado.
Clases de los Objetos del Juego
Aca creamos dos clases para representar los objetos en nuestro juego.Casi toda la logica para el juego va en estas dos clases.
1 class Fist(pygame.sprite.Sprite):
2 """moves a clenched fist on the screen, following the mouse"""
3 def __init__(self):
4 pygame.sprite.Sprite.__init__(self) #Llamada al sprite inicializador
5 self.image, self.rect = load_image('fist.bmp', -1)
6 self.punching = 0
7
8 def update(self):
9 "move the fist based on the mouse position"
10 pos = pygame.mouse.get_pos()
11 self.rect.midtop = pos
12 if self.punching:
13 self.rect.move_ip(5, 10)
14
15 def punch(self, target):
16 "returns true if the fist collides with the target"
17 if not self.punching:
18 self.punching = 1
19 hitbox = self.rect.inflate(-5, -5)
20 return hitbox.colliderect(target.rect)
21
22 def unpunch(self):
23 "called to pull the fist back"
24
25 self.punching = 0
Aca creamos una clase para representar el puño del jugador:esto es derivado de la clase Sprite incluida en pygame.sprite la funcion init es llamada cuando nuevas instancias de esta clase son creadas.La primera cosa que hacemos es estar seguros de llamar la funcion init para nuestra clase base. Esto permite que la funcion init de sprite prepare nuestro objeto para usar como icono. este juego usa una de las los grupos de clases para dibujar iconos.EStas clases pueden dibujar iconos que tengan un atributo de "recta" e "imagen".Simplemente cambiando estos dos atributos, elrenderizador dibujara la actual imagen en la actual posicion.
Todos los iconos tienen un metodo de update(). esta funcion es tipicamente llamada uan vez por cuadro, es aca donde deberia ponerse codigo para mover y refrescar las variables del icono. El metodo update() para el puño mueve el puño en la locacion del puntero del mouse , tambien mueve la posicion del puño si este esta en estado de "pegar".
Las siguientes dos funciones punch() y unpunch() cambian el estado de pegado para el puño. El metodo punch() tambien devuelve un valor verdadero si el puño esta pegando contra el icono dado como objetivo.
1 class Chimp(pygame.sprite.Sprite):
2 """moves a monkey critter across the screen. it can spin the
3 monkey when it is punched."""
4 def __init__(self):
5 pygame.sprite.Sprite.__init__(self) #call Sprite intializer
6 self.image, self.rect = load_image('chimp.bmp', -1)
7 screen = pygame.display.get_surface()
8 self.area = screen.get_rect()
9 self.rect.topleft = 10, 10
10 self.move = 9
11 self.dizzy = 0
12
13 def update(self):
14 "walk or spin, depending on the monkeys state"
15 if self.dizzy:
16 self._spin()
17 else:
18 self._walk()
19
20 def _walk(self):
21 "move the monkey across the screen, and turn at the ends"
22 newpos = self.rect.move((self.move, 0))
23 if not self.area.contains(newpos):
24 if self.rect.left < self.area.left or \
self.rect.right > self.area.right:
25 self.move = -self.move
26 newpos = self.rect.move((self.move, 0))
27 self.image = pygame.transform.flip(self.image, 1, 0)
28 self.rect = newpos
29
30 def _spin(self):
31 "spin the monkey image"
32 center = self.rect.center
33 self.dizzy += 12
34 if self.dizzy >= 360:
35 self.dizzy = 0
36 self.image = self.original
37 else:
38 rotate = pygame.transform.rotate
39 self.image = rotate(self.original, self.dizzy)
40 self.rect = self.image.get_rect(center=center)
41
42 def punched(self):
43 "this will cause the monkey to start spinning"
44 if not self.dizzy:
45 self.dizzy = 1
46 self.original = self.image
La clase chimp esta haciendo mas trabajo que la del puño, pero nada mas complejo.Esta clase movera el chimp hacia artas y adelante atravez de la pantalla, cuando el mono sea golpeado, el dara vueltas alrededor con el efecto de exitacion. Esta clase tambien se deriva de la clase sprite basica, y es inicializada igual que el puño.Mientras que se inicializa, la clase tambien configura el atributo "area" para que sea el tamaño de la pantalla de display.
La funcion update para chimp simplemente mira el acxtual estado "mareado", que es verdadero cuando el momo esta dando vueltas por un puño, esta llama el metodo _walk o _spin.Estas funciones estan prefijadas con un guion bajo.Este es un idioma standart python que suguiere que estos metodos deben ser solo usados por la clase chimp podemos ir hasta ponerles doble guion bajo, que le dira a python que los haga metodos privados, pero nosotros no nescesitamos esta proteccion.
El metodo Walk crea una nueva posicion para el mono moviendo la actual recta por un offset dado, si esta nueva posicion cruza afuera del area del display de la pantalla, se reversa el offset de movimiento,tambien hace un esppejo de la imagen usando la funcion pygame.transform.flip. este es un crudo efecto que hace que el mono se vea como se estuviera cambiando la direccion de su movimiento.
El metodo "spin" es llamado cuando el mono esta actualmente "mareado":el atributo "mareado" es usado para guardar la actual cantidad de rotacion. cuando el mono ha rotado 360 grados este vuelve a poner la imagen del mono en su version original sin rotar.Antes de llamar la funcion transform.rotate, se vera que el codigo hace una referencia local a la funcion simplemente llamada "rotate", no hay nescesidad de hacer eso para este ejemplo, es solo hecho aqui para la longuitud de la linea siguiente un poco mas corta. Note que cuando se llama la funcion de rotar, nosotros siempre rotamos desde desde la imagen original del mono:cuando se rota hay una pequeña perdida de calidad. Cuando se rota repetidamente la misma imagen la calidad sera peor cada vez. tambien cuando se rota una imagen, el tamaño de la imagen cambiara. esto es debido a que la sesquinas de la imagen tambien se rotaran haciendo que la imagen sea mas grande, Hacemos la seguridad de que el centro de la nueva imagen este en el centro de la vieja imagen para que rote sin moverse.
El ultimo metodo es punched() que le dice al iocono que entre en su estado mareado. esto causara que la imagen comienze a girar. tambien hara una copia de la imagen actual llamda "original".
Inicializar Todo
Antes de que podamos hacer mucho con pygame, se nescecita estar seguro de que sus modulos esten inicializados. en este caso tambien abriremos una ventana de graficos simple. Ahora estamos en la funcion main() del programa, lo cual corre todo.
1 pygame.init()
2 screen = pygame.display.set_mode((468, 60))
3 pygame.display.set_caption('Monkey Fever')
4 pygame.mouse.set_visible(0)
La primera linea, inicializa lo que toma pygame para el trabajo a realizar. Observa atraves de los modulos pygame importados y los prepara para inicializar cada uno de ellos. Es posible revisar si algun modulo es fallido al inicializarse, Este tipo de control en general no es muy necesario, pero está disponible si se desea utilizarlo.
Lo siguiente ajusta el modo grafico de pantalla. Nota que el modulo pygame.display es usado para controlar todos los ajustes de pantalla. En este caso preguntaremos por una simple ventana. Hay otro tutorial que nos explica bien el manejo en las configuraciones en modo grafico, pero si queremos, pygame puede manejarnos muy bien esa parte, ya que el puede escojer nuestra mejor configuracion en el sistema que usemos, tal como la profundidad de color, desde que nosotros no le demos configuraciones especificas.
Las ultimas dos lineas nos dice que se ajusta el titulo de la ventana y apaga el mouse sobre nuestra ventana. Ahora tenemos una pequeña ventana negra lista para nuestro que hacer. Normalmente el cursor está siempre como visible, pero no es necesrio esconderlo siempre.
Crear el Background
Nuestro programa tendrá un mensaje de texto en el background. Necesitariamos entonces crear un sencillo surface que represenatria nuestro background y despues utilizarlo repetidamente. El primer paso es crear el surface.
1 background = pygame.Surface(screen.get_size())
2 background = background.convert()
3 background.fill((250, 250, 250))
Esto crearia un surface, el cual seria del mismo tamaño que nuestra pantalla de la ventana. Nota que la llamada extra a convert() está despues de haber creado nuestro suface. Al convertir sin argumentos asegura nuestro background con el mismo formato de la pantalla de la ventana, y del cual nos da unos rapidos resultados.
Llenamos el background entero con un solido color blanco, se llena con una tripleta de RGB como argumento de color.
Poner Texto Centrado en el Background
Ahora tenemos un surface en el background, que nos permitirá poner texto, entonces ponemos el modulo pygame.font e importar sus propiedades, pero si no queremos texto en nuestro juego, podremos saltarnos esta seccion.
1 if pygame.font:
2 font = pygame.font.Font(None, 36)
3 text = font.render("Pummel The Chimp, And Win $$$", 1, (10, 10,10))
4 textpos = text.get_rect(centerx=background.get_width()/2)
5 background.blit(text, textpos)
Como veras, son varios pasos para tenerlo hecho. Primero tendremos que crear el objeto de las fuentes y presentarlo dentro de un nuevo surface. Luego encontramos el centro de este nuevo surface y le damos el blit (pegar) pegarlo dentro del background.
La fuente es creada con el constructor del modulo fuente Font(). Usualmente se pasa el nombre de un tipo verdadero de fichero fuente a esta funcion, pero se puede pasar el argumento None. El constructor de la fuente tambien necesita saber el tamaño de la fuente que queramos crear.
Ponemos la fuente dentro de un nuevo surface. Al ponerla en la funcion crea un nuevo surface que es del tamaño apropiado para nuestro texto. En este caso estamos tambien diciendo que se cree un texto antialiased (Fara que lusca bien) y usar un color gris oscuro.
En lo siguiente, necesitamos encontrar la posicion centrada de el texto en nuestra pantalla. Creamos un objeto "Rect" desde las dimensiones del texto, que nos permite facilmente asignarle un centro de pantalla.
Finalmente lo pegamos (o con blit que es como compiar y pegar) el texto en la imagen del background.
Aparece el background mientras el ajuste final
Aun teniendo la ventana negra en la pantalla. Mostramos nuestro background mientras esperamos por otros recursos para cargar.
1 screen.blit(background, (0, 0))
2 pygame.display.flip()
Pegaremos nuestro background entero dentro del display de la ventana.
En pygame los cambios del display del suface, no son inmediatamente visibles. Normalmente un display puede actualizarce en areas que no podran ser visualizadas por usuario. Con el bufereado doble de pantalla el display debe ser volteado o movido, para que los cambios sean visibles en este caso la funcion flip()trabaja bien debido a que solo maneja el area entera de la ventana y maneja los surfaces sencillamentebufereadas y doblementebufereadas.
Preparar Objetos del Juego
Aqui creamos todos los objetos que iremos a necesitar en el juego.
1 whiff_sound = load_sound('whiff.wav')
2 punch_sound = load_sound('punch.wav')
3 chimp = Chimp()
4 fist = Fist()
5 allsprites = pygame.sprite.RenderPlain((fist, chimp))
6 clock = pygame.time.Clock()
Primero, cargamos dos efectos de sonido con la funcion load_sound que habiamos definido. Luego creamos una instancia de cada uno de las clases sprite, y por ultimo se crea un grupo sprite que contendrá todos nuestros sprites.
Actualmente utilizamos un grupo especial de sprites llamado RenderPlain. Este grupo puede dibujar todos los sprites contenidos en la pantalla. Este es llamado RenderPlain ya que es la forma mas avanzada de grupos Render. Pero nuestro juego solo necesita de un dibujado muy sencillo. Creamos el grupo llamado "allsprites" pasandole una lista con todos los sprites los cuales perteneceran al grupo. Podremos agregar o remover sprites desde este grupo, pero en este juego no será necesario.
El objeto clock lo creamos para ayudar a controlar nuestra tasa de frames en el juego, el cual usaremos en nuestro ciclo principal para asegurarnos que no corra tan rapido nuestro juego.
Ciclo Principal
No hay mucho, solo un ciclo al infinito.
1 while 1:
2 clock.tick(60)
Todos los juegos corren en una clase de ciclo. El normal orden de este es observar el estado del computador y la entrada del usuario, mover y actualizar el estado de los objetos, para luego dibujarlos en pantalla. Llamaremos a nuestro objeto clock, lo cual hará que nuestro juego no corra mas de 60 frames por segundos.
Manejo de los eventos de Entrada
1 for event in pygame.event.get():
2 if event.type == QUIT:
3 return
4 elif event.type == KEYDOWN and event.key == K_ESCAPE:
5 return
6 elif event.type == MOUSEBUTTONDOWN:
7 if fist.punch(chimp):
8 punch_sound.play() #punch
9 chimp.punched()
10 else:
11 whiff_sound.play() #miss
12 elif event.type == MOUSEBUTTONUP:
13 fist.unpunch()
Primero obtendremos todos los ecventos disponibles desde pygame y ciclo atraves de cada uno de ellos, Las primeras 2 pruebas ven si el usuario sale del juego, o ha presionado la tecla escape. En estos casos solo retornamos de la funcion main() y el programa limpia todo lo demas.
Despues se ve si el boton del mause es presionado, si el boton es presionado, le diremos al objeto si este ha colisionado con el mico. Entonces sonará el efecto apropiado, si el mico fue golpeado le diremos que empiece a jirar (Llamando a su metodo pounched())
Dibujar la escena completa
Ahora que todos los objetos estan en el lugar correcto, es tiempo de dibujarlos.
1 screen.blit(background, (0, 0))
2 allsprites.draw(screen)
3 pygame.display.flip()
La primera llamada con blit dibujará el background completo dentro de la pantalla completa. Se llama al metodo draw() del contenedor sprite. Depues este contenedor sprite seria una instancia del grupo "DrawPlain" y se conocerá para dibujar nuestros sprites. Por ultimo el flip() contiene el doble bufer de la pantalla en nuestro software pygame. Esto hace que todo lo que dibujemos sea visible a la vez.
Actualizar los Sprites
1 allsprites.update()
Los grupos Sprite tienen un metodo update(), el vual simplemente llama al metodo update para todos los Sprite contenidos. Cada objeto se moverá alrededor, dependiendo en que estado se encuentres. Aqui es donde el miquito moverá dando pasos de un lado a otro, o gira un poco dependiendo de si es golpeado con el puño.
Game Over
El usuario ha salido, es tiempo de limpiar todo.
La limpieza despues de haber corrido un juego con pygame es bastante simple. Todas las variables son destruidas y no tenemos realmente que hacer nada.
