Está en la página 1de 26

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

Sprites
Mario se convierte en nuestro Sprite
Los programas que hemos realizado hasta ahora con PyGame son (relativamente) bastante elaborados y, a medida que crezca la complejidad, pueden volverse ms y ms enrevesados. Afortunadamente, PyGame se puede encargar por nosotros de muchas ms cosas! En efecto, hay determinados detalles que hemos implementado por nuestra cuenta y que PyGame ya incorpora de una manera ms simple. Slo hay que aprenderla. No temas; el trabajo que has empleado hasta aqu te habr ayudado a crecer como programador/ programadora. En particular, y gracias al concepto de programacin dirigida a objetos, PyGame crea y gestiona los sprites de manera nativa. Una vez comprendido, todo se vuelve ms sencillo! En las siguientes pginas, vamos a coger una imagen del famoso Mario de Nintendo y vamos a hacer que se desplace como en los juegos de plataformas (gracias a Kevin Harris por un pequeo programa de demostracin en el que nos estamos apoyando). De paso aprenderemos cmo modificar el movimiento para que parezca no lineal, simulando la gravedad.

Vamos all!

PGINA 1 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

mario01.py
En este primer paso slo vamos a mostrar la imagen de Mario como resultado, pero internamente habremos creado un sprite que luego nos servir para continuar y ampliarlo en los siguientes casos. ste es el cdigo:
# -*- coding: utf-8 -*#----------------------------------------------------------------------# mario01.py # Implemetacin de sprites #----------------------------------------------------------------------import sys import pygame from pygame.locals import * # Clase MiSprite class MiSprite( pygame.sprite.Sprite ): # Inicializador de la clase def __init__( self, dibujo ): # Importante: Primero hay que inicializar la clase Sprite original pygame.sprite.Sprite.__init__( self ) # Almacenar en el sprite la imagen deseada self.image = pygame.image.load(dibujo) self.image = self.image.convert() # Denir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Inicializar PyGame y crear la Surface del juego pygame.init() visor = pygame.display.set_mode((640, 480)) # Inicializar el sprite sprite = MiSprite("mario.bmp") grupo = pygame.sprite.RenderUpdates( sprite ) # Crear un reloj para controlar la animacin reloj = pygame.time.Clock() # El bucle de la animacin while 1: #Fijar la animacin a 60 fps reloj.tick(60) # Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit()

PGINA 2 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

sys.exit() # Actualizar el sprite grupo.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) # Mostrar la animacin pygame.display.update()

Tras la importacin de las libreras habituales, lo primero que hacemos en el cdigo anterior es definir nuestra nueva clase de sprite que deriva de la clase de sprite de PyGame.
class MiSprite( pygame.sprite.Sprite ):

pygame.sprite.Sprite es la clase de sprite que implementa PyGame de forma nativa. Al ponerla entre parntesis le decimos a Pygame que cree la nuestra a partir de aquella. Recuerda que toda clase debera tener definida la funcin especial __init__() en la que pueden ponerse todas aquellas tareas que queremos que se realicen al crearse los sprites. Ojo, algo muy importante! Cuando definamos una clase basada en otra, es muy conveniente que llamemos a su vez a la funcin que inicializa la clase original. Eso se consigue escribindolo en primer lugar:
def __init__( self, dibujo ): pygame.sprite.Sprite.__init__( self )

Un secreto con respecto al significado de self. Cuando creamos un objeto del tipo de la clase que estamos definiendo, self representa al objeto mismo. Es un convenio muy til pero que al programador primerizo le causa algn que otro dolor de cabeza. Python est construido de forma que en toda funcin que se defina dentro de una clase, el primer argumento debe ser siempre self. Sin embargo, cuando se invoca a estas funciones, nunca se pone el susodicho self. Python se encarga por s mismo de pasrselo a la funcin y no tienes que ocuparte t de ello. Acurdate siempre de esto; en la definicin s, en el uso no. A su vez, si quieres definir variables que pertenezcan al objeto para poder utilizarlas posteriormente, siempre debes definirlas con el como propiedades del objeto self, es decir, debes definirlas con el self. por delante. En nuestro ejemplo, definimos la variable self.image para almacenar en ella el dibujo que tendr nuestro sprite:
self.image = pygame.image.load(dibujo)

PGINA 3 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

De hecho, self.image es una propiedad que PyGame nos obliga a definir en nuestros sprites, pues es la que usar automticamente para dibujarlos en pantalla. Fjate cmo lo hemos hecho; en la definicin de la funcin __init__() hemos puesto un segundo argumento, adems de self, dibujo. All le pasaremos la imagen cuando creemos el objeto que representa nuestro sprite de Mario. En la definicin de self.image usamos, por lo tanto, la funcin de PyGame que ya conocemos para cargarla, pygame.image.load(). A su vez, tambin como sabemos, conviene convertir la imagen al formato adecuado para Pygame, as que la siguiente lnea es
self.image = self.image.convert()

