Haciendo Juegos con Python y PyGame

Una charla por Ciro Durán (ciro - punto - duran - arroba - gmail - punto - com), dada en el marco del Flisol 2010, el 24 de abril de 2010.

bienveneido.jpg

Note

Bienvenidos todos a esta charla sobre cómo hacer videojuegos. Esta es una charla razonablemente técnica y requeriré de ustedes al menos que sepan programar o que hayan tenido contacto con algún lenguaje de programación.


title.jpg

Note

En la charla de hoy veremos cómo hacer videojuegos usando el lenguaje de programación Python junto con la librería PyGame. Siendo una charla introductoria, comenzar de una vez hablando de estas tecnologías presume que ustedes conocen cómo hacer un videojuego. Un videojuego, aunque se parece a una aplicación común y corriente, tiene una serie de retos técnicos y artísticos que hay que conocer antes de adentrarnos en el lenguaje de programación.


composicion.jpg

Note

Sería justo entonces comenzar por lo que es un videojuego y cómo se conforma. Por la sanidad de ustedes y la mía, vamos a definir el videojuego como un software que implementa un juego, y un juego es una actividad con jugadores, un objetivo bien definido y reglas establecidas que los jugadores deben cumplir.

Luego, un videojuego consta de dos partes importantes: código y recursos (datos). "Pero, Ciro, así también está formado cualquier programa LOL", y en principio es así, pero en este caso el objetivo de los videojuegos es mucho más sutil: ambas partes están fuertemente integradas y orientadas a ofrecer al jugador una experiencia.

Luego, el código del videojuego es el encargado de controlar el hardware de la máquina (sea PC, Mac, consola o celular), de interpretar la entrada del jugador, y de establecer las reglas del juego. La interpretación de la entrada puede ser de algo tan sencillo como las pulsaciones de un teclado, el movimiento de un mouse, o de cosas más complicadas como el movimiento de un WiiMote, las interacciones de una tableta, o cualquier cosa que se pueda traducir a impulsos electrónicos.

Las reglas del juego es una parte importante en la implementación de un juego que suele ser extremadamente subestimada al inicio. Para mi es tan importante que en mis clases le dedico al menos un tercio del semestre para dedicarnos a estudiar cómo las reglas de un juego entran en contacto entre sí y forman un sistema rico y complejo.

Por otro lado, los recursos del videojuego son los que proveen feedback al jugador: las imágenes que ve, los modelos 3D que habitan en el mundo, los sonidos que produce este mundo, el texto de las conversaciones si lo hay, y los niveles si el juego implementa alguna manera de hacer niveles.


python-1.jpg

Note

Python es un lenguaje de propósito general, de alto nivel, con una sintaxis clara que estimula a los programadores a que el código sea legible. Es un lenguaje que implementa múltiples paradigmas: entre ellos la orientación a objetivos, la programación imperativa y la funcional, y posee un sistema de tipado dinámico y manejo automatizado de memoria. Posee además una librería de utilidades muy completa.

Ok, eso fue una definición básicamente traducida de Wikipedia. Escogí Python para esta charla por la misma razón por la que la doy en mi curso de videojuegos: su sintaxis es clara, y al ser un lenguaje interpretado me permite mostrar didácticamente algunos aspectos de la implementación de videojuegos.


¡Probemos Python!

Note

Como en esta charla estoy asumiendo que los asistentes saben programar, me limitaré a hablar de lo que distingue a Python de otros lenguajes, a mostrar las estructuras básicas que maneja el lenguaje, y un par de módulos de la librería. En la charla esta lámina tiene un intérprete de Python embebido, por lo que todas estas cosas las puedo demostrar interactivamente escribiéndolas directamente.

Lo más notorio a primera vista de Python es la indentación para la delimitación de bloques de código. La delimitación puede ser con espacios o tabs, pero es una práctica común y recomendada hacerla con espacios. Lo segundo más notorio es la ausencia de punto y comas para terminar las instrucciones. En la mayoría de los casos basta con un enter para finalizar una instrucción, salvo cuando se continúa un string que no ha sido finalizado; Python es capaz de leer el salto de línea y continuar el string.

