Programación con Pygame IV

26
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 más y más enrevesados. ¡Afortunadamente, PyGame se puede encargar por nosotros de muchas más cosas! En efecto, hay determinados detalles que hemos implementado por nuestra cuenta y que PyGame ya incorpora de una manera más simple. Sólo 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 programación dirigida a objetos, PyGame crea y gestiona los sprites de manera nativa. Una vez comprendido, ¡todo se vuelve más sencillo! En las siguientes páginas, 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 pequeño programa de demostración en el que nos estamos apoyando). De paso aprenderemos cómo modificar el movimiento para que parezca no lineal, simulando la gravedad. ¡Vamos allá! ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO PÁGINA 1 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

description

Los archivos pueden encontrarse en http://sites.google.com/site/laislalibre/informatica/python/pygame

Transcript of Programación con Pygame IV

Page 1: Programación con Pygame IV

SpritesMario 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 más y más enrevesados.

¡Afortunadamente, PyGame se puede encargar por nosotros de muchas más cosas!

En efecto, hay determinados detalles que hemos implementado por nuestra cuenta y que PyGame ya incorpora de una manera más simple. Sólo 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 programación dirigida a objetos, PyGame crea y gestiona los sprites de manera nativa. Una vez comprendido, ¡todo se vuelve más sencillo! En las siguientes páginas, 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 pequeño programa de demostración en el que nos estamos apoyando). De paso aprenderemos cómo modificar el movimiento para que parezca no lineal, simulando la gravedad.

¡Vamos allá!

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 1 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 2: Programación con Pygame IV

mario01.py

En este primer paso sólo 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 código:

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 2 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

# -*- coding: utf-8 -*-#-----------------------------------------------------------------------# mario01.py# Implemetación de sprites#-----------------------------------------------------------------------

import sysimport pygamefrom pygame.locals import *

# Clase MiSpriteclass 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() # Definir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150)

# Inicializar PyGame y crear la Surface del juegopygame.init()visor = pygame.display.set_mode((640, 480))

# Inicializar el spritesprite = MiSprite("mario.bmp")grupo = pygame.sprite.RenderUpdates( sprite )

# Crear un reloj para controlar la animaciónreloj = pygame.time.Clock()

# El bucle de la animaciónwhile 1: #Fijar la animación a 60 fps reloj.tick(60)

# Gestionar los eventos for evento in pygame.event.get(): if evento.type == QUIT: pygame.quit()

Page 3: Programación con Pygame IV

Tras la importación de las librerías habituales, lo primero que hacemos en el código anterior es definir nuestra nueva clase de sprite que deriva de la clase de sprite de PyGame.

pygame.sprite.Sprite es la clase de sprite que implementa PyGame de forma nativa. Al ponerla entre paréntesis le decimos a Pygame que cree la nuestra a partir de aquella.

Recuerda que toda clase debería tener definida la función 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 función que inicializa la clase original. Eso se consigue escribiéndolo en primer lugar:

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 algún que otro dolor de cabeza. Python está construido de forma que en toda función 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 pasárselo a la función y no tienes que ocuparte tú de ello. Acuérdate siempre de esto; en la definición 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:

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 3 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

sys.exit()

# Actualizar el sprite grupo.update()

# Dibujar la escena visor.fill((255,255,255)) grupo.draw( visor )

# Mostrar la animación pygame.display.update()

class MiSprite( pygame.sprite.Sprite ):

def __init__( self, dibujo ): pygame.sprite.Sprite.__init__( self )

self.image = pygame.image.load(dibujo)

Page 4: Programación con Pygame IV

De hecho, self.image es una propiedad que PyGame nos obliga a definir en nuestros sprites, pues es la que usará automáticamente para dibujarlos en pantalla. Fíjate cómo lo hemos hecho; en la definición de la función __init__() hemos puesto un segundo argumento, además de self, dibujo. Allí le pasaremos la imagen cuando creemos el objeto que representa nuestro sprite de Mario. En la definición de self.image usamos, por lo tanto, la función de PyGame que ya conocemos para cargarla, pygame.image.load(). A su vez, también como sabemos, conviene convertir la imagen al formato adecuado para Pygame, así que la siguiente línea es

¿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 función __init__() ? Hay otra propiedad de los sprites que PyGame nos obliga a definir también y es self.rect. Se trata, ni más ni menos, de una variable de tipo rect que almacena la posición y el tamaño tiene nuestro sprite.

La manera más sencilla de obtener el rect es decirle a Python que lo calcule por sí misma llamando a la función miembro de una imagen get_rect(). Esta función nos devuelve el tamaño de la imagen a la que pertenece (en forma de rect). En la siguiente línea lo único que hacemos es indicar en qué posición va a estar el sprite modificando las propiedades top y left del rect (consulta la documentación 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 animación. Optamos por una ventana de 640x480 pixeles, con las opciones que PyGame toma por defecto.

Ha llegado el momento importante; vamos a crear a Mario. Crear un sprite es un proceso que, comúnmente, requiere dos pasos. Primero hay que crear al propio sprite. Y segundo hay que agruparlo. ¿Qué quiere decir esto? De cara a manejar múltiples 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 sólo sprite. Veamos:

Hemos creado dos variables. La primera, sprite, contiene nuestro sprite de Mario. La segunda, grupo, contiene el grupo al que pertenece nuestro sprite.

¿Cómo lo hemos hecho? Para empezar, hemos invocado el nombre de la clase del sprite pasándole como argumento el nombre del archivo que contiene la imagen que vamos a

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 4 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

self.image = self.image.convert()

self.rect = self.image.get_rect() self.rect.topleft = (0, 150)

pygame.init()visor = pygame.display.set_mode((640, 480))

sprite = MiSprite("mario.bmp")grupo = pygame.sprite.RenderUpdates( sprite )

Page 5: Programación con Pygame IV

usar. Fíjate en la definición de la clase; allí verás que la función __init__() la hemos puesto con dos parámetros, self (que recuerda que se ignora cuando se llama a la función) y otra más que luego usamos en la definición para indicar la imagen del sprite.

Para añadir un sprite a un grupo usamos pygame.sprite.RenderUpdates(); esta función nos crea un grupo de sprites al que añade el que le pasemos como argumento. El grupo, como hemos dicho, lo almacenamos en una variable que hemos llamado (qué imaginación) grupo. Dicho sea de paso, si tuviéramos otro sprite y quisiéramos añadirlo a este grupo, bastaría que escribiéramos grupo.add(nombre_de_otro_sprite). ¡Fácil!

Lo siguiente es algo que va a hacer mucho más simple lo que hasta ahora hemos hecho más complicado; controlar la velocidad de la animación. En los tutoriales anteriores lo hemos hecho a mano, mirando el tiempo que pasaba y ajustando la diferencia hasta que se conseguía el tiempo requerido. Afortunadamente, Python se puede encargar automáticamente de eso por ti. Para ello necesitamos crear un objeto especial:

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 animación. Lo podemos ver al comienzo del bucle habitual:

¡Qué sencillo resulta ahora! Como quiero que la animación se reproduzca a 60 fotogramas por segundo y no más rápido, basta con que lo indique usando la función 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 típico código 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 código:

Aquí podemos ver la utilidad del concepto de grupo de PyGame. Para decirle al grupo que actualice las posiciones (esto es, que averigüe cuáles deben ser las nuevas posiciones) de todos sus sprites basta usar su función miembro update(). Y para decirle

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 5 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

reloj = pygame.time.Clock()

while 1: #Fijar la animación a 60 fps reloj.tick(60)

# Actualizar el sprite grupo.update()

# Dibujar la escena visor.fill((255,255,255)) grupo.draw( visor )

# Mostrar la animación pygame.display.update()

Page 6: Programación con Pygame IV

que los dibuje en esas posiciones usamos draw() pasándole como argumento la surface en la que ha de hacerse (nuestro visor). Finalmente, como siempre en PyGame, volcamos toda esa información en pantalla para que la muestre con pygame.display.update().

Te puedes preguntar ¿y cómo sabe PyGame, cuando llamo a grupo.update() dónde están 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 función en MiSprite, PyGame no hace nada y deja a Mario siempre en la misma posición. Ejecuta el programa:

Una vez que comprendas el proceso, verás que es sencillo y potente. Prueba a eliminar los comentarios; el código que resulta es bastante breve y fácil de entender

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 6 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 7: Programación con Pygame IV

mario02.py

La ventaja de utilizar la programación dirigida a objetos que incorpora PyGame es que modificar el comportamiento de los sprites es muy sencillo. Vamos a darle movimiento a Mario. Nada más simple:

# -*- coding: utf-8 -*-#-------------------------------------------------------------------# mario02.py# Movimiento sencillo#-------------------------------------------------------------------

import sysimport pygamefrom pygame.locals import *

# Clase MiSpriteclass 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() # Definir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) def update(self): # Modificar la posición del sprite self.rect.move_ip(1,0)

# Inicializar PyGame y crear la Surface del juegopygame.init()visor = pygame.display.set_mode((640, 480))

# Inicializar el spritesprite = MiSprite("mario.bmp")grupo = pygame.sprite.RenderUpdates( sprite )

# Crear un reloj para controlar la animaciónreloj = pygame.time.Clock()

# El bucle de la animaciónwhile 1:

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 7 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 8: Programación con Pygame IV

¡Extremadamente sencillo! ¿Recuerdas que, en cada fotograma, grupo.update() llama a la función update() de cada sprite para saber dónde debe dibujarlo? Bueno, pues simplemente debemos definir esa función dentro de nuestra clase para tener la tarea hecha. Esto es lo único que hemos añadido al código:

De paso, usamos otra función incorporada, move_ip(), que también hace las cosas más sencillas. En lugar de modificar a mano las coordenadas de self.rect, el rectángulo que define la posición y el tamaño del sprite, move_ip() toma de argumento dos valores que indican cuánto 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 dirección vertical).