Te suena verdad? El resultado de convertir la imagen lo volvemos a almacenar en la variable self.image. Ya est preparada para ser utilizada! Qu mas cosas hemos puesto en la funcin __init__() ? Hay otra propiedad de los sprites que PyGame nos obliga a definir tambin y es self.rect. Se trata, ni ms ni menos, de una variable de tipo rect que almacena la posicin y el tamao tiene nuestro sprite.
self.rect = self.image.get_rect() self.rect.topleft = (0, 150)

La manera ms sencilla de obtener el rect es decirle a Python que lo calcule por s misma llamando a la funcin miembro de una imagen get_rect(). Esta funcin nos devuelve el tamao de la imagen a la que pertenece (en forma de rect). En la siguiente lnea lo nico que hacemos es indicar en qu posicin va a estar el sprite modificando las propiedades top y left del rect (consulta la documentacin de PyGame). Inicialmente, estar a la izquierda de la ventana (0) y a 150 pixeles del borde superior. Ya tenemos definido nuestro sprite! Lo siguiente es inicializar PyGame y crear la Surface donde vamos a mostrar la animacin. Optamos por una ventana de 640x480 pixeles, con las opciones que PyGame toma por defecto.
pygame.init() visor = pygame.display.set_mode((640, 480))

Ha llegado el momento importante; vamos a crear a Mario. Crear un sprite es un proceso que, comnmente, requiere dos pasos. Primero hay que crear al propio sprite. Y segundo hay que agruparlo. Qu quiere decir esto? De cara a manejar mltiples sprites, nos interesa tenerlos clasificados (el protagonista, los malos, la comida...). PyGame nos dar posteriormente herramientas para trabajar con todos ellos a la vez o por separado, gestionar sus colisiones, etc. Esto hay que hacerlo siempre, incluso cuando, como ahora, tenemos un slo sprite. Veamos:
sprite = MiSprite("mario.bmp") grupo = pygame.sprite.RenderUpdates( sprite )

Hemos creado dos variables. La primera, sprite, contiene nuestro sprite de Mario. La segunda, grupo, contiene el grupo al que pertenece nuestro sprite. Cmo lo hemos hecho? Para empezar, hemos invocado el nombre de la clase del sprite pasndole como argumento el nombre del archivo que contiene la imagen que vamos a
PGINA 4 DE 26 DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

usar. Fjate en la definicin de la clase; all vers que la funcin __init__() la hemos puesto con dos parmetros, self (que recuerda que se ignora cuando se llama a la funcin) y otra ms que luego usamos en la definicin para indicar la imagen del sprite. Para aadir un sprite a un grupo usamos pygame.sprite.RenderUpdates(); esta funcin nos crea un grupo de sprites al que aade el que le pasemos como argumento. El grupo, como hemos dicho, lo almacenamos en una variable que hemos llamado (qu imaginacin) grupo. Dicho sea de paso, si tuviramos otro sprite y quisiramos aadirlo a este grupo, bastara que escribiramos grupo.add(nombre_de_otro_sprite). Fcil! Lo siguiente es algo que va a hacer mucho ms simple lo que hasta ahora hemos hecho ms complicado; controlar la velocidad de la animacin. En los tutoriales anteriores lo hemos hecho a mano, mirando el tiempo que pasaba y ajustando la diferencia hasta que se consegua el tiempo requerido. Afortunadamente, Python se puede encargar automticamente de eso por ti. Para ello necesitamos crear un objeto especial:
reloj = pygame.time.Clock()

pygame.time.Clock() nos devuelve un objeto que almacenamos en la variable reloj y que se encargar de controlar los fotogramas por segundo a los que se va mostrar la animacin. Lo podemos ver al comienzo del bucle habitual:
while 1: #Fijar la animacin a 60 fps reloj.tick(60)

Qu sencillo resulta ahora! Como quiero que la animacin se reproduzca a 60 fotogramas por segundo y no ms rpido, basta con que lo indique usando la funcin tick() del objeto reloj. Python, por s mismo, se encargar de esperar lo suficiente (si hace falta) para conseguirlo... Con la seguridad de que lo tenemos todo controlado (incluido el tpico cdigo de eventos que solemos usar para salir del programa), podemos pasar a la tarea de dibujar en pantalla el fotograma. Una vez conocido el sistema, es muy sencillo. Piensa que, en cada pasada del bucle, lo que deseas hacer es mirar todos los sprites que tengas, ver donde tienes que ponerlos, borrarlos de donde estaban antes y dibujarlos en sus nuevas posiciones. Si te fijas, es precisamente eso lo que hemos hecho en el cdigo:
# Actualizar el sprite grupo.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) # Mostrar la animacin pygame.display.update()

