Os dejo otro ejemplo de un juego desarrollado en Python + Pygame. Se trata de un clon del famoso juego Lumines. Es una variante de Tetris, en donde se debe formar cuadrados de 2 x 2 del mismo color.
En este blog escribo acerca de las actividades y herramientas que uso para el desarrollo de mis clases. Soy profesor de ciclos de formación profesional de informática.
domingo, 25 de enero de 2009
lunes, 5 de enero de 2009
pacman - Game over
Finalizo aquí las entradas a través de las que he ido construyendo el típico videojuego de pacman usando Python y Pygame. El objetivo de todo esto era proponer el desarrollo de un juego para enseñar a los alumnos a programar de una forma entrenida y eficaz.
Por favor, déjanos saber a través de comentarios en este blog tu opinión acerca de esta propuesta y si lo usas en clase, cuéntanos que tal te ha ido.
pacman 0.008 - Sonido
En esta ocasión introducimos los sonidos en nuestro videojuego. Además, aprovechamos para introducir algunas otras mejoras:
- Añadimos teletransportadores.
- Contador de disparos.
- Mensajes de puntos y disparos.
- Control de fin de juego. El juego finalizará cuando muera pacman o todos los fantasmas.
- Los fantasmas se reproducirán cada 20 segundos.
El uso de sonido es muy sencillo con Pygame. Para cargar un sonido basta con crear un objeto Sound de la siguiente forma:
sonido = pygame.mixer.Sound ( fichero_sonido )Y para reproducirlo empleamos la función "play" del objeto sonido.
sonido.play()
Si queremos reproducir el sonido de forma indefinida, por ejemplo, para la música de fondo, basta con pasar "-1" como parámetro a la función "play".
sonido.play(-1)
Si queremos detener el sonido tan solo tenemos que llamar a la función "stop"
sonido.stop()
En nuestro juego hemos creado un repositorio de sonido para evitar recargar continuamente el mismo fichero.
sonidos = {}
def cargar_sonido ( fichero_sonido ):
global sonidos
sonido = sonidos.get ( fichero_sonido, None )
if sonido is None:
sonido = pygame.mixer.Sound ( os.path.join ("sonidos",
fichero_sonido))
sonidos[fichero_sonido] = sonido
return sonido
El resto de mejoras las puedes ver en el código de esta nueva versión de Pacman
domingo, 4 de enero de 2009
pacman 0.007 - animando al pacman
En esta ocasión vamos a mejorar nuestro juego haciendo una especie de gif animado con nuestro pacman para que abra y cierre la boca. Además, aprovechamos la ocasión para refactorizar nuestro código creando la clase "SpriteMovil" de la que heredera todos los sprites que se mueven en pantalla como el pacman y los fantasmas. El resto de objetos estáticos heredaran de la clase "MiSprite".
El sprite Pacman contará con una lista de imágenes que irá cambiando según la dirección que tome el pacman y de forma continua para simular el efecto de abrir y cerrar la boca. Vamos a tener cuatro vectores de ocho imágenes, uno con pacman apuntando a la izquierda, otro a la derecha, otro arriba y el último, con pacman apuntando hacia abajo. Cargamos las imágenes en el constructor de la clase Pacman
class Pacman ( SpriteMovil ):
NUMERO_FOTOGRAMAS = 8
def __init__(self, fichero_imagen, pos_inicial):
SpriteMovil.__init__(self, fichero_imagen, pos_inicial, [0,0])
self.vidas = 3
self.puntos = 0
self.__imagenArriba = {}
self.__imagenAbajo = {}
self.__imagenDerecha = {}
self.__imagenIzquierda = {}
for i in range(0, self.NUMERO_FOTOGRAMAS, 1):
self.__imagenIzquierda[i] = cargar_imagen(
"pacman-izquierda" + str(i+1) + ".gif")
self.__imagenDerecha[i] = cargar_imagen(
"pacman-derecha" + str(i+1) + ".gif")
self.__imagenArriba[i] = cargar_imagen(
"pacman-arriba" + str(i+1) + ".gif")
self.__imagenAbajo[i] = cargar_imagen(
"pacman-abajo" + str(i+1) + ".gif")
self.__fotogramasActuales = self.__imagenDerecha
self.__fotogramaActual = 1
self.__tiempoCambioFotograma = pygame.time.get_ticks()
La propiedad "_fotogramasActuales" cambiará según la dirección que tome nuestro Pacman y la propiedad "_fotogramaActual" cambiará de forma iterativa a lo largo del tiempo cada 100 milisegundos, tomando como valores de 1 a 8, lo que represesenta cada uno de los ocho fotogramas. Estas propiedades se cambian en la función "update" de la clase "Pacman".
def update (self):
...
if self.velocidad[0] > 0:
self.__fotogramasActuales = self.__imagenDerecha
elif self.velocidad[0] < 0:
self.__fotogramasActuales = self.__imagenIzquierda
elif self.velocidad[1] > 0:
self.__fotogramasActuales = self.__imagenAbajo
elif self.velocidad[1] < 0:
self.__fotogramasActuales = self.__imagenArriba
if pygame.time.get_ticks()-self._tiempoCambioFotograma > 100:
self.__fotogramaActual = (self.__fotogramaActual + 1) %
self.NUMERO_FOTOGRAMAS
self.__tiempoCambioFotograma = pygame.time.get_ticks()
En nuestra próxima versión del juego introduciremos los sonidos y algunas mejoras más.
Pacman 0.006 Con marcador
Añadimos un marcador en nuestro juego en donde se muestre las vidas que les queda a nuestro Pacman y los puntos conseguidos.
Para esto, creamos una clase llamada "Marcador" que también es un sprite. Nuestro marcador es bastante sencillo, empleando la función "sprint.font.render" para crear una imagen con el texto indicado.
class Marcador (pygame.sprite.Sprite):
def __init__(self, pacman):
pygame.sprite.Sprite.__init__(self)
self.pacman = pacman
self.font = pygame.font.SysFont("None", 20)
self.rect = pygame.rect.Rect(0,0,0,0)
def update(self):
self.text = "vidas: %d, puntos: %d" %
(self.pacman.vidas, self.pacman.puntos)
self.image = self.font.render(self.text, 1,(255, 255, 0))
self.rect = self.image.get_rect()
#situamos al marcador en la esquina inferior derecha
self.rect.topleft =
(pygame.display.get_surface().get_width() -
self.rect.width,
pygame.display.get_surface().get_height() -
self.rect.height)
Un detalle, observa que le pasamos al constructor de la clase Marcador, función "__init__", el sprite Pacman del que vamos a mostrar sus puntos y vidas. Estas dos propiedades se la añadimos a la clase Pacman.
class Pacman ( MiSprite ):
def __init__(self, fichero_imagen, pos_inicial):
MiSprite.__init__(self, fichero_imagen, pos_inicial, [0,0])
self.vidas = 3
self.puntos = 0
Además hacemos que cuando pacman se coma un objeto, se compruebe si este tiene asociado puntos y si es así, se los sumamos a los puntos de pacman
class Pacman ( MiSprite ):
...
def update (self):
...
global sprites
sprites_choque = pygame.sprite.spritecollide(self, sprites, False)
for sprite in sprites_choque:
if sprite != self:
if hasattr ( sprite, "comestible" ):
if sprite.comestible:
if hasattr ( sprite, "puntos" ):
self.puntos += sprite.puntos
sprite.kill() #destruimos el sprite
En la próxima entrega haremos que nuestro fantasma se mueva por toda la pantalla en busca de nuestro pacman.
viernes, 2 de enero de 2009
pacman 0.005 - Construyendo un laberinto
En esta nueva entrega de desarrollo de un juego clásico de pacman usando Python y Pygame, vamos añadir paredes para poder construir un laberinto en el que pacman huye de los fantasmas mientras se va comiendo todo lo que encuentra.
Añadimos una nueva clase denominada "Pared" que heredera de la clase "Sprite". Lo particular de esta clase es que no cargaremos una imagen sino que se dibujará un rectángulo. Para hacer esto crearemos una superficie de dibujo con las dimensiones especificadas a través de una lista o tupla que indica ancho y alto, "pygame.Surface(dimension)", y a continuación la rellenaremos del color deseado, "image.fill(color)".
class Pared ( pygame.sprite.Sprite ):
def __init__(self, color, pos_inicial, dimension):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(dimension)
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.topleft = pos_inicial
self.infranqueable = True
def update(self):
pygame.sprite.Sprite.update(self)
Además, queremos que las paredes no puedan ser atravesadas por pacman ni por los fantasmas. Para hacer esto, modificaremos la función "update" de la clase "MiSprite", comprobándose si el sprite choca contra otro sprite que presenta la propiedad "infranqueable". Observa que al final de la función "__init__" de la clase "Pared" hemos inicializado la propiedad "infranqueable" a True.
class MiSprite ( pygame.sprite.Sprite ):
...
def update (self):
#la funcion "copy" crea una copia del rectangulo
copia_rect = copy.copy(self.rect)
self.rect.move_ip ( self.velocidad[0], self.velocidad[1])
colisiones = pygame.sprite.spritecollide(self,
sprites,
False)
for colision in colisiones:
if colision != self:
if hasattr ( colision, "infranqueable" ):
if colision.infranqueable:
self.rect = copia_rect
return
...
Como lógica para manejar el choque con los objetos "infranqueables" hemos guardado la posición del sprite, movido el sprite y a continuación comprobado si choca contra un objeto. Si es así, volvemos el sprite a la posición original antes del choque.
Por último, creamos dos paredes en nuestro juego para probar nuestra nueva versión.
sprite = Pared ( [150,150,150], [72,72], [100,10] )En la próxima entrega añadiremos un marcador a nuestro juego en donde se visualice los puntos y las vidas que les queda a nuestro pacman.
sprites.add ( sprite )
sprite = Pared ( [150,150,150], [172,172], [10,100] )
sprites.add ( sprite )
jueves, 1 de enero de 2009
pacman 0.004 - ¡A comer!
Lo que haremos a continuación es enseñar a comer a nuestro pacman. La regla será que pacman se comerá todos los objetos (sprites) comestibles con los que se encuentre. Un sprite será comestible si tiene el atributo "comestible" y éste tiene valor "True" y pacman se lo comerá si colisiona con él.
Para ver los sprites con los que va colisionando nuestro pacman vamos a emplear la función "pygame.sprite.spritecollide". Esta función dado un sprite X y una lista de sprites devuelve los sprites de esta lista que colisiona con el sprite X. El tercer parámetro de esta función, que tenemos a False, indica si queremos que se eliminen automáticamente de la lista todos aquellos sprites con los que colisiona.
sprites_choque = pygame.sprite.spritecollide(spriteX,
sprites,
False)
Así quedará nuestra función "update" de Pacman. Para facilitar su lectura no la incluyo entera pero la puedes ver en el código que te adjunto
class Pacman ( MiSprite ):
...
def update (self):
...
MiSprite.update(self)
global sprites
sprites_choque = pygame.sprite.spritecollide(self,
sprites,
False)
for sprite in sprites_choque:
if sprite != self:
if hasattr ( sprite, "comestible" ):
if sprite.comestible:
sprite.kill() #destruimos el sprite
Por si no sabes mucho de Python, la función "hasattr" te permite comprobar si un objeto tiene definido un determinado atributo.
Destruimos un sprite llamando a su método "kill" que lo destruye eliminándolo de todas aquellas listas que lo contienen.
Para hacer que las cerezas y las bolitas sean comestibles basta con indicarlo a través del nuevo atributo "comestible".
sprite = MiSprite ("fruta.gif", [0, 100], [0,0])En la próxima entrega veremos còmo crear paredes dentro de nuestro juego de tal forma que sean infranqueables.
sprite.comestible = True
sprites.add ( sprite )
sprite = MiSprite ("bola.gif", [100, 100], [0,0] )
sprite.comestible = True
sprites.add ( sprite )
pacman 0.003 - Controlando nuestro pacman
Vamos a modificar nuestro juego Pacman para que el muñequito sea controlado por teclado. Además, para que no huyan los muñecos, limitaremos su movimiento a las dimensiones de la pantalla.
Como iremos introduciendo lógica particular para nuestro pacman, crearemos una clase específica para él. La nueva clase se llama "Pacman" y heredará de la clase "MiSprite". Por lo pronto, lo único que haremos es que en la función "update" trate los eventos de teclado y cambie la dirección del muñequito.
class Pacman ( MiSprite ):Ya ves, muy sencillo, vemos si se produce un evento de tecla pulsada, comprobamos la tecla y si la tecla es de una de la de movimiento de cursor, cambiamos la dirección de nuestro pacman.
def __init__(self, fichero_imagen, pos_inicial):
MiSprite.__init__(self, fichero_imagen, pos_inicial, [0,0])
def update (self):
global eventos
v = 1 #velocidad a la que se mueve nuestro pacman
for event in eventos:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
self.velocidad[0] = -v
self.velocidad[1] = 0
elif event.key == pygame.K_RIGHT:
self.velocidad[0] = v
self.velocidad[1] = 0
elif event.key == pygame.K_UP:
self.velocidad[1] = -v
self.velocidad[0] = 0
elif event.key == pygame.K_DOWN:
self.velocidad[1] = v
self.velocidad[0] = 0
elif event.key == pygame.K_SPACE:
pass
MiSprite.update(self)
Además, vamos a modificar la función "update" de la clase "MiSprite" para que los muñecos no salgan fuera de nuestra pantalla.
class MiSprite ( pygame.sprite.Sprite ):No creo que haga falta comentar el código, es también muy sencillo. Se comprueba si el rectángulo de la imagen desborda algún lado de la pantalla y si es así, la modificamos para que no se salga de la pantalla.
....
def update (self):
self.rect.move_ip(self.velocidad[0],self.velocidad[1])
screen = pygame.display.get_surface ()
if self.rect.top < 0:
self.rect.top = 0
self.velocidad[1] = 0
elif self.rect.bottom > screen.get_height():
self.rect.bottom = screen.get_height()
self.velocidad[1] = 0
if self.rect.left < 0:
self.rect.left = 0
self.velocidad[0] = 0
elif self.rect.right > screen.get_width():
self.rect.right = screen.get_width()
self.velocidad[0] = 0
Para la próxima entrega haremos que nuestro pacman pueda comer determinados objetos del juego.