Programación de juegos en Python – Tutorial 2
Esto es lo que pretendemos conseguir en este segundo tutorial, un pong, aprendiendo por el camino a crear una ventana, colocar imagenes en pantalla, crear sprites, mover los sprites, detectar colisiones y controlar las entradas del ratón.
Como veis, este tutorial será más completo que el anterior, dejandonos la puerta abierta para empezar a hacer nuestros primeros juegos.
NOTA: Sería conveniente que se siga el tutorial escribiendo código a la vez, en lugar de limitarse a ejecutar el ejemplo.
1. IMPORTANDO LIBRERIAS
Tenemos que importar las librerias que usaremos en el juego, para empezar nos bastará con PYGAME.
import pygame
from pygame.locals import *
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480
También definimos dos variables globales (por metodología se suelen poner en mayusculas) que nos indicarán la resolución del area de juego.
2. CONFIGURANDO LA PANTALLA
Empezaremos siempre por crear una función principal y escribiremos el siguiente código:
def main():
pygame.init()
screen = pygame.display.set_mode( (SCREEN_WIDTH,SCREEN_HEIGHT) )
pygame.display.set_caption( "PyPong" )
if __name__ == '__main__': main()
Iniciamos la librería y seteamos el modo de visualización a la resolución seleccionada.
Le ponemos un nombre a la ventana y listo, ya tenemos nuestra ventana funcionando.
* Descargar código escrito hasta ahora: aquí
3. CARGANDO IMAGENES
Cargar una imagen es una instrucción muy sencilla, pero como es una tarea repetitiva que haremos muchas veces, nos crearemos una función para cargar imagenes con algunas opciones extras.
def load_image(name, colorkey = False):
try: image = pygame.image.load(name)
except pygame.error, message:
print 'No se puede cargar la imagen: ', name
raise SystemExit, message
image = image.convert()
if(colorkey):
colorkey = image.get_at((0,0))
image.set_colorkey(colorkey, RLEACCEL)
return image, image.get_rect()
Expliquemos esta función empezando por lo que recibe, que es el nombre de una imagen y si llevará colorkey.
El colorkey es el color de la imagen que se tomará como transparente, es algo realmente util para imagenes con bordes no uniformes o redondeados, como bolas, bombas, ect …
Despues nos encontramos con un try, que es como un «espera, voy a probar esto … y si hay una excepción (algo falla), haz esto otro».
Probamos a cargar la imagen, y si algo falla (no está en el mismo directorio o simplemente no está, o no se tienen permisos, ect …) imprimimos un mensaje en consola y cerramos la aplicación (raise SystemExit, message).
Luego convertimos la imagen al formato interno que usa SDL, esto hará que todas las operaciones que manejen esta imagen vayan más sueltas.
Luego miramos si queremos usar colorkey en esta imagen, si es así, lo cogemos del pixel que esta en la posición 0,0 (arriba a la izquierda), y lo seteamos en la imagen con la constante RLEACCEL, por que no cambiará y esto hará que se cargue más rápido.
Por último devolvemos la imagen y las dimensiones de la imagen con la función get_rect().
4. COLOCAR UN FONDO.
Cambiaremos un poco el código en la función principal para que quede algo asi …
background_image, background_rect = load_image( "fondo.jpg" )
pygame.mouse.set_visible( False )
screen.blit(background_image, (0,0))
pygame.display.flip()
Las dos primeras sentencias son sencillas de entender, la primera carga una imagen y sus dimensiones y la guarda en background_image y background_rec respectivamente (este último por compatibilidad con la función de cargar imagenes, que nos devuelve un segundo parámetro). La segunda sentencia oculta el puntero del ratón.
Luego colocamos el fondo en la posición 0,0 (arriba a la izquierda) y por ultimo mostramos todo en pantalla (esta última orden es la que muestra todo).
*Descargar la imagen de fondo oficial: aquí
*Descargar código fuente realizado hasta ahora: aquí
5. EMPEZANDO CON SPRITES
Es una buena costumbre hacer una clase con cada sprite, de manera que tengamos controlados sus datos, parametros y acciones.
Quien no tenga idea sobre clases y objetos, aconsejo que se lean este articulo de la wikipedia, aunque no utilizaremos propiedades mas avanzadas como la jerarquia o la herencia de clases.
Vamos a empezar creando el objeto bola y haciendo que rebote sobre los limites de la pantalla.
class bola(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image, self.rect = load_image('ball.gif', 0)
self.rect.centerx = SCREEN_WIDTH / 2
self.rect.centery = SCREEN_HEIGHT / 2
self.speed = [2,2]
def update(self):
if self.rect.left < 0 or self.rect.right > SCREEN_WIDTH: self.speed[0] = -self.speed[0]
if self.rect.top < 0 or self.rect.bottom > SCREEN_HEIGHT: self.speed[1] = -self.speed[1]
self.rect.move_ip( (self.speed[0],self.speed[1]) )
La variable self indica la clase actual en la que se está, normalmente se pasan a todas las funciones dentro de una clase.
Despues de definir el nombre y tipo de la clase creamos el constructor (__init__) iniciliazando la clase sprite en pygame, despues cargamos la imagen y las dimensiones que tendrá esa imagen mediante la función que hicimos antes.
Luego seleccionamos como posición inicial el centro de la pantalla, y por último especificamos su velocidad, que esta formado por la cantidad de pixel que avanzará arriba/abajo y derecha/izquierda.
La función update se encargará de hacer avanzar la bola y rebotarla cuando haya llegado a los límites de la pantalla. La función move_ip(x,y) mueve de forma relativa el sprite por pantalla, esto es, subirá o bajará x pixel y avanzará retrocederá y pixel.
Esto explicado puede que haya quedado confuso, pero creo que mirando el código se entiende que es lo que hace cada paso.
6. PONIENDO EL SPRITE EN LA PANTALLA
Una vez creada la clase tenemos que instanciarla en un bucle principal que crearemos a tal efecto, con lo que la función principal quedaría asi:
def main():
pygame.init()
screen = pygame.display.set_mode( (SCREEN_WIDTH,SCREEN_HEIGHT) )
pygame.display.set_caption( "PyPong" )
background_image, background_rect = load_image( "fondo.jpg" )
screen.blit(background_image, (0,0))
pygame.mouse.set_visible( False )
sbola = bola()
gtodos = pygame.sprite.RenderPlain((sbola))
clock = pygame.time.Clock()
while 1:
clock.tick(60)
sbola.update()
screen.blit( background_image, (0,0) )
gtodos.draw(screen)
pygame.display.flip()
Lo primero que hacemos es instanciar la bola y guardar el objeto en la variable sbola, normalmente se suele poner la primer letra del tipo que sea la variable, aunque la nomenclatura de las variables depende del programador, cada cual tiene sus métodos.
Despues viene una acción muy importante, ya que creo un grupo de sprites (renderplain) con TODOS los sprites que se usen en el juego, para despues realizar acciones globales a todos a la vez (como pintar todos en pantalla, actualizar, ect…) .
Por último creo un reloj, ahora veremos para que lo usaremos.
Creamos un bucle infinito, que es donde se desarrollará toda la acción y lo primero que haremos es poner el reloj a un paso de 60, esto se hace para que nunca se pase de 60 frames por segundo, asi no importará si estamos ejecutando esto en un pentium 2 que en el mare nostrum, la velocidad será, como máximo, de 60 frames por segundo.
Movemos la bola llamando a la función del objeto que hace esta acción.
Para acabar colocamos el fondo (ya que lo hemos «despintado» al mover la bola) y por último pintamos todos los sprites en pantalla.
Todo esto lo hemos hecho en un fondo «oculto», ya que al usuario se le estará mostrando solo el fondo. La última instrucción es la que se encarga de volcar el buffer oculto al buffer principal.
Esta técnica es la que se conoce como double-buffering, SDL la realiza por hardware si puede, si no, por software.
Si ejecutamos veremos la pelotita botar por la pantalla, ahora necesitamos las raquetas …
* Descargar imagen oficial de la bola: aquí
* Descargar el código fuente hecho hasta ahora: aquí
7. REACCIONANDO A LOS CONTROLES
Vamos a crear la clase pala, que contendrá el movimiento de la pala, tanto de la del jugador, como la del CPU.
class pala(pygame.sprite.Sprite):
def __init__(self, x):
pygame.sprite.Sprite.__init__(self)
self.image, self.rect = load_image('pala.png', 1)
self.rect.centerx = x
self.rect.centery = SCREEN_HEIGHT / 2
def mover_raton(self):
pos = pygame.mouse.get_pos()
self.rect.centery = pos[1]
if self.rect.bottom >= SCREEN_HEIGHT: self.rect.bottom = SCREEN_HEIGHT
elif self.rect.top < =0: self.rect.top = 0
def mover_cpu(self, objetivo):
self.rect.centery = objetivo.rect.centery
if self.rect.bottom >= SCREEN_HEIGHT: self.rect.bottom = SCREEN_HEIGHT
elif self.rect.top < =0: self.rect.top = 0
Lo unico nuevo en el constructor, es que le pasaremos una variable para colocar la pala en la posición x que nos llegue, por lo demas, todo igual que en la bola.
Despues definimos la función mover_raton que lo que hace es capturar la posición del ratón y colocar el sprite en la posición y (altura) del cursor. Las dos últimas instrucciones es una corrección para que si la pala se "sale" de la pantalla, vuelve al máximo permitido.
La función mover_cpu es la inteligencia artificial de la pala controlada por el ordenador, en este sentido no nos quebraremos mucho la cabeza por ahora y simplemente haremos que "siga" la altura de la bola, que se la pasaremos como parámetro. Luego tenemos la corrección antes explicada.
Con esto ya tenemos definidas las palas, vamos a instanciarlas desde la función principal.
En la zona de inicialización de variables, donde instanciamos a la bola, debemos añadir las instancias a las palas y agrupar todos los sprites:
sbola = bola()
spala1p = pala(20)
spala2p = pala(SCREEN_WIDTH - 20)
gtodos = pygame.sprite.RenderPlain((sbola, spala1p, spala2p))
clock = pygame.time.Clock()
Lo único remarcable es que tenemos que mandar como parámetro la posición x donde dibujar la raqueta.
Ahora, en la función principal tenemos que darle movimiento a las palas:
pygame.event.pump()
spala1p.mover_raton()
spala2p.mover_cpu(sbola)
sbola.update()
La primera sentencia «recarga» los eventos de la libreria (ratón, teclado, ect…) para que esten disponibles y poder mover la pala.
Para ello añadimos las funciones que mueven las palas ANTES que la bola, el porque de añadirlo antes es por jugabilidad, si movemos antes la bola que la pala, podría darse el caso que visualmente estemos bien colocados, pero para el sistema habriamos perdido, quien haya jugado se acordará de aquello de «¡¡ Pero si no me daba … !!».
* Descargar imagen oficial de la pala: aquí
* Descargar código fuente realizado hasta ahora: aquí
8. DETECTANDO COLISIONES
Por último vamos a detectar las colisiones entre la bola y las palas, para ello añadiremos la siguiente función al objeto bola
def colision(self, objetivo):
if self.rect.colliderect(objetivo.rect): self.speed[0] = -self.speed[0]
A la cual le llega como parámetro el objetivo a chequear, en este caso seran las palas, más adelante veremos cómo.
Mediante la sentencia colliderect miramos si se «tocan» las imágenes, si es el caso, cambiamos el sentido horizontal de la bola.
En la función main, dentro del bucle infinito, debemos mirar si la bola ha colisionado ANTES de moverla, mediante las dos siguientes sentencias:
sbola.colision(spala1p)
sbola.colision(spala2p)
Que lo único que hace es comprobar la colisión con las dos raquetas.
Por último, quien haya seguido el ejemplo escribiendolo desde cero, se habrá dado cuenta de que no hay manera de «cerrar» la ventana. Para solucionar eso debemos incluir, despues de mover la bola, las siguientes sentencias:
pygame.event.pump()
keyinput = pygame.key.get_pressed()
if keyinput[K_ESCAPE] or pygame.event.peek(QUIT): raise SystemExit
Asi, si se detecta que ha pulsado escape, se cerrara la ventana.
* Descargar todo el código fuente: aquí
Con esto termina este tutorial, ya tendemos conocimientos suficientes como para hacer juegos sencillos que controlen pocos sprites, en la siguiente entrega aprenderemos a controlas muchos sprites de manera cómoda, le pondremos música a los juegos y veremos los controles desde teclado.
He intentado explicar todo lo mejor posible, aún asi, si hay algo que no se entiende o se quiere profundizar más en algún aspecto, os remito al foro de programación, o a los comentarios a este tutorial. Como siempre estoy abierto a cualquier corrección, mejora o sugerencia.
ESTE JUEGO AUN NO ESTÁ TERMINADO …
Este juego funciona, pero tiene bugs, por ejemplo, puedes atrapar la bola entre la pala y la pared, ¿podrias solucionar esto?
Esto es un poco más comlpicado …
La mecanica del juego es muy simple, ¿podrias mejorar el sistema de rebote de la bola?
PISTA: Podrias utilizar trigonometría para controlar la bola, puedes apoyarte en esta web si tienes dudas.
NOTA: Para más información acerca de las funciones utilizadas, como siempre, podeis hojear la documentación de PYGAME
Para descargar todas las imagenes y códigos utilizados: aquí.
No perdais de vista este ejemplo, pues lo usaremos para probar la librería python-2play y jugar en red en los últimos tutoriales !!!
Pingback: AcenTiLLo
Pingback: Pygame « Mi blog (que original)
Pingback: Tutorial Pygame 3: creando un videojuego (el clasico pong) « Python Mania