Aqu podemos ver la utilidad del concepto de grupo de PyGame. Para decirle al grupo que actualice las posiciones (esto es, que averige cules deben ser las nuevas posiciones) de todos sus sprites basta usar su funcin miembro update(). Y para decirle
PGINA 5 DE 26 DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

que los dibuje en esas posiciones usamos draw() pasndole como argumento la surface en la que ha de hacerse (nuestro visor). Finalmente, como siempre en PyGame, volcamos toda esa informacin en pantalla para que la muestre con pygame.display.update(). Te puedes preguntar y cmo sabe PyGame, cuando llamo a grupo.update() dnde estn las nuevas posiciones de los sprites? En realidad, grupo.update() lo que hace es llamar a las funciones update() de todos los sprites que pertencen al grupo. Como nosotros no hemos definido esa funcin en MiSprite, PyGame no hace nada y deja a Mario siempre en la misma posicin. Ejecuta el programa:

Una vez que comprendas el proceso, vers que es sencillo y potente. Prueba a eliminar los comentarios; el cdigo que resulta es bastante breve y fcil de entender

PGINA 6 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

mario02.py
La ventaja de utilizar la programacin dirigida a objetos que incorpora PyGame es que modificar el comportamiento de los sprites es muy sencillo. Vamos a darle movimiento a Mario. Nada ms simple:

# -*- coding: utf-8 -*#------------------------------------------------------------------# mario02.py # Movimiento sencillo #------------------------------------------------------------------import sys import pygame from pygame.locals import * # Clase MiSprite class MiSprite( pygame.sprite.Sprite ): # Inicializador de la clase def __init__( self, dibujo ): # Importante: Primero hay que inicializar la clase Sprite original pygame.sprite.Sprite.__init__( self ) # Almacenar en el sprite la imagen deseada self.image = pygame.image.load(dibujo) self.image = self.image.convert() # Denir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) def update(self): # Modicar la posicin del sprite self.rect.move_ip(1,0) # Inicializar PyGame y crear la Surface del juego pygame.init() visor = pygame.display.set_mode((640, 480)) # Inicializar el sprite sprite = MiSprite("mario.bmp") grupo = pygame.sprite.RenderUpdates( sprite ) # Crear un reloj para controlar la animacin reloj = pygame.time.Clock() # El bucle de la animacin while 1:

PGINA 7 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

#Fijar la animacin a 60 fps reloj.tick(60) # Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit() sys.exit() # Actualizar el sprite grupo.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) # Mostrar la animacin pygame.display.update()

Extremadamente sencillo! Recuerdas que, en cada fotograma, grupo.update() llama a la funcin update() de cada sprite para saber dnde debe dibujarlo? Bueno, pues simplemente debemos definir esa funcin dentro de nuestra clase para tener la tarea hecha. Esto es lo nico que hemos aadido al cdigo:
def update(self): # Modicar la posicin del sprite self.rect.move_ip(1,0)

De paso, usamos otra funcin incorporada, move_ip(), que tambin hace las cosas ms sencillas. En lugar de modificar a mano las coordenadas de self.rect, el rectngulo que define la posicin y el tamao del sprite, move_ip() toma de argumento dos valores que indican cunto hay que desplazar el rect. En nuestro ejemplo, este desplazamiento es de 1 pixel hacia la derecha y 0 pixeles hacia abajo (es decir, no se desplaza en direccin vertical). Terminado. Si ejecutas el programa vers a Mario desplazndose lentamente por la pantalla; exactamente a 60 pixeles por segundo (1 pixel en cada fotograma, 60 fotogramas por segundo).

PGINA 8 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

mario03.py
Implementar el rebote es igual de sencillo:

# -*- coding: utf-8 -*#------------------------------------------------------------------# mario03.py # Rebote #------------------------------------------------------------------import sys import pygame from pygame.locals import * # Clase MiSprite class MiSprite( pygame.sprite.Sprite ): # Inicializador de la clase def __init__( self, dibujo ): # Importante: Primero hay que inicializar la clase Sprite original pygame.sprite.Sprite.__init__( self ) # Almacenar en el sprite la imagen deseada self.image = pygame.image.load(dibujo) self.image = self.image.convert() # Denir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Denir las velocidad self.dx = 1 def update(self): # Modicar la posicin del sprite self.rect.move_ip(self.dx,0) # Comprobar si hay que cambiar el movimiento if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx # Inicializar PyGame y crear la Surface del juego pygame.init() visor = pygame.display.set_mode((640, 480)) # Inicializar el sprite sprite = MiSprite("mario.bmp") grupo = pygame.sprite.RenderUpdates( sprite ) # Crear un reloj para controlar la animacin reloj = pygame.time.Clock() # El bucle de la animacin

