Programación con Pygame VI

10
Drive: Un juego de Scrolling (Basado en un script original de Mark Ivey) Importación de Librerías y Definición de Constantes Se cargan las librerías habituales, incluyendo como en el juego anterior random y os, y definimos la anchura y la altura de la ventana de juego. También definimos la constante VELOCIDAD que nos va a indicar la velocidad a la que vamos a movernos por la carretera (en realidad, la velocidad a la que va a desplazarse la imagen de carretera de fondo hacia abajo para dar la sensación de movimiento; tal como lo hemos puesto, será a base de 5 pixeles por fotograma) Funciones cargarImagen() y cargarSonido() Las funciones cargarImagen() y cargarSonido() vuelven a ser las mismas que en el juego anterior, sólo hemos modificado ligeramente la primera de ellas. ¿De qué manera? Hemos asumido que no siempre queremos transparencia. Así que el segundo parámetro de la función, tieneTrasnparencia lo que va a indicar es si la imagen ya la tiene incorporada o no (por defecto es no). Comprobando con un if cuál es el caso, llamamos a PROGRAMA: DRIVE CURSO: 1º BACHILLERATO PÁGINA 1 DE 10 CC: FERNANDO SALAMERO

description

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

Transcript of Programación con Pygame VI

Page 1: Programación con Pygame VI

Drive: Un juego de Scrolling

(Basado en un script original de Mark Ivey)

Importación de Librerías y Definición de ConstantesSe cargan las librerías habituales, incluyendo como en el juego anterior random y os, y definimos la anchura y la altura de la ventana de juego. También definimos la constante VELOCIDAD que nos va a indicar la velocidad a la que vamos a movernos por la carretera (en realidad, la velocidad a la que va a desplazarse la imagen de carretera de fondo hacia abajo para dar la sensación de movimiento; tal como lo hemos puesto, será a base de 5 pixeles por fotograma)

Funciones cargarImagen() y cargarSonido()Las funciones cargarImagen() y cargarSonido() vuelven a ser las mismas que en el juego anterior, sólo hemos modificado ligeramente la primera de ellas. ¿De qué manera? Hemos asumido que no siempre queremos transparencia. Así que el segundo parámetro de la función, tieneTrasnparencia lo que va a indicar es si la imagen ya la tiene incorporada o no (por defecto es no). Comprobando con un if cuál es el caso, llamamos a

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 1 DE 10 CC: FERNANDO SALAMERO

Page 2: Programación con Pygame VI

la función adecuada para convertir la imagen, ya sea imagen.convert_alpha() o imagen.convert(), viejas conocidas nuestras.

Excepto la imagen de fondo que vamos a desplazar, las demás imágenes son archivos png con transparencia, así que en esos casos invocaremos a la función con True.

Clase CocheYa sabemos, una vez vistos los tutoriales anteriores, cuál es la estructura de la definición de un sprite de PyGame, es decir, una clase derivada de pygame.sprite.Sprite. Vamos a centrarnos en las variaciones propias que nos interesan para este juego. La clase Coche será la que represente el coche del jugador. Fíjate que, en este juego, lo que se desplaza verticalmente es la imagen de fondo y no el coche.

