Archivos mensuales: abril 2016

Eventos en Pygame (1) – Mover un objeto con tu teclado

Si has seguido los dos artículos que han aparecido acerca de Pygame, habrás notado que el programa se articula en torno a un bucle que controla unas variables llamadas genéricamente “eventos”, esto es, cosas que han pasado. Como muchas librerías que proporcionan mecanismos para desarrollar interactividad con el usuario, Pygame proporciona un bucle de eventos, o eventos en un bucle. Esto significa que cada cierto tiempo nos da la oportunidad de saber qué ocurre en los dispositivos de entrada (teclado, ratón, joystick) y nos proporciona esa información en orden, en una estructura de tipo lista ordenada. En general, los eventos se deberían procesar en modo cola: las colas son estructuras de datos en cuya cabecera encontraremos el primer elemento que se ha introducido, y podremos así consultar su contenido en el orden correcto. En Pygame los obtendremos en forma de lista, pudiendo procesarlos en orden, pero también acceder aleatoriamente a los elementos de la lista.

Vamos a ver los tipos de eventos que ofrece Pygame para el teclado y cómo se comportan, reutilizando para ello el programa del artículo anterior.

El bucle de procesado de eventos de Pygame

El bucle de eventos es la pieza central del programa, y en ella podemos comprobar las cosas que han ido ocurriendo y traducirlas en efectos sobre la pantalla. Lo hemos hecho con un fragmento de código que suele tener una estructura como la siguiente

while [condición]:
    for event in pygame.event.get():
        if event.type == [evento]:
            [acción]

Es decir, mientras se cumpla una determinada condición, extraeremos los eventos de la cola y los examinaremos uno a uno, programando acciones en nuestro programa con respecto al tipo (y contenido) del evento. Para eso, obtendremos el contenido de la cola en forma de lista ordenada mediante pygame.event.get()

Visto así, Pygame es una librería orientada a eventos, puesto que la forma de controlar el ritmo del juego que nos propone es precisamente procesar los eventos que se registran entre una pasada y otra del bucle. Pero, la cuestión: ¿cómo de frecuente debe ser el bucle para que el programa funcione de forma fluida, pero sin hacer que la frecuencia del mismo degrade el rendimiento?

En general, desde el punto de vista del programador, no interesa consultar los eventos a más frecuencia que la de refresco en pantalla puesto que la mayoría de periféricos de entrada y de reacciones del usuario son más lentas que la pantalla. En los vídeos de alta resolución, esta frecuencia es de 60 Hz, es decir, 60 cuadros por segundo.

Para realizar este control, Pygame nos ofrece un reloj que permite estructurar el programa en intervalos temporales, de tal forma que podamos esperar a que se cumpla una determinada frecuencia. Lo hemos usado ya, son las líneas resaltadas de este código esquemático:

FPS = [valor] # frecuencia: nº de veces por segundo.
clock = pygame.time.Clock()

while [condición]
    clock.tick(FPS) # pausa hasta el siguiente "tick" de reloj

    for event in pygame.event.get():
        if event.type == [evento]:
            [acción]

clock.tick(FPS)  suspende la ejecución del programa hasta que el tiempo no llega a la siguiente fracción de segundo de la duración que hemos definido (1/FPS segundos).

Procesando teclas: eventos de teclado

Vamos a ver muy brevemente cómo mover el objeto que ya teníamos construido en el artículo anterior. Para ello os presento primero el programa modificado en donde podréis ver dos novedades (resaltadas en el código):

  • He movido la acción de mover el cuadrado por la pantalla a la propia clase Cuadrado , creando un nuevo método en ella, mover_p  a la que le podemos proporcionar la pantalla como parámetro, de forma que se pueda encapsular el borrado y el redibujado de las zonas afectadas por el movimiento.
  • Por otro lado, hay código nuevo en el bucle de eventos, que analizaré a continuación. Puedes ejecutar primero el programa y ver que, ahora, el objeto se puede mover en horizontal con las flechas del cursor. Sin embargo, se mueve “por pasos”, es decir: sólo detecta una pulsación y lo mueve de 20 en 20 píxels.
import pygame

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (255,0,255)
FPS = 60

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)

    def mover_p(self, pantalla, avance_x, avance_y):
        'avance del cuadrado en un cuadro, con refresco de pantalla'
        cuadrado.borra(pantalla)
        rect_inicial = cuadrado.rect.copy()
        cuadrado.mover(avance_x, avance_y)
        rect_final = cuadrado.rect.copy()
        cuadrado.pinta(pantalla)
        pygame.display.update(rect_final.union(rect_inicial))


# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((640,480))
pantalla.fill(NEGRO)

cuadrado = Cuadrado(50,50, 35, BLANCO, NEGRO)
cuadrado2 = Cuadrado(540,50,35, MAGENTA, NEGRO)
cuadrado.pinta(pantalla)
cuadrado2.pinta(pantalla)
pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()

while not cuadrado.colisiona_con(cuadrado2):
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
    
        # Movimiento
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                cuadrado.mover_p(pantalla, -20, 0)

            if event.key == pygame.K_RIGHT:
                cuadrado.mover_p(pantalla, 20, 0)

    
print("Hay solape")
    
pygame.time.delay(3000)
pygame.quit()

Vamos a examinar el bucle que procesa los eventos:

  • Ya sabemos que para obtenerlos podemos invocar a pygame.event.get(), el cual los devuelve en una lista sobra la que podemos iterar con cualquier construcción de tipo bucle.
  • Dentro del bucle, comprobaremos para cada evento su tipo mediante el valor event.type . En nuestro programa controlamos los eventos de tipo pygame.QUIT ,  y pygame.KEYDOWN  (“tecla abajo”).
    • Una vez detectado el tipo de evento que nos interesa, y atendiendo a esto, podemos examinar la información que trae dicho evento como contenedor de datos.
    • En este caso, nos interesa el atributo event.key, que nos dice la tecla pulsada.
    • Para este artículo reaccionaremos ante pygame.K_LEFT  y pygame.K_RIGHT , que se corresponden con las flechas izquierda y derecha del cursor.

El funcionamiento de este programa es “paso a paso”, es decir, Pygame sólo va a proporcionar una pulsación de teclas sin importar el tiempo que mantengamos la tecla pulsada. Así pues, no responde a un movimiento fluido.

Para conseguir un movimiento fluido basta con configurar el módulo de teclado con la sentencia que os he resaltado en la siguiente versión del programa (también he modificado las líneas que ejecutan el movimiento para suavizarlo un poco):

import pygame

BLANCO = (255,255,255)
NEGRO = (0, 0, 0)
MAGENTA = (255,0,255)
FPS = 60

class Cuadrado:
    def __init__(self, x, y, lado, color, fondo):
        self.x = x
        self.y = y
        self.color = color
        self.fondo = fondo
        self.lado = lado
        self.rect = pygame.Rect(self.x,
                                self.y,
                                self.lado,
                                self.lado)

    def __pinta(self, pantalla, color):
        'Realiza el dibujo efectivo'
        pygame.draw.rect(pantalla, color, self.rect, 0)
        
    def pinta(self, pantalla):
        'Pinta el cuadrado con el color propio'
        self.__pinta(pantalla, self.color)

    def borra(self, pantalla):
        'Borra el cuadrado'
        # Realmente lo pinta con el color de fondo
        self.__pinta(pantalla, self.fondo)

    def colisiona_con(self, cuadrado2):
        'Comprueba la colisión con otro cuadrado'
        return self.rect.colliderect(cuadrado2.rect)

    def mover(self, avance_x, avance_y):
        'avance del cuadrado en un cuadro (frame)'
        self.rect.move_ip(avance_x, avance_y)

    def mover_p(self, pantalla, avance_x, avance_y):
        'avance del cuadrado en un cuadro, con refresco de pantalla'
        cuadrado.borra(pantalla)
        rect_inicial = cuadrado.rect.copy()
        cuadrado.mover(avance_x, avance_y)
        rect_final = cuadrado.rect.copy()
        cuadrado.pinta(pantalla)
        pygame.display.update(rect_final.union(rect_inicial))


# inicializamos pygame
pygame.init()

# definición de la pantalla
pantalla = pygame.display.set_mode((640,480))
pantalla.fill(NEGRO)

cuadrado = Cuadrado(50,50, 35, BLANCO, NEGRO)
cuadrado2 = Cuadrado(540,50,35, MAGENTA, NEGRO)
cuadrado.pinta(pantalla)
cuadrado2.pinta(pantalla)
pygame.display.update()

# reloj de control de refresco
clock = pygame.time.Clock()
pygame.key.set_repeat(True)

while not cuadrado.colisiona_con(cuadrado2):
    # pausa hasta el siguiente "tick" de reloj
    clock.tick(FPS)

    # detección de evento QUIT (aspa)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
    
        # Movimiento
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                cuadrado.mover_p(pantalla, -2, 0)

            if event.key == pygame.K_RIGHT:
                cuadrado.mover_p(pantalla, 2, 0)

    
print("Hay solape")
    
pygame.time.delay(3000)
pygame.quit()
    

En el sentido de ampliar esta sencilla técnica con todas sus posibilidades, y ya para terminar este artículo, os dejo en el apartado de referencias una serie de enlaces que tener a mano para dominar los eventos de teclado (están en inglés). Con lo que sabéis ya podéis hacer un pequeño experimento de juego en el que un cuadrado tenga que llegar hasta el otro esquivando objetos que se muevan por la pantalla. ¿Os atrevéis?

No dudéis en dejarme vuestros comentarios en este artículo o a través del formulario de contacto.

Referencias

  • Referencia de los eventos en Pygame: qué tipos de eventos hay tanto de teclado como de otros dispositivos, cuál es su estructura, y cómo gestionarlos o leer su contenido: http://www.pygame.org/docs/ref/event.html
  • Mapa de teclado – teclas que reconoce Pygame: http://www.pygame.org/docs/ref/key.html. En este enlace, además, encontraréis la referencia completa del teclado para poderlo configurar.

Movimiento y colisiones en Pygame

Gran parte de los videojuegos se basan en un funcionamiento básico en el que un objeto interactúa con otro en la pantalla. En esos videojuegos, la gracia está en evitar a otros objetos, o por el contrario en buscar esos otros objetos. Piensa en Pac-Man, o el comecocos de toda la vida: el protagonista debe comer todas las fichas de un laberinto, mientras los fantasmas le persiguen. Las paredes no se pueden atravesar, y si los fantasmas tocan a nuestro héroe, éste muere. Pero sin embargo, si el comecocos se come una ficha especial, la mecánica es la inversa: se puede comer a los fantasmas enviándolos a una especie de cárcel.

De esta forma, tenemos:

  • Varios objetos moviéndose: un comecocos y cuatro fantasmas.
  • Un objeto especial (el comecocos) debe ir colisionando con cuantos objetos inmóviles pueda (fichas), pero no puede atravesar los límites que definen el laberinto (las paredes).
  • Si los fantasmas chocan con el comecocos, en función del estado de este último, ocurren eventos especiales (muerte del comecocos o captura del fantasma).

Todo en este tipo de juegos se basa en dos mecanismos: el movimiento y la detección de colisiones. Vamos a examinar el funcionamiento de estos dos mecanismos con Pygame.

Sigue leyendo Movimiento y colisiones en Pygame

(c) American Micro Devices, Inc. (AMD), 2008

Episodio 21 – Microprocesadores, Microcontroladores y System on a Chip

 

En el episodio de hoy abordo tres conceptos que he dado por supuesto en el resto de los episodios y entre los que hay diferencias que conviene tener claras: microprocesadores, microcontroladores y lo que se ha venido a llamar System on a Chip, o SoC.

Es un capítulo de glosario, en el que hago mucho hincapié en el propósito de cada uno de estos tipos de sistemas programables: sistemas de propósito general en contraposición sistemas embebidos o empotrados, cuyo propósito es mucho más específico.

Espero tus comentarios en http://pitando.net y a través de twitter (https://twitter.com/pitandonet) y Facebook (https://facebook.com/pitandonet)

Foto del episodio: AMD Opteron “Barcelona”, de 4 núcleos, sin el encapsulado.
© American Micro Devices, Inc. (AMD), 2008

Python y programación orientada a objetos

Nuestra próxima incursión en la programación de videojuegos es una oportunidad fantástica para introducir la programación orientada a objetos en nuestra vida, de una forma sencilla y práctica con Python. Usaremos como ejemplos… galletas y bicicletas 🙂

Bowden Spacelander Bicycle - Museo de Brooklyn (licencia CC 3.0 - http://creativecommons.org/licenses/by/3.0)
Bowden Spacelander Bicycle

(Foto: Museo de Brooklyn – licencia CC 3.0)

La programación orientada a objetos es un paradigma (estilo, marco conceptual, filosofía) de programación que trata de organizar los programas modelando objetos, como si objetos del mundo real se tratasen. Las primeras nociones de objetos en un programa informático se dieron en el Instituto Tecnológico de Massachussets en las décadas de los 1950 y 1960. Por aquel entonces, un objeto era algo tan vago como “un elemento identificable con atributos asociados”. Este paradigma se formalizó en Oslo en el Centro de Cálculo Noruego, en el que intentaban simular movimientos navíos y sus cargas entre diferentes puertos usando Simula I. Como encontraban limitaciones en el lenguaje a la hora de plantear un modelo lo suficientemente completo, crearon una versión del lenguaje llamada Simula 67 que ya introducía muchos de los conceptos de lenguajes modernos orientados a objetos. Con este lenguaje ya podían declarar tipos concretos de barcos con propiedades y comportamientos muy detallados y específicos, creando así simulaciones mucho mejores. Anteriormente a este punto, los conceptos de la programación orientada a objetos eran difusos, no estaban explícitos en los lenguajes y eran más bien una forma de organizar el código que un modelo de programación establecido.

En este artículo vamos a ver los conceptos fundamentales de la programación orientada a objetos, y los aplicaremos a Python.

Sigue leyendo Python y programación orientada a objetos

Programa de demostración

Videojuegos en Python: instalar y probar Pygame

Llega abril y con él vuelven los videojuegos a PItando: vamos a empezar la andadura de programar algún juego en Python usando las librerías Pygame.

Pygame es un proyecto que nació en el año 2000 y que permite desarrollar muy rápidamente (si se tiene experiencia) juegos en dos dimensiones. De hecho, hay competiciones de juegos desarrollados con límite de tiempo basadas en el uso de este desarrollo. De todas formas, Python y Pygame no son Scratch, así que trabajar con estos recursos va a requerir soltura con Python y cierta madurez que Scratch no requiere.

Por hoy lo que haremos será instalar el entorno, probar que funciona y examinar minuciosamente un primer ejemplo tomado de la web oficial.

Vamos a empezar, entonces.

Sigue leyendo Videojuegos en Python: instalar y probar Pygame