Python usa tipado dinámico (duck typing) con binding tardío para sus variables y funciones. Esto quiere decir que el tipo de una variable o de una función no es comprobado sino solamente hasta que sea usado. También quiere decir que las variables no suelen ser acompañadas de un tipo de objeto. Si dos clases implementan un miembro del mismo nombre, a Python no le importa si se usa uno u otro. En caso de que a una variable ya inicializada se le intente asignar otro tipo o se intente acceder a un miembro no inicializado, Python levantará un error.

Los tipos básico que maneja Python son: strings (str/unicode), listas (list), tuplas (tuple), conjuntos (set/frozenset), diccionarios (dict), enteros (int, enteros grandes (long), decimales representados en coma flotante con doble precisión (float), y booleanos (bool).

La lista es uno de los tipos más útiles de Python. Permite concatenar, acceder a diversas partes de ella usando el operador [], reversar el orden, y demás. Los strings son casos especiales de listas, y se manejan del mismo modo. Las tuplas también se manejan de manera similar, aunque las tuplas son inmutables, mientras que las listas son mutables.

Los diccionarios permite guardar grupos de pares clave, valor. El valor puede ser de cualquier tipo, pero la clave debe ser inmutable.

Las funciones se defininen con la palabra def, seguido del nombre y los parámetros. Las clases se definen con la palabra class, seguido del nombre, y entre paréntesis el nombre de la clase que extiende, o vacío si no extiende a nadie.

El código en Python está dividido principalmente en módulos, representado cada uno por un archivo. Para usar los miembros y funciones de un módulo en otro, uno debe importar el módulo usando la instrucción import.


pygame.jpg

Note

PyGame, por otro lado, es un conjunto de módulos que contienen rutinas de bajo nivel para implementar juegos. Esto quiere decir que PyGame no es una aplicación que "hará el juego por ti". De hecho, con PyGame implementaremos un juegos con prácticamente el mismo nivel técnico que cualquier otra librería profesional, como SDL o Allegro.

PyGame no requiere OpenGL para funcionar, no tiene problemas para usar múltiples cores, tiene buena velocidad de ejecucion ya que sus rutinas importantes están escritas en C optimizado y Assembler, es multi-plataforma, y es simple. Pygame es el lenguaje utilizado en el proyecto OLPC.

Dicho esto, PyGame permite el acceso a la pantalla para dibujar sobre ella, cargar imágenes de formatos populares en memoria, cargar, reproducir y sintetizar sonidos, manejar la interpretación de entrada del teclado, del mouse o de un joystick, manejar el timing del juego, entre otras cosas.


¡Probemos PyGame!

Note

Al igual que hicimos con Python hace un par de láminas, esta lámina posee un intérprete de Python en el que probaremos un poco las capacidades de PyGame creando una pantalla y dibujando un cuadrado sobre ella, muy al estilo del lenguaje Logo.


Estructura de codigo de un juego en PyGame

Note

A continuación viene el código fuente para implementar un juego de Pong entre dos personas, con código comentado. La idea es enseñar cuáles son las estructuras de código que intervienen cuando se hace un juego, incluyendo las rutinas de inicialización, las estructuras de datos del juego, y el loop principal del juego.

# INSTRUCCIONES PARA IMPORTAR MODULOS
import pygame
from pygame.locals import Rect, K_ESCAPE, QUIT, KEYDOWN, KEYUP, K_UP, K_DOWN, K_q, K_a

# ALGUNAS VARIABLES A NIVEL DE MODULO
SCREEN_DIMENSIONS = (800, 600)
screen = None
clock = None

paddle1 = None
paddle2 = None
ball = None

# UNA CONDICICION QUE HACE QUE EL MAIN SE EJECUTE SOLAMENTE
# SI Pong.py ES EL MODULO QUE ESTAMOS EJECUTANDO DIRECTAMENTE
if __name__ == '__main__':
    main()

# LA RUTINA DE INICIO
def main():
    global screen
    global clock
    global SCREEN_DIMENSIONS
    
    # Inicializando todo lo relacionado con Pygame
    pygame.init()
    screen = pygame.display.set_mode( SCREEN_DIMENSIONS )
    clock = pygame.time.Clock()
    
    initData()
    
    mainLoop()

# INICIALIZANDO LAS ESTRUCTURAS DE DATOS
def initData():
    global paddle1
    global paddle2
    global ball
    
    paddle1 = Paddle(30, 300)
    paddle2 = Paddle(770, 300)
    ball = Ball(400, 300, 3, 3)

# EL LOOP PRINCIPAL
def mainLoop():
    global clock
    
    while True:
        renderScreen()
        readInput()
        updateState()
        clock.tick(60)

# LA FUNCION QUE DIBUJA A LA PANTALLA
def renderScreen():
    global screen
    s = screen
    
    s.lock()
    s.fill(0)
    s.fill(0xffffff, Rect(ball.left(), ball.top(), ball.width, ball.height))
    s.fill(0xccccff, Rect(paddle1.left(), paddle1.top(), paddle1.width, paddle1.height))
    s.fill(0xccccff, Rect(paddle2.left(), paddle2.top(), paddle2.width, paddle2.height))
    s.unlock()
    pygame.display.update()

# LECTURA DE LA ENTRADA DEL TECLADO
def readInput():
    global paddle1
    global paddle2
    
    for event in pygame.event.get():
        if event.type == QUIT:
            quit()
        elif event.type == KEYDOWN:
            if event.key == K_ESCAPE:
                quit()
            
            if event.key == K_UP:
                paddle2.joystick.up = True
            elif event.key == K_DOWN:
                paddle2.joystick.down = True
            elif event.key == K_q:
                paddle1.joystick.up = True
            elif event.key == K_a:
                paddle1.joystick.down = True
            
        elif event.type == KEYUP:
            if event.key == K_UP:
                paddle2.joystick.up = False
            elif event.key == K_DOWN:
                paddle2.joystick.down = False
            elif event.key == K_q:
                paddle1.joystick.up = False
            elif event.key == K_a:
                paddle1.joystick.down = False

# ACTUALIZANDO EL ESTADO DEL JUEGO
def updateState():
    global paddle1
    global paddle2
    global ball
    
    if (paddle1.joystick.up):
        paddle1.moveUp()
    elif (paddle1.joystick.down):
        paddle1.moveDown()
        
    if (paddle2.joystick.up):
        paddle2.moveUp()
    elif (paddle2.joystick.down):
        paddle2.moveDown()
        
    ball.update(paddle1, paddle2)

# DEFINIENDO ESTRUCTURAS DE DATOS PARA EL JUEGO

# JOYSTICK GUARDA EL ESTADO DEL TECLADO
class Joystick:
    up = False
    down = False

# LA BOLA CONOCE SU POSICION, DIMENSIONES Y VELOCIDAD
class Ball:
    x = 0
    y = 0
    dx = 0
    dy = 0
    width = 10
    height = 10
    
    def __init__(self, x, y, dx, dy):
        self.x = x
        self.y = y
        self.dx = dx
        self.dy = dy
        
    def top(self):
        return self.y - self.height/2
    
    def left(self):
        return self.x - self.width/2
    
    def update(self, paddle1, paddle2):
        # Actualizo posición
        self.x += self.dx
        self.y += self.dy
        
        # Detección de colisión con las paletas
        if paddle1.collides((self.x, self.y)):
            self.dx = -self.dx
        
        if paddle2.collides((self.x, self.y)):
            self.dx = -self.dx
        
        # Detección de colisión con los bordes de la ventana
        if (self.x < 0):
            self.x = 1
            self.dx = -self.dx
        
        if (self.x > SCREEN_DIMENSIONS[0]):
            self.x = SCREEN_DIMENSIONS[0]-1
            self.dx = -self.dx
        
        if (self.y < 0):
            self.y = 1
            self.dy = -self.dy
        
        if (self.y > SCREEN_DIMENSIONS[1]):
            self.y = SCREEN_DIMENSIONS[1]-1
            self.dy = -self.dy

# LA PALETA CONOCE SU POSICION Y LA VELOCIDAD A LA QUE SE MUEVE
class Paddle:
    x = 0
    y = 0
    dy = 8
    width = 10
    height = 120
    joystick = None
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.joystick = Joystick()
        
    def top(self):
        return self.y - self.height/2
    
    def bottom(self):
        return self.y + self.height/2
    
    def left(self):
        return self.x - self.width/2
    
    def right(self):
        return self.x + self.width/2
    
    def moveUp(self):
        self.y -= self.dy
        self.y = max([self.height/2, self.y])
        
    def moveDown(self):
        self.y += self.dy
        self.y = min([self.y, SCREEN_DIMENSIONS[1]-self.height/2])
    
    def collides(self, point):
        return (point[0] >= self.left() and point[0] <= self.right()) and \
               (point[1] >= self.top() and point[1] <= self.bottom())

ejemplos.jpg

Note

A continuación veremos este código en acción. También veremos el código de un juego que nunca terminé, pero suficientemente desarrollado para mostrar algunas características de Python y de PyGame.

Finalmente veremos dos ejemplos de juegos terminados. Uno desarrollado por los muchachos de Hefesto Games para el Caracas Game Jam 2009, llamado The Virus; y otro que es un proyecto de sofware libre llamado Frets of Fire.

Para descargar el Pong: Script ejecutable en Python

Para descargar The Virus: Página en el Global Game Jam

Para descargar Frets of Fire: Frets of Fire


iniciarse.jpg

Note

Ahora, una cosa es enseñarles una estructura de código para que sepan cómo corre un juego, y otra cómo hacen ustedes para escribir sus propios juegos. Aunque el código que les enseñé ilustra esas partes, cuando uno escribe un juego, uno debe comenzar por las estructuras de datos y sus reglas.

Esas reglas suelen ir escritas en papel como parte de un documento de diseño. No importa que tan informalmente lleven este proceso, es imposible sobreestimar la importancia de llevar nuestras ideas primero al papel para luego llevarlo al código. De no hacer esto, estaríamos subestimando la complicación de llevar las reglas del juego a código.

Una vez que se tiene un modelo muy básico del juego, es que podemos pasar a implementarlo. La mejor manera de implementar las cosas en un juego es iterativamente. Comenzamos con un _núcleo_, algo extremadamente básico con arte del programador, y si vemos que funciona vamos agregando cosas alrededor de este código. Intentar implementarlo todo de una vez sólo causa dolores de cabeza y proyectos abandonados por aburrimiento. Quedas advertido.

Luego de escribir las estructuras de datos, continuaríamos con el loop principal, ver cómo se actualiza el estado del juego en cada tick y çómo hacemos para implementar la lectura de datos.

Sobre este último apartado, en el ejemplo del Pong vemos que usamos una estructura de datos llamada Joystick para mantener el estado del control del jugador. Esta estructura en combinación del loop principal nos permite que el juego responda limpiamente ante las acciones del jugador, ya que si hiciésemos una acción por cada evento el movimiento sería captado de una manera distinta.

Si quisieras escribir un primer juego, yo recomendaría por comenzar con un Pong como éste (tu primera tarea puede ser colocarle un sistema de puntaje), o hacer un Tetris, cuyos requerimientos incorporan todo lo necesario para hacer un juego.


herramientas.jpg

Note

¿Qué herramientas se pueden utilizar aparte del intérprete de Python para desarrollar? Uno en teoría podrá usar un notepad sencillo, pero existen otras herramientas más sofisticadas. CPython, la implementación de referencia del lenguaje, trae un editor llamado IDLE que sirve bien para proyectos pequeños.

Vi e Emacs son dos viejos editores que fácilmente incorporan Python dentro de los lenguajes de programación que soportan.

Eclipse y PyDev es una excelente forma de desarrollar para proyectos de mediana a gran envergadura, debido a su filosofía orientada a proyectos. PyDev introduce a Eclipse la completación de código y un debugger gráfico, por lo que es mi mejor recomendación.

Para descargar Python: http://www.python.org

Para descargar Emacs: http://www.gnu.org/software/emacs/

Para descargar Vi: http://www.vim.org/

Para descargar Eclipse y PyDev: http://www.eclipse.org http://pydev.org


recursos.jpg

Note

Para comenzar a aprender a programar en Python y PyGame, recomiendo un par de libros gratuitos sobre estos asuntos, Dive Into Python e Invent Your Own Computer Games with Python. Quizás estos libros te pongan en la ruta del aprendizaje, aunque si no te gustan, en Internet hay muchos y muy buenos tutoriales de ambos temas.

En materia de sitios, Gamedev.net es un viejo sitio con muchísima información para principiantes y no tan principiantes. Sus foros están llenos de gente dispuesta a ayudarte, si vas en la actitud correcta.

The Independent Gaming Source, o TIGSource, es un gran sitio con una comunidad de gente que hace todo tipo de juegos, cuerdos o locos, y en donde podrás obtener muy buen feedback sobre tu juego. Ellos regularmente organizan competiciones en las que no hay premios, sino un tema con el que la gente hace juegos y se divierte.

También, el propio sitio de PyGame contiene una gran cantidad de juegos y código fuente que puede ser estudiado libremente para tus propios proyectos.

Por último, también recomiendo estos eventos en los que se crean juegos en el menor tiempo posible. Ludum Dare es uno de ellos, el cual ya va por su décimo séptima edición, y está ocurriendo ahora mismo (comenzó el viernes). Ludum Dare permite cualquier plataforma para hacer un juego. PyWeek dura una semana, y el objetivo es hacer juegos con Python. Los Game Jams ocurren a veces por ahí, y Caracas tiene una sede del Game Jam desde 2009. Visiten la página para que vean algunos de los juegos creados allí.

Dive Into Python: http://diveintopython.org

Invent Your Own Computer Games With Python: http://inventwithpython.com

Gamedev.net: http://www.gamedev.net

TIGSource: http://www.tigsource.com

Ludum Dare: http://www.ludumdare.com

PyWeek: http://www.pyweek.org

Caracas Game Jam: http://www.caracasgamejam.com


gracias.jpg

Note

Con esto ya nos estamos acercando al final de esta charla. Solo quiero hacer un par de acotaciones finales.

Cuando en mis clases pregunto lo que es un videojuego por primera vez recibo el silencio como respuesta; silencio de "es obvio, ¿no?". Y en principio la pregunta es obvia: "es un software que implementa un juego, duh". ¿Y qué es un juego?, allí la cosa se pone más complicada. Los seres humanos tenemos siglos y siglos jugando, y en estos tiempos modernos nos han metido en la cabeza la idea del juego electrónico de tal manera que casi nos parece inaudito que haya algún juego no electrónico.

Esta disertación lo único que pretende es prevenir cerrarnos a la idea de videojuegos que incorporen más cosas que una pantalla y un control. Tenemos juegos serios, juegos que se mezclan dentro de la vida cotidiana, juegos en Facebook, juegos que usan controles extraños.

Espero que se hayan divertido con esta charla, y nos veremos en una próxima oportunidad.

Si quieres comunicarte conmigo ingresa en http://www.ciroduran.com, http://www.elchiguireliterario.com, o me puedes seguir en Twitter @chiguire, o escribirme un correo en ciro - punto - duran - arroba - gmail - punto - com.