1. En __init__(), aparte de definir los atributos necesarios y situar en pantalla al sprite, creamos la variable giro. Para que el movimiento parezca real, necesitamos girar un poco el coche cuando el jugador pulse izquierda o derecha. De ello es precisamente de lo que se va a encargar esta variable. Hay un detalle importante al respecto; habrás notado que nuestra clase posee dos atributos que almacenan una imagen, image (el obligatorio de todo sprite) e imagen_base. ¿Por qué?Cuando simplemente desplazamos una imagen por pantalla, no la deformamos y todo está en orden. Pero cuando se modifica su forma o su tamaño estamos en lo que se conoce como transformación destructiva. Si realizas varias de estas transformaciones, tu imagen final puede no parecerse en nada a la que tenías originalmente. Un ejemplo; tienes una imagen y la conviertes en una que es tres veces más pequeña. Luego coges ésta y la amplías tres veces para volver al tamaño original. ¡La verás pixelada! Por el camino, has perdido la información de esos pixeles que han desaparecido al encoger la imagen. Al ampliarla nuevamente, el ordenador se ha tenido que inventar esa información dándote la imagen modificada.La forma de solucionar este problema es tener almacenada la imagen original y utilizar ésta en cada transformación, sin acumular transformaciones destructivas. Como quiera que la rotación de una imagen es destructiva (el número de pixeles de la pantalla es limitado y hay que hacer encajar la imagen en éstos), almacenamos en imagen_base el dibujo original del coche. Cuando tengamos que modificarlo hacemos sobre él las operaciones necesarias y el resultado lo ponemos en el image del sprite para que se muestre correctamente.

2. Lo más importante ocurre en update(). Para empezar, no queremos que el coche gire sin parar y pueda darse la vuelta. Sólo queremos permitir el giro para avanzar por la carretera y nada más. Teniendo en cuenta que el ángulo de giro al comienzo es 0, vamos a dejar que el coche se desvíe un máximo de 45 grados a ambos lados. El bloque if que encontramos al comienzo de la función tiene este objetivo.Por otra parte, si no hemos llegado a ese límite (entramos en el else) sí que podemos girar el coche a la posición deseada. Debes también tener en cuenta que, al girar un sprite, PyGame mantiene fija la esquina superior derecha del sprite. Esto tiene ventajas e inconvenientes. El inconveniente que nos ocupa ahora es que, como al girar la imagen ésta cambia de tamaño, el giro queda artificial (el centro del rect desplazado). ¿Cómo solucionarlo? Si piensas un poco darás con la solución; primero almacenamos las coordenadas del centro del rect

x, y = self.rect.center

y más adelante, una vez hecho el giro, devolvemos el centro a su sitio

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 2 DE 10 CC: FERNANDO SALAMERO

Page 3: Programación con Pygame VI

self.rect = self.image.get_rect()self.rect.center = x,y

La primera de las dos líneas anteriores no es gratuita. Recuerda que el rect del dibujo ha cambiado de tamaño, así que primero hay que ajustar el rect del sprite al nuevo tamaño con get_rect() y luego sí, recolocar su rect.center.Fuera de ello, el giro en sí mismo se realiza de la siguiente manera:

self.image = pygame.transform.rotate(self.imagen_base, self.giro)

Observa que a la función de PyGame pygame.transform.rotate() hay que pasarle dos argumentos; el primero es la imagen y la segunda el ángulo de giro que se quiere aplicar. Ese ángulo es como en matemáticas; positivo en sentido antihorario y negativo en el sentido de las agujas del reloj. El resultado de la función es la imagen girada hasta el ángulo deseado, por lo que pasamos a almacenarla en el atributo image del sprite.No ha acabado aquí todo lo correspondiente al movimiento del coche. Si se trata de una simulación realista, cuando el coche está girado debe desplazarse hacia el lado indicado. Y debe hacerlo mientras esté en movimiento. Ese es el sentido de

if self.giro and scroll:

scroll es una variable de estado booleana que indica si el coche está en marcha (como veremos luego, mientras tengas pulsada la barra espaciadora el coche se moverá). Por otra parte, ya hemos visto que si una variable cualquiera se usa en un if funciona como False cuando tiene un valor nulo y como True en cualquier otro caso. Así que nuestro if se cumplirá cuando se den ambas condiciones a la vez, es decir, el coche esté en movimiento y su ángulo de giro sea distinto de cero (que es lo que queremos).El truco usado para mover el coche en la dirección adecuada es el siguiente:

self.rect.move_ip(-self.giro/10,0)

Para entenderlo debes tener en cuenta que, por ejemplo, para moverse hacia la izquierda, la coordenada horizontal debe disminuir mientras que el ángulo de inclinación del coche será positivo. Lo contrario ocurre en el movimiento hacia la derecha. Por ello, en la conocida función move_ip(), el valor que le damos al desplazamiento de la primera coordenada tiene el signo cambiado respecto al ángulo de giro. ¿Qué valor concreto ponemos? Eso ya depende de lo deprisa que quieras que se desplace. Hemos puesto una cantidad en números redondos dividiendo por 10 el giro (así, por poner un caso, cuando el ángulo de es 45 grados, el coche se desplazará horizontalmente a razón de 4 pixeles por fotograma). Recuerda que no hay nada en contra del método de prueba y error; cambia el valor hasta que encuentres uno que te parezca razonable.El resto es simplemente la comprobación habitual para que el sprite no se salga de los límites de la ventana.

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 3 DE 10 CC: FERNANDO SALAMERO

Page 4: Programación con Pygame VI

Clase PlantaLos obstáculos que van apareciendo en el juego son sprites de tipo Planta. Veamos:

1. Para empezar, para dar un poco de variedad, usamos dos diseños distintos de arbusto. Para que el programa elija al azar entre uno de los dos hemos usado un if asociado a random.randint(0,1). Como ya sabes, esta función devuelve un número al azar, en este caso un 0 o un 1. Según cuál sea de los dos (recuerda que un valor cero es equivalente a un False), se utiliza como imagen del sprite una u otra. Fíjate que conseguir que sigan saliendo al azar pero que haya más de un tipo que de otro, también es fácil. Por ejemplo, si se hubiera elegido un número del 0 al 2, en promedio, habría dos veces más de un tipo que del otro (con el valor 0 tendríamos planta2.png y con los valores 1 y 2 planta1.png). Y, por cierto, como ambas imágenes son de tipo png con trasnparencia, llamamos a la función cargarImagen() con el valor True.El otro detalle de la función __init__() es que usa dos parámetros (a parte del obligatorio self). Eso nos permitirá crear la planta en la posición que queramos ya que luego los usamos para posicionar el rect del sprite con topleft.

2. Como las plantas son parte del paisaje, se han de mover con él. En la función update() miramos si el coche está en marcha (en cuyo caso scroll será True) y desplazamos consecuentemente el sprite en la dirección vertical (hacia abajo) la cantidad indicada por VELOCIDAD (exactamente igual que el fondo).Queda un detalle importante. Una vez que la planta pase de largo y salga por la parte inferior de la pantalla no necesitaremos más el sprite. Para que no gaste recursos del ordenador y no se redibuje una y otra vez fuera de la pantalla, conviene eliminarla con self.kill().

Clase FondoLa clase Fondo representa la imagen de la carretera que se va a ir desplazando hacia abajo para dar la sensación de movimiento. El término técnico inglés que se da a este tipo de imágenes es Tiles (algo así como cuadrículas, en español). La imagen no es de tamaño infinito pero debe parecerlo, así que a medida que se termine hay que ir añadiendo a continuación otra imagen o ella misma, dando así la sensación de que la carretera no se acaba nunca. Veremos luego como implementar esto; de momento vamos a ver cómo está definido este tipo especial de objetos (he usado una clase derivada de un sprite, pero hay otras muchas formas de hacerlo).

1. La función __init__() es la misma que la de la clase Planta, incluso más sencilla puesto que tenemos una sola imagen de carretera (de paso vemos que ampliar la definición para tener varias y crear la sensación de que la carretera es siempre distinta no es muy complicado entonces).