PGINA 9 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

while 1: #Fijar la animacin a 60 fps reloj.tick(60) # Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit() sys.exit() # Actualizar el sprite grupo.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) # Mostrar la animacin pygame.display.update()

La forma de conseguir el rebote ya la conocemos; ver cuando se llega al borde de la ventana y cambiar el sentido del movimiento cambiando de signo la cantidad que sumas a la posicin. Lgicamente, para poder cambiar ese signo, necesitamos almacenar la cantidad que sumamos a la posicin en una variable. Como es un movimiento en el eje horizontal, llamemos a esa variable dx. Como es una variable que ha de pertenecer al sprite, vamos a poner su definicin en la funcin __init__() de la clase y, por lo tanto, deberemos aadirle al nombre el ya conocido self:
# Denir las velocidad self.dx = 1

Bien. Una vez hecho esto, implementar el rebote de Mario requiere modificar la definicin de la funcin update() del sprite:
def update(self): # Modicar la posicin del sprite self.rect.move_ip(self.dx,0) # Comprobar si hay que cambiar el movimiento if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx

En efecto, primero movemos el sprite la cantidad deseada (ahora es self.dx) y luego miramos si se ha llegado a uno de los extremos de la pantalla, en cuyo caso cambiamos el movimiento cambiando el signo de self.dx. Ves qu sencillo?

PGINA 10 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

mario04.py
Un nuevo paso. Esta vez, al igual que hacamos con Guy, vamos a invertir la imagen del sprite cuando ste rebote. ste es el cdigo:

# -*- coding: utf-8 -*#------------------------------------------------------------------# mario04.py # Rebote invirtiendo el sprite #------------------------------------------------------------------import sys import pygame from pygame.locals import * # Clase MiSprite class MiSprite( pygame.sprite.Sprite ): # Inicializador de la clase def __init__( self, dibujo ): # Importante: Primero hay que inicializar la clase Sprite original pygame.sprite.Sprite.__init__( self ) # Almacenar en el sprite la imagen deseada self.image = pygame.image.load(dibujo) self.image = self.image.convert() # Denir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Denir la velocidad self.dx = 1 def update(self): # Modicar la posicin del sprite self.rect.move_ip(self.dx,0) # Comprobar si hay que cambiar el movimiento if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.ip( self.image, True, False ) # Inicializar PyGame y crear la Surface del juego pygame.init() visor = pygame.display.set_mode((640, 480)) # Inicializar el sprite sprite = MiSprite("mario.bmp") grupo = pygame.sprite.RenderUpdates( sprite ) # Crear un reloj para controlar la animacin reloj = pygame.time.Clock()

PGINA 11 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

# El bucle de la animacin while 1: #Fijar la animacin a 60 fps reloj.tick(60) # Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit() sys.exit() # Actualizar el sprite grupo.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) # Mostrar la animacin pygame.display.update()

Puedes creer que sea algo tan simple? Pues s: en el mismo lugar del cdigo donde miramos si el sprite ha llegado al borde (y cambiamos la direccin del movimiento en caso afirmativo), lo nico que hacemos es transformar la imagen de Mario por su reflejo horizontal
if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.ip( self.image, True, False )

De nuevo, igual que hicimos con Guy, la funcin pygame.transform.flip() nos permite hacer esa inversin de la imagen. El resultado, lo volvemos a almacenar en self.image que contiene el aspecto de nuestro sprite y, a partir de entonces, nuestro Mario estar mirando en su movimiento hacia el otro lado. Perfecto! Espero que, en este punto, comprendas las ventajas de la programacin dirigida a objetos y del concepto de Sprite de PyGame; todo su comportamiento lo incluimos en la definicin de la clase y cuando usemos un objeto de ese tipo, automticamente se comportar como tal. Ello hace que los programas sean mucho ms fcilmente modificables y ampliables, como estamos viendo.

PGINA 12 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

PGINA 13 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

mario05.py
Implementar el movimiento en la direccin vertical, incluido el rebote correspondiente, te debera resultar ahora bastante fcil:

# -*- coding: utf-8 -*#------------------------------------------------------------------# mario05.py # Implementando el movimiento vertical #------------------------------------------------------------------import sys import pygame from pygame.locals import * # Clase MiSprite class MiSprite( pygame.sprite.Sprite ): # Inicializador de la clase def __init__( self, dibujo ): # Importante: Primero hay que inicializar la clase Sprite original pygame.sprite.Sprite.__init__( self ) # Almacenar en el sprite la imagen deseada self.image = pygame.image.load(dibujo) self.image = self.image.convert() # Denir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Denir las velocidades self.dx = 1 self.dy = 1 def update(self): # Modicar la posicin del sprite self.rect.move_ip(self.dx,self.dy) # Comprobar si hay que cambiar el movimiento if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.ip( self.image, True, False ) if self.rect.top < 0 or self.rect.bottom > 480: self.dy = -self.dy # Inicializar PyGame y crear la Surface del juego pygame.init() visor = pygame.display.set_mode((640, 480)) # Inicializar el sprite sprite = MiSprite("mario.bmp") grupo = pygame.sprite.RenderUpdates( sprite )

PGINA 14 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

# Crear un reloj para controlar la animacin reloj = pygame.time.Clock() # El bucle de la animacin while 1: #Fijar la animacin a 60 fps reloj.tick(60) # Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit() sys.exit() # Actualizar el sprite grupo.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) # Mostrar la animacin pygame.display.update()

Hace falta explicar algo? Slo hemos aadido en __init__() la nueva velocidad vertical self.dy y en update() la hemos aadido al movimiento y a la comprobacin del rebote.

PGINA 15 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

mario06.py
Hasta ahora slo hemos hecho movimientos rectilneos y uniformes. Qu tal simular algo ms real, como una cada con gravedad?

# -*- coding: utf-8 -*#------------------------------------------------------------------# mario06.py # Simular la gravedad #------------------------------------------------------------------import sys import pygame from pygame.locals import * # Clase MiSprite class MiSprite( pygame.sprite.Sprite ): # Inicializador de la clase def __init__( self, dibujo ): # Importante: Primero hay que inicializar la clase Sprite original pygame.sprite.Sprite.__init__( self ) # Almacenar en el sprite la imagen deseada self.image = pygame.image.load(dibujo) self.image = self.image.convert() # Denir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Denir las velocidades self.dx = 1 self.dy = 1 def update(self): # Modicar la posicin del sprite self.rect.move_ip(self.dx,self.dy) # Comprobar si hay que cambiar el movimiento if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.ip( self.image, True, False ) if self.rect.top < 0 or self.rect.bottom > 480: self.dy = -self.dy # Simular la gravedad sumando una cantidad a la velocidad vertical self.dy = self.dy + 0.5 # Inicializar PyGame y crear la Surface del juego pygame.init() visor = pygame.display.set_mode((640, 480)) # Inicializar el sprite

PGINA 16 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

sprite = MiSprite("mario.bmp") grupo = pygame.sprite.RenderUpdates( sprite ) # Crear un reloj para controlar la animacin reloj = pygame.time.Clock() # El bucle de la animacin while 1: #Fijar la animacin a 60 fps reloj.tick(60) # Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit() sys.exit() # Actualizar el sprite grupo.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) # Mostrar la animacin pygame.display.update()

Te podas imaginar que era tan sencillo? Lo nico que hemos hecho ha sido aadir en la funcin update() la siguiente lnea de cdigo:
self.dy = self.dy + 0.5

La explicacin es muy simple. La gravedad lo nico que hace es empujarnos hacia abajo de manera constante. Y la manera de hacerlo en la funcin update() es, por lo tanto, sumar una cantidad constante a la velocidad en el eje vertical. Fjate que tiene precisamente el efecto deseado; cuando el sprite va hacia arriba (self.dy es entonces negativa) al sumarle 0.5 lo que hace es frenarse y cuando va hacia abajo (self.dy positiva) acelerarse. Por supuesto, sin ms que variar ese 0.5 conseguimos una gravedad ms o menos intensa. Ejecuta el programa! Veras como el rebote es ahora ms divertido... No obstante, hay dos detalles que debemos cuidar. El primero es que en mucho juegos tipo plataforma, no queremos que el protagonista se frene y que rebote siempre a la misma altura, no que se vaya frenando. Lo segundo es que tenemos un pequeo bug; si esperamos lo suficiente, veremos como Mario... termina por atravesar los bordes de la ventana! En el prximo paso veremos cmo solucionar esto.

PGINA 17 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

mario07.py
El que el rebote sea siempre a la misma altura y el que Mario termine atravesando el suelo de la ventana pueden solucionarse con una sola lnea:

# -*- coding: utf-8 -*#------------------------------------------------------------------# mario07.py # Movimiento de plataforma #------------------------------------------------------------------import sys import pygame from pygame.locals import * # Clase MiSprite class MiSprite( pygame.sprite.Sprite ): # Inicializador de la clase def __init__( self, dibujo ): # Importante: Primero hay que inicializar la clase Sprite original pygame.sprite.Sprite.__init__( self ) # Almacenar en el sprite la imagen deseada self.image = pygame.image.load(dibujo) self.image = self.image.convert() # Denir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Denir las velocidades self.dx = 5.0 self.dy = 1.0 def update(self): # Modicar la posicin del sprite self.rect.move_ip(self.dx,self.dy) # Comprobar si hay que cambiar el movimiento if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.ip( self.image, True, False ) self.rect.move_ip(self.dx,self.dy) if self.rect.top < 0 or self.rect.bottom > 480: self.dy = -self.dy self.rect.move_ip(self.dx,self.dy) # Simular la gravedad sumando una cantidad a la velocidad vertical self.dy = self.dy + 0.5 # Inicializar PyGame y crear la Surface del juego pygame.init() visor = pygame.display.set_mode((640, 480))

PGINA 18 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

# Inicializar el sprite sprite = MiSprite("mario.bmp") grupo = pygame.sprite.RenderUpdates( sprite ) # Crear un reloj para controlar la animacin reloj = pygame.time.Clock() # El bucle de la animacin while 1: #Fijar la animacin a 60 fps reloj.tick(60) # Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit() sys.exit() # Actualizar el sprite grupo.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) # Mostrar la animacin pygame.display.update()

En efecto, basta darle al sprite un pequeo empujoncito extra en el momento en el que llega al borde. Eso nos deja el movimiento con el mismo valor que justo antes de producirse el rebote, con lo que llegar hasta la misma altura al subir (observa que la altura a la que llega Mario es como un quesito y ese 0.5 que aadimos a la velocidad como un ratn; va mordindole y quitando un trozo tras otro hasta que lo termina, de ah que el rebote vaya, de partida, disminuyendo poco a poco) y no se producirn efectos extraos en las paredes.
if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.ip( self.image, True, False ) self.rect.move_ip(self.dx,self.dy) if self.rect.top < 0 or self.rect.bottom > 480: self.dy = -self.dy self.rect.move_ip(self.dx,self.dy)

Por cierto, si necesitramos solucionar el tema de atravesar el suelo por su cuenta, tampoco sera complicado. Te imaginas cmo? Fcil; antes de cambiar la posicin del sprite con move_ip() deberas comprobar si ya est en el suelo, en cuyo caso deberas poner el valor de self.dy a cero para que dejara de bajar. (Pregunta: Cmo saber si los pies de Mario estn en el borde de la ventana? Respuesta: con el atributo bottom que tiene todo objeto de tipo rect)
PGINA 19 DE 26 DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

mario08.py
Ya que estamos trabajando con sprites. Por qu limitarnos a un slo Mario? Traigamos a su hermano gemelo a la fiesta...

# -*- coding: utf-8 -*#------------------------------------------------------------------# mario08.py # Varios sprites #------------------------------------------------------------------import sys import pygame from pygame.locals import * # Clase MiSprite class MiSprite( pygame.sprite.Sprite ): # Inicializador de la clase def __init__( self, dibujo, posX, posY ): # Importante: Primero hay que inicializar la clase Sprite original pygame.sprite.Sprite.__init__( self ) # Almacenar en el sprite la imagen deseada self.image = pygame.image.load(dibujo) self.image = self.image.convert() # Denir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (posX, posY) # Denir las velocidades self.dx = 5.0 self.dy = 1.0 def update(self): # Modicar la posicin del sprite self.rect.move_ip(self.dx,self.dy) # Comprobar si hay que cambiar el movimiento if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.ip( self.image, True, False ) self.rect.move_ip(self.dx,self.dy) if self.rect.top < 0 or self.rect.bottom > 480: self.dy = -self.dy self.rect.move_ip(self.dx,self.dy) # Simular la gravedad sumando una cantidad a la velocidad vertical self.dy = self.dy + 0.5 # Inicializar PyGame y crear la Surface del juego pygame.init() visor = pygame.display.set_mode((640, 480))

PGINA 20 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

# Inicializar los sprites sprite = MiSprite("mario.bmp", 0, 150) grupo = pygame.sprite.RenderUpdates( sprite ) sprite2 = MiSprite("mario.bmp", 210, 50) grupo.add( sprite2 ) # Crear un reloj para controlar la animacin reloj = pygame.time.Clock() # El bucle de la animacin while 1: #Fijar la animacin a 60 fps reloj.tick(60) # Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit() sys.exit() # Actualizar el sprite grupo.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) # Mostrar la animacin pygame.display.update()

Para empezar, hemos modificado ligeramente la definicin de nuestro tipo de sprite ya que si no todos los sprites que creemos de este tipo comenzarn en el mismo sitio (y por tanto slo veramos uno). Esto es fcil de solucionar; ponemos dos parmetros ms en la funcin miembro __init__() que inicializa la clase.
def __init__( self, dibujo, posX, posY ):

Los parmetros posX y posY se encargarn de situar en su posicin inicial al sprite. Ello quiere decir que hay que modificar una lnea ms de esta funcin:
self.rect.topleft = (posX, posY)

para que as, en efecto, el sprite se posicione all al principio. Lo ltimo que queda es, simplemente, crear los sprites:
sprite = MiSprite("mario.bmp", 0, 150) grupo = pygame.sprite.RenderUpdates( sprite ) sprite2 = MiSprite("mario.bmp", 210, 50) grupo.add( sprite2 )
PGINA 21 DE 26 DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

Respecto del primer Mario, poco que decir. Lo nico que hemos modificado es que, ahora, en la creacin del sprite hay que indicar la posicin en la que lo queremos. Con el segundo sprite hacemos lo mismo (con otra posicin distinta) y, como ya est creado el grupo de sprites, lo aadimos con la funcin miembro add().

Dos cosas podrs notar cuando ejecutes el programa. La primera es que, como la imagen de Mario no tiene transparencias, cuando se superponen los dos gemelos se ve el cuadrado blanco que envuelve a la imagen. La segunda es que cada sprite ignora al otro. Cmo podramos gestionar la colisin entre ambos de manera que, por ejemplo, rebotaran? El problema de la transparencia, en este caso, es muy fcil de solucionar ya que slo hay que decirle al sprite qu color ha de tomar como transparente (para no dibujarlo). Eso se consigue aadiendo una sola lnea el la funcin __init__() del sprite:
self.image.set_colorkey((255,255,255))

La funcin set_colorkey() es una funcin miembro de cualquier surface (y una imagen es a su vez una surface) que toma un argumento que no es si no el color que se usar como transparente. Si la aades y lanzas el programa, vers que ya no aparece ese cuadrado blanco tan molesto alrededor de Mario. Respecto de la colisin de los sprites... tendremos que pasar a la ltima versin del programa.

PGINA 22 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

mario09.py
Hay varias formas de abordar el tema. La que viene a continuacin es slo una de las ms sencillas:

# -*- coding: utf-8 -*#------------------------------------------------------------------# mario09.py # Sprites con colisin #------------------------------------------------------------------import sys import pygame from pygame.locals import * # Clase MiSprite class MiSprite( pygame.sprite.Sprite ): # Inicializador de la clase def __init__( self, dibujo, posX, posY ): # Importante: Primero hay que inicializar la clase Sprite original pygame.sprite.Sprite.__init__( self ) # Almacenar en el sprite la imagen deseada self.image = pygame.image.load(dibujo) self.image = self.image.convert() self.image.set_colorkey((255,255,255)) # Denir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (posX, posY) # Denir las velocidades self.dx = 5.0 self.dy = 1.0 def update(self): # Modicar la posicin del sprite self.rect.move_ip(self.dx,self.dy) # Comprobar si hay que cambiar el movimiento if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.ip( self.image, True, False ) self.rect.move_ip(self.dx,self.dy) if self.rect.top < 0 or self.rect.bottom > 480: self.dy = -self.dy self.rect.move_ip(self.dx,self.dy) # Simular la gravedad sumando una cantidad a la velocidad vertical self.dy = self.dy + 0.5 # Inicializar PyGame y crear la Surface del juego pygame.init()

PGINA 23 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

visor = pygame.display.set_mode((640, 480)) # Inicializar los sprites sprite = MiSprite("mario.bmp", 0, 150) grupo = pygame.sprite.RenderUpdates( sprite ) sprite2 = MiSprite("mario.bmp", 210, 50) grupo2 = pygame.sprite.RenderUpdates( sprite2 ) # Crear un reloj para controlar la animacin reloj = pygame.time.Clock() # El bucle de la animacin while 1: #Fijar la animacin a 60 fps reloj.tick(60) # Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit() sys.exit() # Mira si hay alguna colisin: if pygame.sprite.spritecollideany(sprite, grupo2): sprite.dx = -sprite.dx sprite.dy = -sprite.dy sprite2.dx = -sprite2.dx sprite2.dy = -sprite2.dy # Actualizar el sprite grupo.update() grupo2.update() # Dibujar la escena visor.ll((255,255,255)) grupo.draw( visor ) grupo2.draw( visor ) # Mostrar la animacin pygame.display.update()

