Estructura de un juego Pygame
Introducción
En el capítulo anterior presentamos un primer ejemplo sencillo basado en Pygame. Esto nos permitió introducir de manera concreta la noción de bucle de juego, en el que vamos a profundizar en este capítulo.
En primer lugar, estudiaremos la estructura habitual de un videojuego Pygame. De esta manera, veremos cómo podemos inicializar el juego, definir la pantalla y, por tanto, el espacio dedicado al juego. También veremos cómo implementar un bucle de juego y gestionar el tiempo. Finalmente, explicaremos cómo se maneja el proceso de refresco en Pygame. Aprovecharemos para explicar la gestión de colores en código RGB (Red-Green-Blue), así como el sistema de coordenadas que se utiliza. Un sistema de coordenadas es el que nos permite identificar la ubicación precisa de un objeto dentro de la ventana del juego.
El objetivo aquí es adquirir todos los conocimientos básicos necesarios para afrontar correctamente el capítulo Diseño y grafismo en todos sus estados con Pygame.
Inicialización
Como cualquier módulo o librería utilizada en un programa Python, debe importar Pygame:
import pygame
Al principio del programa, se trata de inicializar Pygame escribiendo la siguiente línea:
pygame.init()
Ayuda en la línea de comandos
Por supuesto, cada vez que utilice una función de Pygame, puede intentar obtener más información sobre la sintaxis y los argumentos esperados consultando la ayuda en línea.
Por ejemplo, la documentación relacionada con pygame.init() está disponible en la dirección https://www.pygame.org/docs/ref/pygame.html#pygame.init
Pero también se puede obtener información directamente en el terminal de Python, utilizando el comando help. Por ejemplo, si escribe esto:
help("pygame.init")
Obtenemos esta breve descripción de la función y su uso.
pygame.init = init(...)
init() -> (numpass, numfail)
initialize all imported pygame modules
Visualización de la ventana
La gestión del tiempo viene inmediatamente a la mente cuando se piensa en diseñar un videojuego. Sin embargo, la gestión del espacio es igual de importante.
De hecho, es necesario pensar en el perímetro físico del juego y, por lo tanto, definir muy rápidamente el tamaño de la ventana del juego. De esta manera, gracias a la función set_mode, definimos una ventana de 600 píxeles de ancho por 400 píxeles de alto. También proponemos añadir un título en la ventana, gracias a la función set_caption.
VENTANA = pygame.display.set_mode((600, 400))
pygame.display.set_caption("Mi ventana")
Tenga en cuenta que escribir ((600, 400)) significa que se pasa una tupla como argumento. Volveremos sobre esta noción de tupla más adelante.
¿Qué más puede hacer set_mode? Es suficiente con consultar la ayuda para averiguarlo. Escriba esta instrucción en la línea de comandos:
help("pygame.display.set_mode")
Y obtiene la siguiente información:
pygame.display.set_mode = set_mode(...)
set_mode(size=(0, 0), flags=0, depth=0, display=0) -> Surface
Initialize a window or screen for display
Vemos que además de la tupla size que ya utilizamos, tenemos otros argumentos, como los flags, que son particularmente útiles....
Recordatorios respecto al bucle de juego
Un bucle de juego (game loop) es un bucle infinito que se interrumpirá al cumplir ciertos criterios. La noción está asociada de alguna manera con una especie de reloj interno del juego. De esta manera, en cada iteración del bucle del juego podemos mover a un personaje o tener en cuenta que un objeto ha alcanzado a otro o que se ha cruzado la línea de llegada, lo que quiere decir que la partida ha terminado. Por lo tanto, cada iteración es una oportunidad para actualizar todos los datos relacionados con el estado actual de la partida. Esquemáticamente, en cada iteración se realizan las siguientes tareas:
1. Comprobar que no se alcanzan las condiciones de parada, en cuyo caso se interrumpe el bucle.
2. Actualizar los recursos necesarios para la iteración actual.
3. Obtener las entradas del sistema o de la interacción con el jugador.
4. Actualizar todas las entidades que caracterizan el juego.
5. Refrescar la pantalla.
Superficies Pygame
1. Definición de una superficie
En Pygame la noción de superficie es fundamental, porque la manipulación de este elemento geométrico es un aspecto importante y consecuente del desarrollo de videojuegos. Una superficie es una línea o un polígono que se muestra en la pantalla. Este polígono se puede rellenar de color o no. Apenas hay límites en las dimensiones de una superficie Pygame, ni siquiera límites en el número de superficies que se pueden manipular en el juego. Por lo tanto, se entiende que una parte importante de la gestión gráfica consistirá en crear y manipular superficies de Pygame.
En Pygame, la superficie se corresponde con la visualización de un polígono de color o una línea discontinua o una imagen (como en el ejemplo Cohete y planetas del capítulo Conceptos del videojuego y primeros pasos con Pygame), o la visualización de texto superpuesto. Estos pocos ejemplos son implementaciones de superficies de Pygame.
Una superficie se crea de diferentes maneras, dependiendo del tipo de superficies que desee mostrar:
-
image.load() cuando se trata de una imagen.
-
font.render() cuando se trata de mostrar texto.
-
pygame.Surface() para una superficie que no es nada especial para la creación.
-
pygame.display.set_mode() para la ventana del juego, que también es una superficie (un poco particular).
La página de documentación...
Gestión del color
La gestión del color es un problema relacionado con las superficies cuyo interior algunas veces queremos colorear, pero es específico del desarrollo de Pygame en general y, por extensión, del desarrollo de Python.
Si volvemos a este fragmento de código:
azul = (0, 0, 255)
azul_superficie.fill(azul)
Vemos que un color se define por una tupla de tres cantidades numéricas. Cada una de estas cantidades es un número entero entre 0 y 255, lo que permite 256 valores posibles.
Tenemos 2563 (256 elevado a 3) posibilidades, por lo que en total hay 16 777 216 posibles variaciones de color. Esto es mucho más de lo que el ojo humano puede distinguir.
Este sistema de codificación se llama codificación RGB (Red Green Blue, rojo-verde-azul). De hecho, cada componente de la tupla es, respectivamente, una modificación de rojo, verde y azul.
De esta manera:
(0, 0, 0) se corresponde con el negro.
(255, 255, 255) se corresponde con el blanco.
(255,0, 0) se corresponde con el rojo primario.
(0, 255, 0) se corresponde con el verde primario.
(0, 0, 255) se corresponde con el azul primario.
Hay muchas aplicaciones en línea que permiten elegir la tupla correspondiente al color deseado. Puede ver un ejemplo en la dirección: https://www.rapidtables.com/web/color/RGB_Color.html
La documentación del módulo Color de Pygame está disponible en el capítulo Principales...
Sistema de coordenadas
Consideramos un sistema de coordenadas ortonormal, cuyo origen (0,0) es el punto superior izquierdo de la ventana del juego. El eje horizontal x es el eje orientado de izquierda a derecha, mientras que el eje vertical y está orientado de arriba a abajo.
Modifiquemos un poco el código anterior para mostrar una superficie rectangular nueva, roja esta vez, que permite especificar las diferentes coordenadas involucradas.
import pygame
azul = (0, 0, 255)
rojo = (255, 0, 0)
pygame.init()
pygame.display.set_caption(u'Surface')
ventana = pygame.display.set_mode((400, 400))
azul_superficie = pygame.Surface((400, 400))
azul_superficie.fill(azul)
rojo_superficie = pygame.Surface((120, 240))
rojo_superficie.fill(rojo)
ventana.blit(azul_superficie, (0, 0))
ventana.blit(rojo_superficie, (50, 100))
pygame.display.flip()
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
break
pygame.quit()
No cambiamos nada en la visualización de la ventana, cuyo fondo es siempre azul. Al contrario, añadimos un rectángulo rojo dentro de esta ventana.
El posicionamiento de los puntos superiores izquierdos de los rectángulos, se realiza gracias a la función blit. La función flip provoca el refresco...
Gestión del tiempo y los eventos
1. Gestión del tiempo en Pygame
La gestión del tiempo es la relación con el tiempo, que tiene una gran importancia en el desarrollo de videojuegos y, por lo tanto, obviamente en Pygame.
La documentación del módulo time de Pygame está disponible en el capítulo Principales módulos de Pygame.
Este módulo ofrece varias funciones que permiten cronometrar la sesión actual (desde el init) o pausar la ejecución, por ejemplo. Para hacer esto se utilizan las dos funciones siguientes:
-
pygame.time.get_ticks
-
pygame.time.wait o pygame.time.delay
El módulo pygame.time también incluye un objeto Clock, que permite ir más allá en la gestión del tiempo. La documentación oficial en línea para este objeto Clock está en: https://www.pygame.org/docs/ref/time.html#pygame.time.Clock
Tenga en cuenta que, en términos de tipografía, la nomenclatura de las funciones en Pygame comienza con una letra minúscula. La nomenclatura de los objetos (como Surface o Clock) comienza con una letra mayúscula. Esta es una buena manera de saber, de un solo vistazo, con qué tipo de objeto estamos trabajando.
La función tick de Clock (pygame.time.Clock.tick) permite actualizar el reloj asociado con el juego actual. Se llama cada vez que se actualiza la pantalla del juego y tick permite especificar el número máximo de fotogramas (imágenes) que se muestran por segundo y, por lo tanto, limitar y controlar la velocidad de ejecución del juego.
Por lo tanto...
Los códigos globales de los dos ejemplos
1. Primer ejemplo
El código del primer ejemplo (FIL.py), que se corresponde con la visualización mediante el sistema de coordenadas, es el siguiente.
import pygame
azul = (0, 0, 255)
rojo = (255, 0, 0)
pygame.init()
pygame.display.set_caption(u'Superficie')
ventana = pygame.display.set_mode((400, 400))
azul_superficie = pygame.Surface((400, 400))
azul_superficie.fill(azul)
rojo_superficie = pygame.Surface((120, 240))
rojo_superficie.fill(rojo)
ventana.blit(azul_superficie, (0, 0))
ventana.blit(rojo_superficie, (50, 100))
pygame.display.flip()
while True:
event = pygame.event.wait()
if event.type == pygame.QUIT:
break
pygame.quit()
2. Segundo ejemplo
A continuación, se muestra el código global del "cuadrado que rebota":
import sys
import pygame
rojo = 255, 0, 0
azul = 0, 0, 255
*
pygame.init()
ventana = pygame.display.set_mode((400, 400))
pygame.display.set_caption("El cuadrado que rebota")
clock = pygame.time.Clock()
XX = 300
MOVIMIENTO = 3
while 1:
clock.tick(50)
for event in pygame.event.get(): ...