2. Respecto a update() hay que adelantar algo. En el juego vamos a usar dos imágenes de carretera, una a continuación de la otra. Para simular que la carretera no se acaba, a medida que una de ellas sale por completo de la pantalla se cambiará su posición de forma que quede justo detrás de la que viene a continuación (como si fuera un rodillo). Así que en update() miramos primero si hay movimiento con un bloque if y la variable booleana de estado scroll y en tal caso se procede a desplazar el sprite. Primero bajamos el sprite el número de pixeles que indica VELOCIDAD. En segundo lugar hay que ver cuando poner el sprite detrás del segundo que vayamos a tener. Piensa que los dos son iguales y tienen por lo tanto la misma altura. Cuando el borde superior del primero llegue abajo el segundo habrá bajado ya esa cantidad, así que hay

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 4 DE 10 CC: FERNANDO SALAMERO

Page 5: Programación con Pygame VI

que retroceder el sprite su altura menos la altura de la ventana. Así es como deben empalmar:

if self.rect.top >= ALTO: self.rect.bottom = ALTO - self.rect.height

Cuerpo Principal del JuegoSiguiendo el esquema habitual nos encontramos con lo siguiente:

1. Inicializar y cargar sonidos

Poco que comentar aquí. El sonido que cargamos en la variable choque lo emplearemos luego cuando el coche colisione con algún arbusto.

2. Crear los Sprites y los Grupos

Creamos los dos sprites que se encargarán de simular la carretera interminable. El primero, carretera1, lo situamos en pantalla ocupándola por completo. El segundo, carretera2, lo creamos justo encima. Como en la definición de la clase Carretera los parámetros que se usan para posicionar el sprite son los de la esquina superior izquierda, en este segundo objeto hemos de poner de coordenada vertical la altura del propio sprite (en este caso de carretera1, ya que es igual a carretera2 pero este último ¡todavía no está creado!) cambiada de signo:

carretera2 = Carretera(0, -carretera1.rect.height)

A continuación creamos el coche del jugador, coche, y añadimos los sprites a los grupos apropiados; los tiles a fondoGrupo y el coche a cocheGrupo. Creamos, así mismo, un grupo inicialmente vacío pero que contendrá los arbustos que vayan apareciendo en el juego al azar, cosasGrupo.

3. Definición de variables de útiles

Ha tocado el turno a la definición de las variables de juego (muchas veces también llamadas variables de estado). jugando controla si el juego está activo o ha terminado, por lo que lo emplearemos el el bucle while típico de la animación. scroll ya sabemos que indica si el coche está en marcha o no. intervaloPlantas lo usamos de la misma manera que usamos intervaloEnemigos en el juego de Star Wars; vigila cada cuánto hay que crear un nuevo obstáculo. Igual que la otra vez, eso se realizará cuando llegue a un cierto valor y se ponga a cero la variable para volver a empezar. Finalmente, reloj nos permitirá, de la forma habitual, controlar la velocidad de la animación.

4. Bucle del Juego

En el bucle del juego, después de indicar que se va a ejecutar a 60 fotogramas por

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 5 DE 10 CC: FERNANDO SALAMERO

Page 6: Programación con Pygame VI

segundo, entramos directamente el la lista de eventos. Hay dos situaciones en las que nos fijamos aquí. La primera es el momento de terminar la partida, ya sea al cerrar la ventana o al pulsar la tecla ESC. La segunda es determinar cuando hay que poner el coche en marcha o cuando hay que pararlo. La tecla que se encargará de ello es la de la barra espaciadora (con ella pulsada el motor está en funcionamiento y cuando la dejamos de pulsar el motor se detiene). Nada más sencillo; nos fijamos en los eventos KEYDOWN y KEYUP y si la tecla culpable es K_SPACE y asignamos el valor conveniente a la variable scroll.Para mover el coche sin interferir a la barra espaciadora usamos, también como siempre, la función pygame.key.get_pressed(). El diccionario resultante lo almacenamos en teclasPulsadas y miramos si las teclas del cursor están implicadas, en cuyo caso se procede a girar el coche en la dirección adecuada. Recuerda el convenio de signos con los ángulos; al girar hacia la izquierda hay que aumentar el ángulo (por eso sumamos VELOCIDAD al atributo giro del coche) y hay que restar cuando el giro es hacia la derecha.El código que encontramos luego es el mismo que en Star Wars; se incrementa el contador y si llega a 200 se crea una nueva Planta. Fíjate cómo se ha hecho:

cosasGrupo.add( Planta( random.randint(0,ANCHO) , -50 ) )

Al crear el arbusto lo hacemos fuera de la pantalla (50 pixeles por encima) para que aparezca suavemente por el borde superior mientras se mueve. Su coordenada horizontal la generamos al azar en cualquier punto comprendido entre los límites de la ventana.Ya está casi todo hecho. Lo siguiente es llamar a la función update() de todos los grupos para que sitúe a todos los sprites en las nuevas posiciones. Y antes de dibujarlos en pantalla hay que comprobar si hay colisión entre el coche y algún arbusto. El código es casi idéntico que el que usamos en Star Wars. Hay una pequeña diferencia;

pygame.sprite.groupcollide( cocheGrupo, cosasGrupo, 0, 1)

En el programa anterior, cuando un rayo láser impactaba en una nave, queríamos que desaparecieran los dos sprites. Ahora lo que queremos es que desaparezca el arbusto pero no el coche. Es por eso por lo que en los argumentos encargados de eliminar a los sprites implicados en la colisión ponemos un 0 en el correspondiente al grupo del coche (‘no lo elimines’) y un 1 en el grupo de los arbustos (‘elimínalo’).Hecho el trabajo duro, sólo queda usar draw() con todos los grupos para que dibuje los sprites en sus nuevas posiciones y pygame.display.update() para que se vuelque el fotograma completado en pantalla.Una última cosa; ¿te has fijado que esta vez no hemos borrado los sprites de sus antiguas posiciones? En este caso no hace falta por que, al ocupar el tile de la carretera toda la ventana, cuando se dibuja este primer sprite lo hace por encima de todo lo que hubiera antes. Algo que nos ahorramos.

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 6 DE 10 CC: FERNANDO SALAMERO

Page 7: Programación con Pygame VI

# -*- coding: utf-8 -*-#-------------------------------------------------------------------# Clon de Drive# (Basado en un script original de Mark Ivey)#-------------------------------------------------------------------

import randomimport os, pygamefrom pygame.locals import *

ANCHO = 800ALTO = 600VELOCIDAD = 5

#-------------------------------------------------------------------# Función cargarImagen()# ( Carga una imagen desde un archivo, devolviendo el objeto apropiado)#-------------------------------------------------------------------

def cargarImagen( archivo, tieneTransparencia = False ):

lugar = os.path.join( "data", archivo )

try: imagen = pygame.image.load( lugar ) except pygame.error, mensaje: print "No puedo cargar la imagen:", lugar raise SystemExit, mensaje

if tieneTransparencia: imagen = imagen.convert_alpha() else: imagen = imagen.convert() return imagen

#-------------------------------------------------------------------# Función cargarSonido()# ( Carga un sonido desde un archivo, devolviendo el objeto apropiado) #-------------------------------------------------------------------

def cargarSonido( archivo ):

class sinSonido: def play( self ): pass

if not pygame.mixer or not pygame.mixer.get_init(): return sinSonido()

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 7 DE 10 CC: FERNANDO SALAMERO

Page 8: Programación con Pygame VI

lugar = os.path.join( "data", archivo )

try: sound = pygame.mixer.Sound( lugar ) except pygame.error, message: print "No puedo cargar el sonido:", lugar raise SystemExit, message

return sound

#-------------------------------------------------------------------# Sprite Coche# (Coche del Jugador) #-------------------------------------------------------------------

class Coche( pygame.sprite.Sprite ):

def __init__( self ): pygame.sprite.Sprite.__init__( self ) self.imagen_base = cargarImagen( "coche.png", True ) self.image = self.imagen_base self.rect = self.image.get_rect() self.giro = 0 self.rect.center = (550,500)

