Bienvenidos un día más al curso de Python. En este capítulo vamos a tratar el tema de la sincronización en los videojuegos completo, y seguiremos complementando el videojuego que estamos realizando con la librería “Pygame”. En el capítulo anterior ya dejamos hecho el esqueleto de nuestro videojuego y os recomendamos recordar cómo se utiliza el paradigma de programación orientado a objetos. Hoy empezaremos con un poco de teoría, y luego la aplicaremos a nuestro videojuego. Así que pongámonos manos a la obra.
Sincronización en los videojuegos
La forma de sincronizar un videojuego es importante ya que nos permite adaptar nuestro juego a los distintos ordenadores donde lo ejecutemos, de manera que, en todos, el resultado sea el mismo. Siempre y cuando el hardware sea capaz de ejecutar el videojuego. Existen dos maneras de sincronización:
Una manera es la sincronización por “Framerate” o “Frames per Second (FPS)”: Este método se centra en la frecuencia con que se ejecuta el ciclo principal de un videojuego en un segundo (mientras más alto, más fluidez).
Es decir, se va obteniendo el tiempo que ha trascurrido desde el inicio del ciclo, se hacen las acciones del juego y cuando pasen los FPS especificados, se actualiza y/o refresca la pantalla. Así se logra una fluidez constante sin importar en que equipo se ejecute.
Este es uno de los métodos más extendidos (especialmente en juegos en 2D). Claro que este método tiene sus ventajas y desventajas:
- Ventaja: ya que limitamos la cantidad máxima de FPS que puede lograr el juego, éste debería verse de la misma forma en cualquier ordenador en donde se ejecute, ya que si el equipo es muy potente sólo funcionará a los FPS especificados (pese a que puede ir más rápido).
- Desventaja: al usar este método en ordenadores más rápidos (que el que utilicemos para su desarrollo) el juego se verá fluido, pero si lo ejecutamos en un ordenador con un procesador mucho más antiguo del que usamos para desarrollarlo, lo más probable es que se vea bastante lento (por esa razón existen los requerimientos mínimos).
El segundo método es la sincronización por tiempo: En este caso se sincroniza en base al tiempo (por lo que no importan los FPS) moviéndose de igual manera los objetos sin importar en que equipo se ejecute el juego (ya que el movimiento depende del tiempo transcurrido).Ya que lo que se hace es calcular la posición de un objeto en función del tiempo transcurrido.
Este método se usa bastante en videojuegos 3D, ya que el “framerate” varía mucho en cada ciclo.
- Ventajas: Los objetos y/o “sprites” se mueven siempre a la misma velocidad, sin importar cuántos FPS se alcancen (ya que su movimiento es en función del tiempo), por lo cual no hay que preocuparse de controlar el “framerate”.
- Desventajas: Pese a que los objetos se mueven siempre a la misma velocidad, en un ordenador más lento el desplazamiento no se verá fluidamente, por ejemplo en caso de que tarde el juego 1 segundo en cada ciclo, cada vez que se deba mover un objeto este se desplazará grandes distancias (ya que el tiempo entre actualizaciones y/o ciclos en donde se refresca la pantalla es grande), produciéndose un salto muy notorio, siendo este un caso muy exagerado.
Si en el primer método (FPS) queríamos mover un objeto 8 pixeles, haríamos lo siguiente:
x = x + 8
En cambio, si lo hacemos en base al tiempo tendríamos:
x = x + (velocidad) * (tiempo)
Es decir, física básica, en donde por ejemplo si se desplaza el objeto a una velocidad de 0.008, y el ciclo demora 1 segundo en ejecutarse (1000ms), el nuevo incremento será de:
x = x + 0.008 * 1000
x = x + 8
Después de esto continuemos con el desarrollo de nuestro videojuego.
Moviendo la pelota (y creando un reloj)
En el juego utilizaremos el método de sincronización de “FPS”. Entonces vamos a crear dentro de la clase Pelota una función de actualización, que se encargará de hacer avanzar la bola y que haga que rebote cuando haya llegado a los límites de la pantalla.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ---------------------------
# Importacion de los módulos
# ---------------------------
import pygame
from pygame.locals import *
import os
import sys
# -----------
# Constantes
# -----------
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480
IMG_DIR = "imagenes"
# ------------------------------
# Clases y Funciones utilizadas
# ------------------------------
def load_image(nombre, dir_imagen, alpha=False):
# Encontramos la ruta completa de la imagen
ruta = os.path.join(dir_imagen, nombre)
try:
image = pygame.image.load(ruta)
except:
print("Error, no se puede cargar la imagen: " + ruta)
sys.exit(1)
# Comprobar si la imagen tiene "canal alpha" (como los png)
if alpha is True:
image = image.convert_alpha()
else:
image = image.convert()
return image
# -----------------------------------------------
# Creamos los sprites (clases) de los objetos del juego:
class Pelota(pygame.sprite.Sprite):
"La bola y su comportamiento en la pantalla"
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = load_image("bola.png", IMG_DIR, alpha=True)
self.rect = self.image.get_rect()
self.rect.centerx = SCREEN_WIDTH / 2
self.rect.centery = SCREEN_HEIGHT / 2
self.speed = [3, 3]
def update(self):
if self.rect.left SCREEN_WIDTH:
self.speed[0] = -self.speed[0]
if self.rect.top SCREEN_HEIGHT:
self.speed[1] = -self.speed[1]
self.rect.move_ip((self.speed[0], self.speed[1]))
# ------------------------------
# Funcion principal del juego
# ------------------------------
def main():
pygame.init()
# creamos la ventana y le indicamos un titulo:
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Pong")
# cargamos los objetos
fondo = load_image("fondo.jpg", IMG_DIR, alpha=False)
bola = Pelota()
clock = pygame.time.Clock()
# el bucle principal del juego
while True:
clock.tick(60)
bola.update()
# actualizamos la pantalla
screen.blit(fondo, (0, 0))
screen.blit(bola.image, bola.rect)
pygame.display.flip()
# Posibles entradas del teclado y mouse
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if __name__ == "__main__":
main()
Vamos a explicar un poco el método que acabamos de crear, los dos “if” que incluye el método sirven para comprobar si la pelota alcanzo los bordes de la pantalla, si esto ocurre que la pelota comenzará a moverse en sentido contrario (por ejemplo, si llegó hasta el borde derecho, la pelota comenzará a moverse a la izquierda, lo mismo para los otros bordes)
La función “move_ip(x,y)” mueve de forma relativa el “sprite” por pantalla, esto es, subirá o bajará “x” pixeles y avanzará o retrocederá “y” pixeles (en este caso utilizara la velocidad que definimos anteriormente para la bola, moviéndola 3 pixeles hacia la derecha y abajo).
Ahora en la función principal del juego tenemos una línea que inicia la clase “bola = Pelota()” y después de esto otra que crea un reloj que controle el tiempo del juego “clock = pygame.time.Clock()”, el cual se ejecuta justo antes de iniciar el bucle principal del juego.
Después ponemos el reloj a un paso de 60 “frames” por segundo para lo que tenemos que hacer los siguiente: “clock.tick(60)”, esto sirve para que nunca se pase de 60 “frames” por segundo, así no importará si estamos ejecutando esto en un “pentium II” o en una “procesador i7”, ya que la velocidad siempre será como máximo de 60 “frames” por segundo.
Finalmente con “bola.update()” actualizamos la posición de la pelota y luego se vuelve a dibuja la pantalla.
La función “Clock.tick” es bastante curiosa, si se usa sin argumentos (o sea clock.tick()) devuelve el tiempo que ha pasado (en milisegundos) desde la última vez que se llamó, es decir funciona como un reloj, pero si se usa con un argumento, que es el “framerate” (por ejemplo “clock.tick(60)”), la función esperará el tiempo necesario para mantener al juego corriendo a la velocidad solicitada, o sea en el ejemplo el juego nunca correrá a más de 60 “frames” por segundo (sirve para controlar el “framerate”).
Aquí lo dejamos por hoy, os recomendamos que reviséis los capítulos de programación orientada a objetos, ya que como habréis podido comprobar vamos a estar utilizando este paradigma de programación.
En el próximo capítulo nos centraremos en la creación de raquetas que golpearán la pantalla. Comentaros que para todos los que se acaban de incorporar indicarles que tenemos un índice con todos los capítulos del curso, ya que nunca es tarde para empezar.