Terminado. Si ejecutas el programa verás a Mario desplazándose lentamente por la pantalla; exactamente a 60 pixeles por segundo (1 pixel en cada fotograma, 60 fotogramas por segundo).

#Fijar la animación 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.fill((255,255,255)) grupo.draw( visor )

# Mostrar la animación pygame.display.update()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 8 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

def update(self): # Modificar la posición del sprite self.rect.move_ip(1,0)

Page 9: Programación con Pygame IV

mario03.py

Implementar el rebote es igual de sencillo:

# -*- coding: utf-8 -*-#-------------------------------------------------------------------# mario03.py# Rebote#-------------------------------------------------------------------

import sysimport pygamefrom pygame.locals import *

# Clase MiSpriteclass 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() # Definir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Definir las velocidad self.dx = 1 def update(self): # Modificar la posición 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 juegopygame.init()visor = pygame.display.set_mode((640, 480))

# Inicializar el spritesprite = MiSprite("mario.bmp")grupo = pygame.sprite.RenderUpdates( sprite )

# Crear un reloj para controlar la animaciónreloj = pygame.time.Clock()

# El bucle de la animación

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 9 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 10: Programación con Pygame IV

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 posición. Lógicamente, para poder cambiar ese signo, necesitamos almacenar la cantidad que sumamos a la posición 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 definición en la función __init__() de la clase y, por lo tanto, deberemos añadirle al nombre el ya conocido self:

Bien. Una vez hecho esto, implementar el rebote de Mario requiere modificar la definición de la función update() del sprite:

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?

while 1: #Fijar la animación 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.fill((255,255,255)) grupo.draw( visor )

# Mostrar la animación pygame.display.update()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 10 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

# Definir las velocidad self.dx = 1

def update(self): # Modificar la posición 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

Page 11: Programación con Pygame IV

mario04.py

Un nuevo paso. Esta vez, al igual que hacíamos con Guy, vamos a invertir la imagen del sprite cuando éste rebote. Éste es el código:

# -*- coding: utf-8 -*-#-------------------------------------------------------------------# mario04.py# Rebote invirtiendo el sprite#-------------------------------------------------------------------

import sysimport pygamefrom pygame.locals import *

# Clase MiSpriteclass 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() # Definir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Definir la velocidad self.dx = 1 def update(self): # Modificar la posición 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.flip( self.image, True, False )

# Inicializar PyGame y crear la Surface del juegopygame.init()visor = pygame.display.set_mode((640, 480))

# Inicializar el spritesprite = MiSprite("mario.bmp")grupo = pygame.sprite.RenderUpdates( sprite )

# Crear un reloj para controlar la animaciónreloj = pygame.time.Clock()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 11 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 12: Programación con Pygame IV

¿Puedes creer que sea algo tan simple? Pues sí: en el mismo lugar del código donde miramos si el sprite ha llegado al borde (y cambiamos la dirección del movimiento en caso afirmativo), lo único que hacemos es transformar la imagen de Mario por su reflejo horizontal

De nuevo, igual que hicimos con Guy, la función pygame.transform.flip() nos permite hacer esa inversión 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 programación dirigida a objetos y del concepto de Sprite de PyGame; todo su comportamiento lo incluimos en la definición de la clase y cuando usemos un objeto de ese tipo, automáticamente se comportará como tal. Ello hace que los programas sean mucho más fácilmente modificables y ampliables, como estamos viendo.

# El bucle de la animaciónwhile 1: #Fijar la animación 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.fill((255,255,255)) grupo.draw( visor )

# Mostrar la animación pygame.display.update()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 12 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.flip( self.image, True, False )

Page 13: Programación con Pygame IV

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 13 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 14: Programación con Pygame IV

mario05.py

Implementar el movimiento en la dirección vertical, incluido el rebote correspondiente, te debería resultar ahora bastante fácil:

# -*- coding: utf-8 -*-#-------------------------------------------------------------------# mario05.py# Implementando el movimiento vertical#-------------------------------------------------------------------

import sysimport pygamefrom pygame.locals import *

# Clase MiSpriteclass 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() # Definir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Definir las velocidades self.dx = 1 self.dy = 1 def update(self): # Modificar la posición 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.flip( 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 juegopygame.init()visor = pygame.display.set_mode((640, 480))

# Inicializar el spritesprite = MiSprite("mario.bmp")grupo = pygame.sprite.RenderUpdates( sprite )

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 14 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 15: Programación con Pygame IV

¿Hace falta explicar algo? Sólo hemos añadido en __init__() la nueva velocidad vertical self.dy y en update() la hemos añadido al movimiento y a la comprobación del rebote.

# Crear un reloj para controlar la animaciónreloj = pygame.time.Clock()

# El bucle de la animaciónwhile 1: #Fijar la animación 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.fill((255,255,255)) grupo.draw( visor )

# Mostrar la animación pygame.display.update()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 15 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 16: Programación con Pygame IV

mario06.py

Hasta ahora sólo hemos hecho movimientos rectilíneos y uniformes. ¿Qué tal simular algo más real, como una caída con gravedad?

# -*- coding: utf-8 -*-#-------------------------------------------------------------------# mario06.py# Simular la gravedad#-------------------------------------------------------------------

import sysimport pygamefrom pygame.locals import *

# Clase MiSpriteclass 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() # Definir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Definir las velocidades self.dx = 1 self.dy = 1 def update(self): # Modificar la posición 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.flip( 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 juegopygame.init()visor = pygame.display.set_mode((640, 480))

# Inicializar el sprite

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 16 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 17: Programación con Pygame IV

¿Te podías imaginar que era tan sencillo? Lo único que hemos hecho ha sido añadir en la función update() la siguiente línea de código:

La explicación es muy simple. La gravedad lo único que hace es empujarnos hacia abajo de manera constante. Y la manera de hacerlo en la función update() es, por lo tanto, sumar una cantidad constante a la velocidad en el eje vertical. Fíjate 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 más que variar ese 0.5 conseguimos una gravedad más o menos intensa.

¡Ejecuta el programa! Veras como el rebote es ahora más 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 pequeño bug; si esperamos lo suficiente, veremos como Mario... ¡termina por atravesar los bordes de la ventana!

En el próximo paso veremos cómo solucionar esto.

sprite = MiSprite("mario.bmp")grupo = pygame.sprite.RenderUpdates( sprite )

# Crear un reloj para controlar la animaciónreloj = pygame.time.Clock()

# El bucle de la animaciónwhile 1: #Fijar la animación 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.fill((255,255,255)) grupo.draw( visor )

# Mostrar la animación pygame.display.update()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 17 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

self.dy = self.dy + 0.5

Page 18: Programación con Pygame IV

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 línea:

# -*- coding: utf-8 -*-#-------------------------------------------------------------------# mario07.py# Movimiento de plataforma#-------------------------------------------------------------------

import sysimport pygamefrom pygame.locals import *

# Clase MiSpriteclass 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() # Definir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (0, 150) # Definir las velocidades self.dx = 5.0 self.dy = 1.0 def update(self): # Modificar la posición 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.flip( 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 juegopygame.init()visor = pygame.display.set_mode((640, 480))

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 18 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 19: Programación con Pygame IV

En efecto, basta darle al sprite un pequeño 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 añadimos a la velocidad como un ratón; va mordiéndole 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 producirán efectos extraños en las paredes.

Por cierto, si necesitáramos solucionar el tema de atravesar el suelo por su cuenta, tampoco sería complicado. ¿Te imaginas cómo? Fácil; antes de cambiar la posición del sprite con move_ip() deberías comprobar si ya está en el suelo, en cuyo caso deberías poner el valor de self.dy a cero para que dejara de bajar. (Pregunta: ¿Cómo saber si los pies de Mario están en el borde de la ventana? Respuesta: con el atributo bottom que tiene todo objeto de tipo rect)

# Inicializar el spritesprite = MiSprite("mario.bmp")grupo = pygame.sprite.RenderUpdates( sprite )

# Crear un reloj para controlar la animaciónreloj = pygame.time.Clock()

# El bucle de la animaciónwhile 1: #Fijar la animación 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.fill((255,255,255)) grupo.draw( visor )

# Mostrar la animación pygame.display.update()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 19 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

if self.rect.left < 0 or self.rect.right > 640: self.dx = -self.dx self.image = pygame.transform.flip( 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)

Page 20: Programación con Pygame IV

mario08.py

Ya que estamos trabajando con sprites. ¿Por qué limitarnos a un sólo Mario? Traigamos a su hermano gemelo a la fiesta...

# -*- coding: utf-8 -*-#-------------------------------------------------------------------# mario08.py# Varios sprites#-------------------------------------------------------------------

import sysimport pygamefrom pygame.locals import *

# Clase MiSpriteclass 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() # Definir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (posX, posY) # Definir las velocidades self.dx = 5.0 self.dy = 1.0 def update(self): # Modificar la posición 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.flip( 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 juegopygame.init()visor = pygame.display.set_mode((640, 480))

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 20 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 21: Programación con Pygame IV

Para empezar, hemos modificado ligeramente la definición de nuestro tipo de sprite ya que si no todos los sprites que creemos de este tipo comenzarán en el mismo sitio (y por tanto sólo veríamos uno). Esto es fácil de solucionar; ponemos dos parámetros más en la función miembro __init__() que inicializa la clase.

Los parámetros posX y posY se encargarán de situar en su posición inicial al sprite. Ello quiere decir que hay que modificar una línea más de esta función:

para que así, en efecto, el sprite se posicione allí al principio.

Lo último que queda es, simplemente, crear los sprites:

# Inicializar los spritessprite = 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 animaciónreloj = pygame.time.Clock()

# El bucle de la animaciónwhile 1: #Fijar la animación 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.fill((255,255,255)) grupo.draw( visor )

# Mostrar la animación pygame.display.update()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 21 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

def __init__( self, dibujo, posX, posY ):

self.rect.topleft = (posX, posY)

sprite = MiSprite("mario.bmp", 0, 150)grupo = pygame.sprite.RenderUpdates( sprite )sprite2 = MiSprite("mario.bmp", 210, 50)grupo.add( sprite2 )

Page 22: Programación con Pygame IV

Respecto del primer Mario, poco que decir. Lo único que hemos modificado es que, ahora, en la creación del sprite hay que indicar la posición en la que lo queremos. Con el segundo sprite hacemos lo mismo (con otra posición distinta) y, como ya está creado el grupo de sprites, lo añadimos con la función miembro add().

Dos cosas podrás 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. ¿Cómo podríamos gestionar la colisión entre ambos de manera que, por ejemplo, rebotaran?

El problema de la transparencia, en este caso, es muy fácil de solucionar ya que sólo hay que decirle al sprite qué color ha de tomar como transparente (para no dibujarlo). Eso se consigue añadiendo una sola línea el la función __init__() del sprite:

La función set_colorkey() es una función 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 añades y lanzas el programa, verás que ya no aparece ese cuadrado blanco tan molesto alrededor de Mario.

Respecto de la colisión de los sprites... tendremos que pasar a la última versión del programa.

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 22 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

self.image.set_colorkey((255,255,255))

Page 23: Programación con Pygame IV

mario09.py

Hay varias formas de abordar el tema. La que viene a continuación es sólo una de las más sencillas:

# -*- coding: utf-8 -*-#-------------------------------------------------------------------# mario09.py# Sprites con colisión#-------------------------------------------------------------------

import sysimport pygamefrom pygame.locals import *

# Clase MiSpriteclass 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)) # Definir el rect del sprite self.rect = self.image.get_rect() self.rect.topleft = (posX, posY) # Definir las velocidades self.dx = 5.0 self.dy = 1.0 def update(self): # Modificar la posición 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.flip( 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 juegopygame.init()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 23 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 24: Programación con Pygame IV

Lo primero es una advertencia: como cualquier programador sabe, los documentos de referencia (en los que se incluyen las librerías, objetos e instrucciones disponibles en el lenguaje) son un compañero inseparable en la aventura de programar. ¿No tienes cerca la referencia de Pygame? Cógela ahora mismo. Piensa que te nombramos unas pocas funciones u objetos pero hay muchas más. Y muchas más opciones.

Lo segundo es hacerte notar que hemos añadido la línea a la que nos referíamos al final del capítulo anterior, el código que consigue que el color blanco (255,255,255) se tome como transparente y no se dibuje.

visor = pygame.display.set_mode((640, 480))

# Inicializar los spritessprite = 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 animaciónreloj = pygame.time.Clock()

# El bucle de la animaciónwhile 1: #Fijar la animación 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 colisión: 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.fill((255,255,255)) grupo.draw( visor ) grupo2.draw( visor )

# Mostrar la animación pygame.display.update()

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 24 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

Page 25: Programación con Pygame IV

Bien, vamos al tema de la detección de colisiones. PyGame incorpora muchas funciones que gestionan los posibles choques entre sprites y/o grupos. ¡Mira el documento de Referencia de PyGame! Como notarás enseguida, una de las maneras más cómodas es usar la función pygame.sprite.spritecollideany(). Fíjate qué pone en la referencia:

Lo que nos interesa son dos cosas; la función 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 función colisiona con algún sprite que pertenezca al grupo que también le pasamos, devolviéndonos True o False en consecuencia. Esto nos viene muy bien pues solo tenemos dos sprites. En programas más complejos, con muchos sprites, necesitaríamos otras funciones distintas que nos dijeran a su vez cuáles son los sprites que han colisionado, etc.

He aquí otro punto importante; como suele mirarse la colisión de sprites y grupos, conviene tener separados los sprites que queremos mirar si colisionan en diferentes grupos. Típicamente, por ejemplo, tendríamos un grupo para las naves de los enemigos y otro grupo para los rayos láser. O, en el juego del Pong, un grupo para las raquetas y otro para la pelota. Ésa es la razón por la que hemos puesto lo siguiente:

En el capítulo anterior añadíamos el sprite sprite2 al grupo grupo en el que estaba el sprite sprite. Como ahora queremos mirar cuándo chocan, definimos un nuevo grupo, grupo2. ¡Ya los tenemos separados!

Lo siguiente es implementar la colisión. Como ya hemos visto más arriba la sintaxis de la función pygame.sprite.spritecollideany(), no debería ser difícil de entender:

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 25 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

spritecollideany

Consulta simple para ver si un sprite colisiona con algún otro en el grupo.

pygame.sprite.spritecollideany(sprite, group): return bool

Consulta si el sprite dado colisiona con algún sprite en el grupo. La intersección se determina comparando el atributo Sprite.rect de cada sprite.

Esta prueba de colisión puede ser mas rápida que

pygame.sprite.spritecollideany()

dado que tiene menos trabajo para hacer. Retornará al encontrar la primer colisión.

sprite = MiSprite("mario.bmp", 0, 150)grupo = pygame.sprite.RenderUpdates( sprite )sprite2 = MiSprite("mario.bmp", 210, 50)grupo2 = pygame.sprite.RenderUpdates( sprite2 )

Page 26: Programación con Pygame IV

En el código anterior miramos si el sprite sprite ha chocado con algún 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í? Análogamente al rebote en los bordes de la ventana, cambiamos las direcciones de movimiento de los dos sprites. ¡Rebote conseguido!

Sólo queda añadir la parte de código que tiene en cuenta que ahora no hay sólo un grupo para actualizar y dibujar en pantalla. Así que, en los sitios correspondientes, hay que colocar primero

y después

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... ¿Sabrías solucionarlo? Ya puestos, ¿sabrías hacer que comiencen desde una posición aleatoria? ¿Y que salgan, no dos, si no muchos marios, por ejemplo cada vez que se haga click con el ratón?

Ante nosotros se extiende un campo de creatividad ilimitado...

ASUNTO: PROGRAMACIÓN CON PYTHON Y PYGAME CURSO: 1º BACHILLERATO

PÁGINA 26 DE 26 DURACIÓN: PERÍODOS DE DOS CLASES

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

grupo2.update()

grupo2.draw( visor )