def update( self ): if self.giro >= 45: self.giro = 45 elif self.giro <= -45: self.giro = -45 else: x, y = self.rect.center self.image = pygame.transform.rotate(self.imagen_base, self.giro) self.rect = self.image.get_rect() self.rect.center = x,y if self.giro and scroll: self.rect.move_ip(-self.giro/10,0) if self.rect.left <= 0: self.rect.left = 0 elif self.rect.right >= ANCHO: self.rect.right = ANCHO #-------------------------------------------------------------------# Sprite Planta# (Palntas de la carretera) #-------------------------------------------------------------------

class Planta( pygame.sprite.Sprite ):

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 8 DE 10 CC: FERNANDO SALAMERO

Page 9: Programación con Pygame VI

def __init__( self, posx, posy ): pygame.sprite.Sprite.__init__( self ) if random.randint(0,1): self.image = cargarImagen( "planta1.png", True ) else: self.image = cargarImagen( "planta2.png", True ) self.rect = self.image.get_rect() self.rect.topleft = (posx,posy) def update( self ): if scroll: self.rect.move_ip( 0,VELOCIDAD ) if self.rect.top > ANCHO: self.kill()

#-------------------------------------------------------------------# Sprite Fondo# (Carretera) #-------------------------------------------------------------------

class Carretera( pygame.sprite.Sprite ):

def __init__( self, posx, posy ): pygame.sprite.Sprite.__init__( self ) self.imagen_base = cargarImagen( "carretera.png", False ) self.image = self.imagen_base self.rect = self.image.get_rect() self.rect.topleft = (posx,posy)

def update( self ): if scroll: self.rect.move_ip( 0,VELOCIDAD ) if self.rect.top >= ALTO: self.rect.bottom = ALTO - self.rect.height

#-------------------------------------------------------------------# Cuerpo Principal del Juego#-------------------------------------------------------------------

pygame.init()visor = pygame.display.set_mode( (ANCHO, ALTO) )pygame.display.set_caption( "Drive" )

choque = cargarSonido( "choque.wav" )

carretera1 = Carretera(0,0)carretera2 = Carretera(0, -carretera1.rect.height)coche = Coche()

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 9 DE 10 CC: FERNANDO SALAMERO

Page 10: Programación con Pygame VI

fondoGrupo = pygame.sprite.RenderUpdates(carretera1)fondoGrupo.add(carretera2)cocheGrupo = pygame.sprite.RenderUpdates(coche)cosasGrupo = pygame.sprite.RenderUpdates()

jugando = Truescroll = FalseintervaloPlantas = 0reloj = pygame.time.Clock()

while jugando: reloj.tick( 60 ) for event in pygame.event.get(): if event.type == QUIT: jugando = False elif event.type == KEYDOWN: if event.key == K_ESCAPE: jugando = False elif event.key == K_SPACE: scroll = True elif event.type == KEYUP: if event.key == K_SPACE: scroll = False teclasPulsadas = pygame.key.get_pressed() if teclasPulsadas[K_LEFT]: coche.giro += VELOCIDAD if teclasPulsadas[K_RIGHT]: coche.giro -= VELOCIDAD intervaloPlantas += 1 if intervaloPlantas >= 100 and scroll: cosasGrupo.add( Planta( random.randint(0,ANCHO) , -50 ) ) intervaloPlantas = 0 fondoGrupo.update() cocheGrupo.update() cosasGrupo.update()

for pum in pygame.sprite.groupcollide( cocheGrupo, cosasGrupo, 0, 1): choque.play()

fondoGrupo.draw( visor ) cocheGrupo.draw( visor ) cosasGrupo.draw( visor )

pygame.display.update()

PROGRAMA: DRIVE CURSO: 1º BACHILLERATO

PÁGINA 10 DE 10 CC: FERNANDO SALAMERO