Programación de juegos en Python – Tutorial 3
Volvemos con la tercera entrega del tutorial para aprender a programar juegos en python, esta vez veremos como crear un «Space Invaders» sencillo pero con muchos sprites en pantalla y sonido, tambien veremos un control más amplio del teclado y nos meteremos más a fondo con temporizadores y las funciones de numeros aleatorios.
Este script lo conseguí hace más de un año en alguna web en ingles (no recuerdo cual) y simplemente lleva algunas modificaciones para utilizar las funciones que ya creamos en el tutorial 2, lo demás, lo he intentado dejar tal y como su autor, Kevin Harris, lo dejó.
También gracias a dios he utilizado sus gráficos, ya que los mios podrián haber dañado la sensibilidad de algún diseñador …
Esta vez creo que lo mejor es descargar el fuente y despues ir comentando el código nuevo que aparece en él, asi que todo el ejemplo lo podeis descargar de aquí: Tutorial 3
1. ORGANIZACIÓN
Lo primero que vemos al descomprimir el tutorial es que hay una carpeta que se llama datos, ahí es donde estan las imágenes y los sonidos del juego, es mejor tenerlo todo organizado, pues cuando la cantidad de gráficos, sonidos, animaciones, músicas, tilesets, … aumente, más difícil será encontrar lo que se busca.
Al abrir el script vemos que importamos una librería nueva, la os, que nos da información sobre el sistema operativo sobre el que se ejecuta el juego (versión, directorio …), esto nos será util para localizar los datos (graficos y sonidos) del juego, más adelante nos servirá para guardar una partida, las opciones o las máximas puntuaciones, por ejemplo.
En la sección de constantes, además de las que nos indican la resolución del juego, tenemos una nueva que es la fuente de los datos. Esto es así por si cambiamos la carpeta de sitio, no tener que modificar todas las lineas de código donde le hagamos referencia.
Los unicos cambios que se han producido en la función de cargar imagenes es que la primera linea es esta:
fullname = os.path.join(DATOS, name)
Que guarda la ruta completa de la imagen ayundandose de la constante DATOS. A partir de ahora en la función se utiliza la variable fullname para llamar a la imagen.
2. MÚSICA, MAESTRO …
La siguiente función es la que se encarga de cargar los sonidos del juego, a continuación comentaremos «linea a linea» lo que hace esta función:
def load_SoundFile(name):
class NoneSound:
def play(self): pass
if not pygame.mixer or not pygame.mixer.get_init():
return NoneSound()
fullname = os.path.join(DATOS, name)
try:
sound = pygame.mixer.Sound(fullname)
except pygame.error, message:
print "No se pudo cargar el sonido:", fullname
raise SystemExit, message
return sound
Veamos, a la función le llega el nombre del archivo de sonido, pero lo primero que hacemos es crear una clase para que si algo falla, el juego siga funcionando y no se nos cuelgue por que no se pudo cargar el sonido de un laser (quizas por que haya 100.000 sonidos a la vez y la máquina donde se ejecuta el juego no tenga tantos canales, o por que no tenga tarjeta de sonido, o que esté mal configurada, por ejemplo). Asi que pasamos de esos sonidos.
Despues comprobamos que se puede ejecutar sonidos, si tiene tarjeta de sonido y está configurada. Si no, usamos la clase de NoneSound y continuamos el juego sin sonido.
Ahora, para organizarnos, pillamos la ruta del sonido y la guardamos en fullname. Esto ya sabemos para que es.
Por último intentamos cargar el sonido, si hubo algún problema es que algo no está en su sitio, y el juego debe pararse. Si todo fue correctamente, devolvemos una variable de sonido.
3. EL MOTOR DEL JUEGO (1ª PARTE)
Nos vamos a la funcion principal (main()), ya veremos las clases de los sprites más adelante, ahora es el momento de inicializar variables y lo primero que se hace es «plantar una semilla».
La semilla es un concepto un tanto dificil de explicar sin entrar en terrenos quizas demasiado técnicos, asi que solamente diremos que es lo que hace todavia más aleatorio los números aleatorios, seleccionando como partida normalmente la fecha y hora actual. Para más informacion, la wikipedia: Números aleatorios
Lo siguiente que nos llama la atención es que a la hora de definir la pantalla (screen), vamos a usar doble-buffered por hardware, con lo cual hará que nuestro juego pueda ir más fluido gracias a la tarjeta gráfica. (Si os dá error en esta linea, simplemente quitar la opción).
Ahora cargamos todos los sonidos, nos fijamos que el nombre de las variable de sonido las acabamos con FX, esto es así para ayudarnos a diferenciarla, así es más comodo recordarlas.
Vemos que hay por ahí una variable que la hacemos global, esto significa que la variable será la misma en todo el código, y no solo en esta función principal. Nos servirá más adelante cuando tengamos que llamar al sonido del laser.
Ahora veremos otra forma de almacenar sprites, esta es más util cuando hay muchos sprites del mismo tipo. Crearemos un contenedor de sprites del tipo RenderClear que nos permitirá no solo dibujar, sino tambien limpiar los sprites que contenga.
La función que utilizamos en el tutorial 2, RenderPlain, solo nos permitia pintar, lo cual puede ser util si, como era el caso, cada vez que «refrescabamos» la pantalla, borrabamos todo. En este ejemplo, con tantos sprites a la vez, no podemos permitirnos esos lujos, asi que tenemos que ir pintando y borrando los sprites dibujados solamente, y no toda la pantalla.
Asi vemos que creamos contenedores para la nave del jugador, sus laseres, las naves enemigas y sus laseres. De camino hemos insertado ya 3 naves enemigas y hemos hecho global otra variable de los laseres enemigos.
Ahora inicializamos las variables del juego, que son las siguientes:
running: Nos indica si se está ejecutando el bucle principal.
addTieFighterCounter: Es la «cuenta atrás» para generar un nuevo enemigo.
clock: Este ya lo conocemos, es el reloj que lleva los frames por segundo.
4. LOS OBJETOS DEL JUEGO
Vamos a ver cómo hemos creado los objetos del juego, para ello vamos a examinar las clases XWing, TIEFighter, XWingLaser y TIEFighterLaser.
Del objeto XWing cabe destacar que tiene dos variables de velocidad que nos permiten movernos en las 8 direcciones básicas y que el movimiento está predeterminado a la mitad inferior de la pantalla.
Los TIEFighter se mueven como la bola del tutorial 2, pero solo en la mitad superior de la pantalla. Estos sprites se inicializan en una posición (startx) dada al constructor (ya vimos este tema en el tutorial 2).
El sistema de disparo es muy sencillo, pillamos un número aleatorio entre 1 y sesenta, y si es uno, disparamos.
El disparo simplemente crea un nuevo objeto de TIEFighterLaser que veremos a continuación (usando la variable global tiefighterLaserSprites como contenedor) y «tocamos» el sonido del laser enemigo (también es una variable global llamada tiefighterShotFX).
Los dos laseres hacen los mismo, uno hacia arriba (jugador) y otro hacia abajo (enemigo). Cabe destacar que si se salen de la pantalla, se eliminan esos laseres, liberandose así recursos para la máquina.
5. EL CONTROL POR TECLADO
Una vez entramos en el bucle principal y vemos que hemos limitado la acción a 60 frames por segundo vemos todas las acciones del teclado.
Esta forma de «capturar» los eventos es diferente de la utilizada en el tutorial 2, entonces usabamos un «detector de evento» y buscabamos cuando se pulsaba una tecla.
En este caso usaremos un array que contiene todos los eventos actuales y buscaremos las teclas que nos interesan.
Sobre cuál de los dos metodos es mejor depende de la diversidad de eventos que busquemos (si miramos teclado solamente, o si miramos algo más como ratón, gamepad o joystick, o mensajes del sistema).
No creo que haga falta comentar mucho las acciones, cuando se pulsa una dirección se pone la velocidad correspondiente a 4, cuando se suelta la tecla, se pone a cero.
Cuando se pulsa el espacio se crea un laser en la posicion superior central de la nave y se «toca» el sonido del laser del jugador.
6. EL MOTOR DEL JUEGO (2ª PARTE)
Para agregar otro enemigo hemos utilizado un temporizador «casero», contamos las veces que se ejecuta el bucle, y cada 200 veces creamos un nuevo enemigo (200 veces a 60 por segundo viene a ser a un enemigo cada 3 y pico segundos).
Es el temporizador más sencillo que hay, en futuros capitulos veremos como usar temporizadores más completos, asincronos, ect …
Para finalizar actualizamos todos los sprites (esto aún no pintará nada en pantalla) mediante la función update que tienen todos los objetos.
Limpiamos los últimos sprites pintados en base al fondo.
Pintamos la nueva situación de todos los sprites.
Por último mostramos todo en pantalla gracias al doble buffer.
Cuando se pulsa escape o se cierra la ventana se sale del bucle principal y salimos del juego automáticamente.
ULTIMAS NOTAS
En este capitulo hemos aprendido a poner sonidos, manejar muchos sprites al mismo tiempo, controlar el juego mediante el teclado y algunas cosillas como aprovechar el hardware para el doble-buffer, los números aleatorios y el ámbito de variables con las variables globales.
En el siguiente tutorial daremos un nuevo paso aprendiendo a crear menus de inicio, incluir animaciones, guardar máximas puntuaciones, leer los primeros tilesets, controlar puntuaciones, vidas …
ESTE JUEGO NO ESTÁ TERMINADO …
Este juego funciona, pero tiene bugs, por ejemplo, los enemigos no pueden alcanzarte …
¿Podrías solucionar esto?
Lo siguiente no es un bug, pero en el código, el autor hizo definiciones de variables globales en medio del código, a pesar de que esto no impide el funcionamiento del juego, queda más feo que pegarle a un padre y haría que cualquier profesor de metodologia de te tirara al cuello.
La solución para no tener que utilizar variables globales es sencilla, pero no trivial …
¿Podrias encontrar la forma de no utilizar variables globales en medio del código?
Pingback: Sukiweb.net » Programando en Python