Lo primero es una advertencia: como cualquier programador sabe, los documentos de referencia (en los que se incluyen las libreras, objetos e instrucciones disponibles en el lenguaje) son un compaero inseparable en la aventura de programar. No tienes cerca la referencia de Pygame? Cgela ahora mismo. Piensa que te nombramos unas pocas funciones u objetos pero hay muchas ms. Y muchas ms opciones. Lo segundo es hacerte notar que hemos aadido la lnea a la que nos referamos al final del captulo anterior, el cdigo que consigue que el color blanco (255,255,255) se tome como transparente y no se dibuje.

PGINA 24 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

Bien, vamos al tema de la deteccin de colisiones. PyGame incorpora muchas funciones que gestionan los posibles choques entre sprites y/o grupos. Mira el documento de Referencia de PyGame! Como notars enseguida, una de las maneras ms cmodas es usar la funcin pygame.sprite.spritecollideany(). Fjate qu pone en la referencia:

spritecollideany
Consulta simple para ver si un sprite colisiona con algn otro en el grupo. pygame.sprite.spritecollideany(sprite, group): return bool Consulta si el sprite dado colisiona con algn sprite en el grupo. La interseccin se determina comparando el atributo Sprite.rect de cada sprite. Esta prueba de colisin puede ser mas rpida que pygame.sprite.spritecollideany() dado que tiene menos trabajo para hacer. Retornar al encontrar la primer colisin.

Lo que nos interesa son dos cosas; la funcin toma dos argumentos (un Sprite y un Grupo) y devuelve un Booleano (True o False). En definitiva, lo que hace es mirar si el sprite que le pasamos a la funcin colisiona con algn sprite que pertenezca al grupo que tambin le pasamos, devolvindonos True o False en consecuencia. Esto nos viene muy bien pues solo tenemos dos sprites. En programas ms complejos, con muchos sprites, necesitaramos otras funciones distintas que nos dijeran a su vez cules son los sprites que han colisionado, etc. He aqu otro punto importante; como suele mirarse la colisin de sprites y grupos, conviene tener separados los sprites que queremos mirar si colisionan en diferentes grupos. Tpicamente, por ejemplo, tendramos un grupo para las naves de los enemigos y otro grupo para los rayos lser. O, en el juego del Pong, un grupo para las raquetas y otro para la pelota. sa es la razn por la que hemos puesto lo siguiente:
sprite = MiSprite("mario.bmp", 0, 150) grupo = pygame.sprite.RenderUpdates( sprite ) sprite2 = MiSprite("mario.bmp", 210, 50) grupo2 = pygame.sprite.RenderUpdates( sprite2 )

En el captulo anterior aadamos el sprite sprite2 al grupo grupo en el que estaba el sprite sprite. Como ahora queremos mirar cundo chocan, definimos un nuevo grupo, grupo2. Ya los tenemos separados! Lo siguiente es implementar la colisin. Como ya hemos visto ms arriba la sintaxis de la funcin pygame.sprite.spritecollideany(), no debera ser difcil de entender:

PGINA 25 DE 26

DURACIN: PERODOS DE DOS CLASES

ASUNTO: PROGRAMACIN CON PYTHON Y PYGAME

CURSO: 1 BACHILLERATO

# Mira si hay alguna colisin: if pygame.sprite.spritecollideany(sprite, grupo2): sprite.dx = -sprite.dx sprite.dy = -sprite.dy sprite2.dx = -sprite2.dx sprite2.dy = -sprite2.dy

En el cdigo anterior miramos si el sprite sprite ha chocado con algn miembro del grupo grupo2 (vamos, con su hermano gemelo que es el nico sprite de ese grupo). En caso afirmativo, pygame.sprite.spritecollideany() devuelve True y, por tanto, se ejecuta el interior del bloque if. Qu hacemos all? Anlogamente al rebote en los bordes de la ventana, cambiamos las direcciones de movimiento de los dos sprites. Rebote conseguido! Slo queda aadir la parte de cdigo que tiene en cuenta que ahora no hay slo un grupo para actualizar y dibujar en pantalla. As que, en los sitios correspondientes, hay que colocar primero
grupo2.update()

y despus
grupo2.draw( visor )

Ejecuta ahora el programa... Genial!

Por cierto; cuando rebotan los dos marios, el uno contra el otro, no invierten su dibujo. Y si dejas que pase el tiempo suficiente, pueden llegar a engancharse... Sabras solucionarlo? Ya puestos, sabras hacer que comiencen desde una posicin aleatoria? Y que salgan, no dos, si no muchos marios, por ejemplo cada vez que se haga click con el ratn? Ante nosotros se extiende un campo de creatividad ilimitado...

PGINA 26 DE 26

DURACIN: PERODOS DE DOS CLASES