Esta lección está en elaboración
Esta lección es un tutorial de la biblioteca pygame, en el que se programa el juego clásico Pong.
Aunque desde la aparición de los ordenadores programables en los años 40 del siglo XX se han escrito programas que podían considerarse juegos, se suele considerar que el primer videojuego fue Computer Tennis. Este videojuego fue creado en 1958 para la exposición anual del Laboratorio Nacional de Brookhaven (EEUU) y simulaba un pista de tenis vista lateralmente. El juego se visualizaba en la pantalla de un osciloscopio y se convirtió en la mayor atracción de esa exposición. Aunque al año siguiente la exposición contó con una versión mejorada del juego, el equipo se acabó desmontando para reutilizar los componentes en el Laboratorio.
Bastante años después, en septiembre de 1972, se comercializó la primera videoconsola de la historia dirigida a los hogares, Magnavox Odyssey. Esta videoconsola se conectaba a una pantalla de televisor y uno de los juegos incluidos era Table Tennis. En este juego cada jugador controlaba una paleta que golpeaba una pelota. El mismo año, pero en noviembre, la compañía Atari comercializó Pong, una de las primeras máquinas de arcade, destinadas a lugares públicos.
Con Pong, un juego trivial para los estándares actuales, empezaba la era moderna de los videojuegos. Pong es un juego de deportes en dos dimensiones que simula un tenis de mesa. El jugador controla en el juego una paleta moviéndola verticalmente en la parte izquierda de la pantalla, y puede competir tanto contra un oponente controlado por computadora, como con otro jugador humano que controla una segunda paleta en la parte opuesta. Los jugadores pueden usar las raquetas para pegarle a la pelota hacia un lado u otro. El objetivo consiste en que uno de los jugadores consiga más puntos que el oponente al finalizar el juego. Estos puntos se obtienen cuando el jugador adversario falla al devolver la pelota.
La palabra Pong es una marca registrada por Atari Interactive (aunque la patente del juego la tuvo la empresa de Magnavox Odyssey), pero la palabra genérica "pong" se usa para describir el género de videojuegos.
Por todo ello, cuando se aprende a programar videojuegos, es habitual comenzar programando un juego como Pong, por su sencillez y también como homenaje al gran clásico.
Pygame es un conjunto de módulos de Python que facilitan la creación de videojuegos. Pygame utiliza internamente la biblioteca SDL (Simple DirectMedia Layer), escrita principalmente en C.
Pygame se distribuye bajo la licencia libre LGPL, lo que permite utilizarla tanto en proyecto libres como comerciales.
Además de la Documentación oficial de pygame, en Internet se pueden encontrar sitios y libros dedicados a la introducción a la programación en general, o a la introducción a la programación de videojuegos en particular, mediante pygame, como los siguientes:
Los pasos para instalar pygame como módulo de sistema en Windows son los siguientes:
Vamos a programar un juego de Pong en el que una de las raquetas esté controlada por el jugador humano mediante el teclado y la otra raqueta esté contralada por el propio programa.
Para la realización del juego Pong, iremos por fases, implementando en cada paso uno de los elementos del programa.
El juego se ejecutará en una ventana independiente. Para crear la ventana, necesitamos bastante código específico de pygame, que no es necesario conocer de memoria, ya que lo podemos reutilizar de un proyecto a otro.
El siguiente programa genera una ventana en blanco con el título del juego, similar a la captura siguiente (la ventana está recortada en la captura).
# pong_1_1.py: Ventana de juego
import pygame
from pygame.locals import QUIT
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
def main():
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 1")
# Bucle principal
jugando = True
while jugando:
ventana.fill(BLANCO)
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()
Cada una de las instrucciones tiene una función específica, que se comenta a continuación:
import pygame
from pygame.locals import *
Importamos los módulos pygame. Para que al hacer referencia en el programa a las constantes de pygame no tengamos que incluir el nombre del módulo, las importamos todas del módulo pygame.locals.
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
Python no tiene un tipo de dato "constante" (es decir, un valor que no se pueda modificar). La costumbre, heredada de otros lenguajes, es simplemente escribir el nombre de una variable en mayúsculas para indicar a quien lea nuestro código fuente que esa variable no se va a modificar en ningún momento del programa. Definimos así varias constantes:
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 1")
Con estas instrucciones:
# Bucle principal
jugando = True
while jugando:
ventana.fill(BLANCO)
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
El bucle principal se ejecuta continuamente:
El primer elemento del juego que programaremos será la pelota. Para definir la pelota crearemos una clase a la que llamaremos PelotaPong. Las clases son propias de la programación orientada a objetos y permiten ampliar los tipos de datos del lenguaje. Una vez definida la clase, podemos crear tantas variables de esa clase como queramos (por ejemplo, una o varias pelotas, en el caso de que quisiéramos jugar con varias pelotas a la vez).
El siguiente programa dibuja una pelota cuadrada de color rojo en el centro de la ventana y la desplaza en línea recta en una de las cuatro direcciones diagonales. Cuando llega al borde de la ventana, la pelota deja de verse aunque sigue moviéndose indefinidamente en la misma dirección.
Para situar los objetos en la ventana pygame utiliza un sistema de coordenadas cartesianas en el que el origen se encuentran en la esquina superior izquierda de la ventana y en el que el eje vertical está dirigido hacia abajo. De esta manera, los valores de las coordenadas serán siempre positivos: cuanto mayor sea la coordenada x más a la derecha estará el objeto y cuanto mayor sea la coordenada y más hacia abajo estará el objeto. Si se asignan valores negativos o mayores que el tamaño de la ventana, no se produce ningún error, pero el objeto simplemente no se verá (dependiendo del tamaño que tenga y de los valores utilizados, el objeto puede verse parcialmente).
# pong_1_2.py: Clase PelotaPong
import random
import pygame
from pygame.locals import QUIT
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
class PelotaPong:
def __init__(self, fichero_imagen):
# --- Atributos de la Clase ---
# Imagen de la Pelota
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
# Dimensiones de la Pelota
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Pelota
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Pelota
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
def main():
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 2")
pelota = PelotaPong("bola_roja.png")
# Bucle principal
jugando = True
while jugando:
pelota.mover()
ventana.fill(BLANCO)
ventana.blit(pelota.imagen, (pelota.x, pelota.y))
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()
Las instrucciones añadidas con respecto al paso 1 son las siguientes:
import random
Importamos el módulo random, que utilizaremos en el programa.
class PelotaPong:
def __init__(self, fichero_imagen):
# --- Atributos de la Clase ---
# Imagen de la Pelota
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
# Dimensiones de la Pelota
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Pelota
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Pelota
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
En la definición de una clase se definen los atributos y los métodos de la clase. Los atributos son variables que van asociadas automáticamente a los objetos. Los métodos son funciones que se pueden aplicar a los objetos. En la definición de la clase para hacer referencia a que los atributos son los del propio objeto se indica con self.
def __init__(self, imagen):
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
self.ancho, self.alto = self.imagen.get_size()
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
La imagen siguiente muestra tres posiciones sucesivas de la pelota. La pelota se desplaza cada vez dir_x unidades en horizontal y dir_y unidades en vertical.
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
Una vez definida una clase, en el cuerpo del programa podemos crear objetos (es decir, variables) de esa clase. En este caso, creamos la variable pelota de la clase PelotaPong. Esta variable automáticamente tendrá los atributos definidos en la clase (imagen, tamaño, posición, dirección) y se le podrán aplicar los métodos definidos en la clase (mover()). Al crear la variable le debemos indicar el fichero de imagen a utilizar, en este caso una imagen png.
pelota = PelotaPong("bola_roja.png")
Para que la pelota se mueva y se dibuje en la pantalla, en el bucle principal del programa debemos:
pelota.mover()
ventana.blit(pelota.imagen, (pelota.x, pelota.y))
En este paso mejoraremos el comportamiento de la pelota haciendo que la pelota rebote al chocar con los cuatro lados de la ventana. Para ello, añadiremos un nuevo método a la clase (rebotar()) que utilizaremos en el bucle principal del programa.
# pong_1_3.py: Rebote de la pelota
import random
import pygame
from pygame.locals import QUIT
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
class PelotaPong:
def __init__(self, fichero_imagen):
# --- Atributos de la Clase ---
# Imagen de la Pelota
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
# Dimensiones de la Pelota
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Pelota
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Pelota
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
def rebotar(self):
if self.x <= 0:
self.dir_x = -self.dir_x
if self.x + self.ancho >= VENTANA_HORI:
self.dir_x = -self.dir_x
if self.y <= 0:
self.dir_y = -self.dir_y
if self.y + self.alto >= VENTANA_VERT:
self.dir_y = -self.dir_y
def main():
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 3")
pelota = PelotaPong("bola_roja.png")
# Bucle principal
jugando = True
while jugando:
pelota.mover()
pelota.rebotar()
ventana.fill(BLANCO)
ventana.blit(pelota.imagen, (pelota.x, pelota.y))
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()
Las instrucciones añadidas con respecto al paso 2 son las siguientes:
Para conseguir que la pelota rebote contra las paredes, basta con detectar cuando la pelota llega al borde de la ventana y en ese momento cambiar la dirección del movimiento.
def rebotar(self):
if self.x <= 0:
self.dir_x = -self.dir_x
if self.x + self.ancho >= VENTANA_HORI:
self.dir_x = -self.dir_x
if self.y <= 0:
self.dir_y = -self.dir_y
if self.y + self.alto >= VENTANA_VERT:
self.dir_y = -self.dir_y
Para que el programa compruebe si se debe producir un rebote basta con llamar al método en el bucle principal del programa:
pelota.rebotar()
Realmente, en el juego de Pong la pelota no debe rebotar en todos los lados, como hacía en el programa anterior. Únicamente debe rebotar arriba y abajo. Si la pelota llega a alguno de los lados, significará que uno de los jugadores no ha devuelto la pelota. En ese caso, el jugador habrá perdido un punto y la pelota debe volver a lanzarse desde el centro (en dirección al jugador que ha ganado el punto, por ejemplo).
En este paso, modificaremos el método rebotar() y añadiremos un método reiniciar() para conseguir el comportamiento comentado en el párrafo anterior.
# pong_1_4.py: Reiniciar pelota al salir por los lados
import random
import pygame
from pygame.locals import QUIT
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
class PelotaPong:
def __init__(self, fichero_imagen):
# --- Atributos de la Clase ---
# Imagen de la Pelota
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
# Dimensiones de la Pelota
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Pelota
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Pelota
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
def rebotar(self):
if self.x <= -self.ancho:
self.reiniciar()
if self.x >= VENTANA_HORI:
self.reiniciar()
if self.y <= 0:
self.dir_y = -self.dir_y
if self.y + self.alto >= VENTANA_VERT:
self.dir_y = -self.dir_y
def reiniciar(self):
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
self.dir_x = -self.dir_x
self.dir_y = random.choice([-5, 5])
def main():
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 4")
pelota = PelotaPong("bola_roja.png")
# Bucle principal
jugando = True
while jugando:
pelota.mover()
pelota.rebotar()
ventana.fill(BLANCO)
ventana.blit(pelota.imagen, (pelota.x, pelota.y))
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()
Las instrucciones añadidas con respecto al paso 3 son las siguientes:
Cuando la pelota se salga por los lados izquierdo y derecho, debe volver al centro, moviéndose en dirección contraria a la que tenía antes. para ello creamos un método reiniciar() que modifica los atributos de posición y dirección de movimiento.
def reiniciar(self):
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
self.dir_x = -self.dir_x
self.dir_y = random.choice([-5, 5])
Modificamos el método rebotar(), de manera que cuando detecte que la pelota ha salido por los lados derecho e izquierdo, llame al método reiniciar().
def rebotar(self):
if self.x <= -self.ancho:
self.reiniciar()
if self.x >= VENTANA_HORI:
self.reiniciar()
if self.y <= 0:
self.dir_y = -self.dir_y
if self.y + self.alto >= VENTANA_VERT:
self.dir_y = -self.dir_y
En este paso añadiremos las raquetas. Para ello definiremos una clase RaquetaPong, similar a PelotaPong y crearemos dos variables de esa clase.
# pong_1_5.py: Clase RaquetaPong
import random
import pygame
from pygame.locals import QUIT
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
class PelotaPong:
def __init__(self, fichero_imagen):
# --- Atributos de la Clase ---
# Imagen de la Pelota
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
# Dimensiones de la Pelota
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Pelota
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Pelota
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
def rebotar(self):
if self.x <= -self.ancho:
self.reiniciar()
if self.x >= VENTANA_HORI:
self.reiniciar()
if self.y <= 0:
self.dir_y = -self.dir_y
if self.y + self.alto >= VENTANA_VERT:
self.dir_y = -self.dir_y
def reiniciar(self):
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
self.dir_x = -self.dir_x
self.dir_y = random.choice([-5, 5])
class RaquetaPong:
def __init__(self):
self.imagen = pygame.image.load("raqueta.png").convert_alpha()
# --- Atributos de la Clase ---
# Dimensiones de la Raqueta
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Raqueta
self.x = 0
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Raqueta
self.dir_y = 0
def mover(self):
self.y += self.dir_y
def main():
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 5")
pelota = PelotaPong("bola_roja.png")
raqueta_1 = RaquetaPong()
raqueta_1.x = 60
raqueta_2 = RaquetaPong()
raqueta_2.x = VENTANA_HORI - 60 - raqueta_2.ancho
# Bucle principal
jugando = True
while jugando:
pelota.mover()
pelota.rebotar()
ventana.fill(BLANCO)
ventana.blit(pelota.imagen, (pelota.x, pelota.y))
ventana.blit(raqueta_1.imagen, (raqueta_1.x, raqueta_1.y))
ventana.blit(raqueta_2.imagen, (raqueta_2.x, raqueta_2.y))
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()
Las instrucciones añadidas con respecto al paso 4 son las siguientes:
class RaquetaPong:
def __init__(self):
self.imagen = pygame.image.load("raqueta.png").convert_alpha()
# --- Atributos de la Clase ---
# Dimensiones de la Raqueta
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Raqueta
self.x = 0
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Raqueta
self.dir_y = 0
def mover(self):
self.y += self.dir_y
La clase RaquetaPong es muy similar a la clase PelotaPong.
def __init__(self):
self.imagen = pygame.image.load("raqueta.png").convert_alpha()
self.ancho, self.alto = self.imagen.get_size()
self.x = 0
self.y = VENTANA_VERT / 2 - self.alto / 2
self.dir_y = 0
def mover(self):
self.y += self.dir_y
Una vez definida la clase, en el cuerpo del programa podemos crear objetos (es decir, variables) de esa clase. En este caso, creamos dos variables raqueta_1 y raqueta_2 de la clase RaquetaPong y modificamos sus atributos x para situarlas en posiciones distintas. Concretamente, las situamos a 60px de los lados izquierdo y derecho.
raqueta_1 = RaquetaPong()
raqueta_1.x = 60
raqueta_2 = RaquetaPong()
raqueta_2.x = VENTANA_HORI - 60 - raqueta_2.ancho
Nota: La posición x podría ser un argumento del método __init__() para no tener que modificar el atributo nada más crear el objeto. En el caso de la primera raqueta, efectivamente podríamos hacerlo así, pero el problema es que la posición de la segunda raqueta depende del ancho de la imagen de la raqueta, que el programa no conoce antes de crear el objeto. Por eso creamos primero el objeto, para poder utilizar su tamaño en la fórmula de la posición. Es verdad que en este caso, como las dos raquetas utilizan la misma imagen podríamos utilizar el ancho de la primera raqueta, pero el problema aparecería cuando quisiéramos utilizar imágenes de raquetas distintas, como se pide en uno de los ejercicios propuestos.
Para ello recurrimos al método bit() del objeto ventana, indicando la imagen que queremos dibujar y su posición en la ventana:
ventana.blit(raqueta_1.imagen, (raqueta_1.x, raqueta_1.y))
ventana.blit(raqueta_2.imagen, (raqueta_2.x, raqueta_2.y))
En este paso, añadiremos el movimiento de la raqueta del jugador humano.
El jugador humano va a controlar su raqueta (por ejemplo, la raqueta raqueta_1) mediante el teclado, concretamente la raqueta subirá al pulsar la tecla "w" y la raqueta "bajará" al pulsar la tecla "s". Para ello, no es necesario modificar la clase, pero en el bucle principal del programa tenemos que detectar la pulsación (y liberación) de las teclas y modificiar el atributo de dirección del movimiento.
# pong_1_6.py: Mover raqueta con teclado
import random
import pygame
from pygame.locals import QUIT
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
class PelotaPong:
def __init__(self, fichero_imagen):
# --- Atributos de la Clase ---
# Imagen de la Pelota
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
# Dimensiones de la Pelota
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Pelota
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Pelota
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
def rebotar(self):
if self.x <= -self.ancho:
self.reiniciar()
if self.x >= VENTANA_HORI:
self.reiniciar()
if self.y <= 0:
self.dir_y = -self.dir_y
if self.y + self.alto >= VENTANA_VERT:
self.dir_y = -self.dir_y
def reiniciar(self):
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
self.dir_x = -self.dir_x
self.dir_y = random.choice([-5, 5])
class RaquetaPong:
def __init__(self):
self.imagen = pygame.image.load("raqueta.png").convert_alpha()
# --- Atributos de la Clase ---
# Dimensiones de la Raqueta
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Raqueta
self.x = 0
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Raqueta
self.dir_y = 0
def mover(self):
self.y += self.dir_y
if self.y <= 0:
self.y = 0
if self.y + self.alto >= VENTANA_VERT:
self.y = VENTANA_VERT - self.alto
def main():
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 6")
pelota = PelotaPong("bola_roja.png")
raqueta_1 = RaquetaPong()
raqueta_1.x = 60
raqueta_2 = RaquetaPong()
raqueta_2.x = VENTANA_HORI - 60 - raqueta_2.ancho
# Bucle principal
jugando = True
while jugando:
pelota.mover()
pelota.rebotar()
raqueta_1.mover()
ventana.fill(BLANCO)
ventana.blit(pelota.imagen, (pelota.x, pelota.y))
ventana.blit(raqueta_1.imagen, (raqueta_1.x, raqueta_1.y))
ventana.blit(raqueta_2.imagen, (raqueta_2.x, raqueta_2.y))
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
# Detecta que se ha pulsado una tecla
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
raqueta_1.dir_y = -5
if event.key == pygame.K_s:
raqueta_1.dir_y = 5
# Detecta que se ha soltado la tecla
if event.type == pygame.KEYUP:
if event.key == pygame.K_w:
raqueta_1.dir_y = 0
if event.key == pygame.K_s:
raqueta_1.dir_y = 0
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()
Las instrucciones añadidas con respecto al paso 5 son las siguientes:
raqueta_1.mover()
def mover(self):
self.y += self.dir_y
if self.y <= 0:
self.y = 0
if self.y + self.alto >= VENTANA_VERT:
self.y = VENTANA_VERT - self.alto
Para pygame, las pulsaciones de teclado son eventos. Cuando se pulsa una tecla, pygame crea un evento pygame.KEYDOWN y guarda la tecla pulsada en event.key. Cuando se libera una tecla, pygame crea un evento pygame.KEYUP y guarda la tecla pulsada en event.key. Por ello, en el bucle for que recorre los eventos detectados añadiremos la comprobación de los eventos y la modificación del atributo dir_y de dirección del movimiento.
# Detecta que se ha pulsado una tecla
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
raqueta_1.dir_y = -5
if event.key == pygame.K_s:
raqueta_1.dir_y = 5
# Detecta que se ha soltado la tecla
if event.type == pygame.KEYUP:
if event.key == pygame.K_w:
raqueta_1.dir_y = 0
if event.key == pygame.K_s:
raqueta_1.dir_y = 0
En este paso, añadiremos el golpe de la raqueta del jugador humano a la pelota.
Para ello, crearemos el método golpear_raqueta() en la clase RaquetaPong y en el bucle principal llamaremos al método.
# pong_1_7.py: Golpe raqueta jugador humano
import random
import pygame
from pygame.locals import QUIT
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
class PelotaPong:
def __init__(self, fichero_imagen):
# --- Atributos de la Clase ---
# Imagen de la Pelota
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
# Dimensiones de la Pelota
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Pelota
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Pelota
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
def rebotar(self):
if self.x <= -self.ancho:
self.reiniciar()
if self.x >= VENTANA_HORI:
self.reiniciar()
if self.y <= 0:
self.dir_y = -self.dir_y
if self.y + self.alto >= VENTANA_VERT:
self.dir_y = -self.dir_y
def reiniciar(self):
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
self.dir_x = -self.dir_x
self.dir_y = random.choice([-5, 5])
class RaquetaPong:
def __init__(self):
self.imagen = pygame.image.load("raqueta.png").convert_alpha()
# --- Atributos de la Clase ---
# Dimensiones de la Raqueta
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Raqueta
self.x = 0
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Raqueta
self.dir_y = 0
def mover(self):
self.y += self.dir_y
if self.y <= 0:
self.y = 0
if self.y + self.alto >= VENTANA_VERT:
self.y = VENTANA_VERT - self.alto
def golpear(self, pelota):
if (
pelota.x < self.x + self.ancho
and pelota.x > self.x
and pelota.y + pelota.alto > self.y
and pelota.y < self.y + self.alto
):
pelota.dir_x = -pelota.dir_x
pelota.x = self.x + self.ancho
def main():
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 7")
pelota = PelotaPong("bola_roja.png")
raqueta_1 = RaquetaPong()
raqueta_1.x = 60
raqueta_2 = RaquetaPong()
raqueta_2.x = VENTANA_HORI - 60 - raqueta_2.ancho
# Bucle principal
jugando = True
while jugando:
pelota.mover()
pelota.rebotar()
raqueta_1.mover()
raqueta_1.golpear(pelota)
ventana.fill(BLANCO)
ventana.blit(pelota.imagen, (pelota.x, pelota.y))
ventana.blit(raqueta_1.imagen, (raqueta_1.x, raqueta_1.y))
ventana.blit(raqueta_2.imagen, (raqueta_2.x, raqueta_2.y))
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
# Detecta que se ha pulsado una tecla
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
raqueta_1.dir_y = -5
if event.key == pygame.K_s:
raqueta_1.dir_y = 5
# Detecta que se ha soltado la tecla
if event.type == pygame.KEYUP:
if event.key == pygame.K_w:
raqueta_1.dir_y = 0
if event.key == pygame.K_s:
raqueta_1.dir_y = 0
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()
Las instrucciones añadidas con respecto al paso 6 son las siguientes:
Este golpe es quizás el punto más complicado del programa.
def golpear(self, pelota):
if (
pelota.x < self.x + self.ancho
and pelota.x > self.x
and pelota.y + pelota.alto > self.y
and pelota.y < self.y + self.alto
):
pelota.dir_x = -pelota.dir_x
pelota.x = self.x + self.ancho
Añadimos en el bucle principal del programa la llamada al método golpear() de la raqueta del jugador humano.
raqueta_1.golpear(pelota)
En este paso, añadiremos el control de la raqueta del jugador controlado por el propio programa.
Para ello, crearemos los método mover_raqueta_ia() y golpear_raqueta_ia() en la clase RaquetaPong y en el bucle principal llamaremos a estos métodos.
# pong_1_8.py: Jugador controlado por el programa (IA)
import random
import pygame
from pygame.locals import QUIT
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
class PelotaPong:
def __init__(self, fichero_imagen):
# --- Atributos de la Clase ---
# Imagen de la Pelota
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
# Dimensiones de la Pelota
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Pelota
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Pelota
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
def rebotar(self):
if self.x <= -self.ancho:
self.reiniciar()
if self.x >= VENTANA_HORI:
self.reiniciar()
if self.y <= 0:
self.dir_y = -self.dir_y
if self.y + self.alto >= VENTANA_VERT:
self.dir_y = -self.dir_y
def reiniciar(self):
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
self.dir_x = -self.dir_x
self.dir_y = random.choice([-5, 5])
class RaquetaPong:
def __init__(self):
self.imagen = pygame.image.load("raqueta.png").convert_alpha()
# --- Atributos de la Clase ---
# Dimensiones de la Raqueta
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Raqueta
self.x = 0
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Raqueta
self.dir_y = 0
def mover(self):
self.y += self.dir_y
if self.y <= 0:
self.y = 0
if self.y + self.alto >= VENTANA_VERT:
self.y = VENTANA_VERT - self.alto
def mover_ia(self, pelota):
if self.y > pelota.y:
self.dir_y = -3
elif self.y < pelota.y:
self.dir_y = 3
else:
self.dir_y = 0
self.y += self.dir_y
def golpear(self, pelota):
if (
pelota.x < self.x + self.ancho
and pelota.x > self.x
and pelota.y + pelota.alto > self.y
and pelota.y < self.y + self.alto
):
pelota.dir_x = -pelota.dir_x
pelota.x = self.x + self.ancho
def golpear_ia(self, pelota):
if (
pelota.x + pelota.ancho > self.x
and pelota.x < self.x + self.ancho
and pelota.y + pelota.alto > self.y
and pelota.y < self.y + self.alto
):
pelota.dir_x = -pelota.dir_x
pelota.x = self.x - pelota.ancho
def main():
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 8")
pelota = PelotaPong("bola_roja.png")
raqueta_1 = RaquetaPong()
raqueta_1.x = 60
raqueta_2 = RaquetaPong()
raqueta_2.x = VENTANA_HORI - 60 - raqueta_2.ancho
# Bucle principal
jugando = True
while jugando:
pelota.mover()
pelota.rebotar()
raqueta_1.mover()
raqueta_2.mover_ia(pelota)
raqueta_1.golpear(pelota)
raqueta_2.golpear_ia(pelota)
ventana.fill(BLANCO)
ventana.blit(pelota.imagen, (pelota.x, pelota.y))
ventana.blit(raqueta_1.imagen, (raqueta_1.x, raqueta_1.y))
ventana.blit(raqueta_2.imagen, (raqueta_2.x, raqueta_2.y))
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
# Detecta que se ha pulsado una tecla
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
raqueta_1.dir_y = -5
if event.key == pygame.K_s:
raqueta_1.dir_y = 5
# Detecta que se ha soltado la tecla
if event.type == pygame.KEYUP:
if event.key == pygame.K_w:
raqueta_1.dir_y = 0
if event.key == pygame.K_s:
raqueta_1.dir_y = 0
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()
Las instrucciones añadidas con respecto al paso 7 son las siguientes:
El ordenador seguirá la siguiente estrategia para mover la raqueta.
def mover_ia(self, pelota):
if self.y > pelota.y:
self.dir_y = -3
elif self.y < pelota.y:
self.dir_y = 3
else:
self.dir_y = 0
self.y += self.dir_y
Este método es como el método golpear() del jugador humano, pero teniendo en cuenta que la pelota se acerca por el lado izquierdo de la raqueta en vez de por el derecho.
def golpear_ia(self, pelota):
if (
pelota.x + pelota.ancho > self.x
and pelota.x < self.x + self.ancho
and pelota.y + pelota.alto > self.y
and pelota.y < self.y + self.alto
):
pelota.dir_x = -pelota.dir_x
pelota.x = self.x - pelota.ancho
Añadimos en el bucle principal del programa las llamadas a los métodos mover_ia() y golpear_ia() de la raqueta del jugador controlado por el propio programa (líneas 129 y 131).
raqueta_1.mover()
raqueta_2.mover_ia(pelota)
raqueta_1.golpear(pelota)
raqueta_2.golpear_ia(pelota)
En este útlimo paso, añadiremos la puntuación de la partida.
Para ello, crearemos unos contadores que se actualizarán al ganar uno de los dos jugadores y que se mostrarán en la ventana.
# pong_1_9.py: Mostrar puntuación de la partida
import random
import pygame
from pygame.locals import QUIT
# Constantes para la inicialización de la superficie de dibujo
VENTANA_HORI = 800 # Ancho de la ventana
VENTANA_VERT = 600 # Alto de la ventana
FPS = 60 # Fotogramas por segundo
BLANCO = (255, 255, 255) # Color del fondo de la ventana (RGB)
NEGRO = (0, 0, 0) # Color del texto (RGB)
class PelotaPong:
def __init__(self, fichero_imagen):
# --- Atributos de la Clase ---
# Imagen de la Pelota
self.imagen = pygame.image.load(fichero_imagen).convert_alpha()
# Dimensiones de la Pelota
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Pelota
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Pelota
self.dir_x = random.choice([-5, 5])
self.dir_y = random.choice([-5, 5])
# Puntuación de la pelota
self.puntuacion = 0
self.puntuacion_ia = 0
def mover(self):
self.x += self.dir_x
self.y += self.dir_y
def rebotar(self):
if self.x <= -self.ancho:
self.reiniciar()
self.puntuacion_ia += 1
if self.x >= VENTANA_HORI:
self.reiniciar()
self.puntuacion += 1
if self.y <= 0:
self.dir_y = -self.dir_y
if self.y + self.alto >= VENTANA_VERT:
self.dir_y = -self.dir_y
def reiniciar(self):
self.x = VENTANA_HORI / 2 - self.ancho / 2
self.y = VENTANA_VERT / 2 - self.alto / 2
self.dir_x = -self.dir_x
self.dir_y = random.choice([-5, 5])
class RaquetaPong:
def __init__(self):
self.imagen = pygame.image.load("raqueta.png").convert_alpha()
# --- Atributos de la Clase ---
# Dimensiones de la Raqueta
self.ancho, self.alto = self.imagen.get_size()
# Posición de la Raqueta
self.x = 0
self.y = VENTANA_VERT / 2 - self.alto / 2
# Dirección de movimiento de la Raqueta
self.dir_y = 0
def mover(self):
self.y += self.dir_y
if self.y <= 0:
self.y = 0
if self.y + self.alto >= VENTANA_VERT:
self.y = VENTANA_VERT - self.alto
def mover_ia(self, pelota):
if self.y > pelota.y:
self.dir_y = -3
elif self.y < pelota.y:
self.dir_y = 3
else:
self.dir_y = 0
self.y += self.dir_y
def golpear(self, pelota):
if (
pelota.x < self.x + self.ancho
and pelota.x > self.x
and pelota.y + pelota.alto > self.y
and pelota.y < self.y + self.alto
):
pelota.dir_x = -pelota.dir_x
pelota.x = self.x + self.ancho
def golpear_ia(self, pelota):
if (
pelota.x + pelota.ancho > self.x
and pelota.x < self.x + self.ancho
and pelota.y + pelota.alto > self.y
and pelota.y < self.y + self.alto
):
pelota.dir_x = -pelota.dir_x
pelota.x = self.x - pelota.ancho
def main():
# Inicialización de Pygame
pygame.init()
# Inicialización de la superficie de dibujo (display surface)
ventana = pygame.display.set_mode((VENTANA_HORI, VENTANA_VERT))
pygame.display.set_caption("Pong 9")
# Inicialización de la fuente
fuente = pygame.font.Font(None, 60)
pelota = PelotaPong("bola_roja.png")
raqueta_1 = RaquetaPong()
raqueta_1.x = 60
raqueta_2 = RaquetaPong()
raqueta_2.x = VENTANA_HORI - 60 - raqueta_2.ancho
# Bucle principal
jugando = True
while jugando:
pelota.mover()
pelota.rebotar()
raqueta_1.mover()
raqueta_2.mover_ia(pelota)
raqueta_1.golpear(pelota)
raqueta_2.golpear_ia(pelota)
ventana.fill(BLANCO)
ventana.blit(pelota.imagen, (pelota.x, pelota.y))
ventana.blit(raqueta_1.imagen, (raqueta_1.x, raqueta_1.y))
ventana.blit(raqueta_2.imagen, (raqueta_2.x, raqueta_2.y))
texto = f"{pelota.puntuacion} : {pelota.puntuacion_ia}"
letrero = fuente.render(texto, False, NEGRO)
ventana.blit(letrero, (VENTANA_HORI / 2 - fuente.size(texto)[0] / 2, 50))
for event in pygame.event.get():
if event.type == QUIT:
jugando = False
# Detecta que se ha pulsado una tecla
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_w:
raqueta_1.dir_y = -5
if event.key == pygame.K_s:
raqueta_1.dir_y = 5
# Detecta que se ha soltado la tecla
if event.type == pygame.KEYUP:
if event.key == pygame.K_w:
raqueta_1.dir_y = 0
if event.key == pygame.K_s:
raqueta_1.dir_y = 0
pygame.display.flip()
pygame.time.Clock().tick(FPS)
pygame.quit()
if __name__ == "__main__":
main()
Las instrucciones añadidas con respecto al paso 8 son las siguientes:
Para guardar y actualizar la puntuación de los jugadores, añadimos las siguientes instrucciones:
Añadimos dos atributos más en la clase PelotaPong, puntuacion y puntuacion_ia para guardar las puntuaciones de cada jugador.
# Puntuación de la pelota
self.puntuacion = 0
self.puntuacion_ia = 0
La puntuación debe modificarse cuando un jugador gana, es decir, cuando la pelota supera uno de los laterales. El programa detecta esa situación en el método rebotar de PelotaPong, así que añadimos en ese método las líneas 44 y 47.
def rebotar(self):
if self.x <= -self.ancho:
self.reiniciar()
self.puntuacion_ia += 1
if self.x >= VENTANA_HORI:
self.reiniciar()
self.puntuacion += 1
Definimos el color NEGRO como constante:
NEGRO = (0, 0, 0) # Color del texto (RGB)
Creamos un objeto de clase Font en el que definimos el tamaño del tipo de letra (con el primer argumento podríamos elegir un tipo de letra determinado, en este caso no indicamos ninguno y por tanto pygame usará el predeterminado)
# Inicialización de la fuente
fuente = pygame.font.Font(None, 60)
Para escribir la puntuación:
texto = f"{pelota.puntuacion} : {pelota.puntuacion_ia}"
letrero = fuente.render(texto, False, NEGRO)
ventana.blit(letrero, (VENTANA_HORI / 2 - fuente.size(texto)[0] / 2, 50))
Estos son los aspectos pendientes de completar en el programa: