Cómo programar un juego en Python con Pygame
Esta es una introducción a Pygame para personas que ya estén familiarizadas con Python. En este artículo verás los pasos necesarios para crear un simple juego estilo "balón prisionero" en el cual el jugador tiene que esquivar pelotas que rebotan.
1. **Descarga Pygame.** Busca la versión correspondiente a tu plataforma en el sitio http://www.pygame.org/download.shtml.
2. **Ejecuta el instalador.** 
3. **Verifica que la instalación haya funcionado.** Abre la terminal de Python y escribe "import pygame". Si no aparece un error, entonces Pygame se habrá instalado correctamente.

import pygame
4. **Abre un archivo nuevo.** 
5. **Importa Pygame.** Pygame es una biblioteca que te dará acceso a funciones gráficas. Si quieres más información sobre cómo trabajan estas funciones, puedes buscar información sobre ellas en el sitio web de Pygame: https://www.pygame.org/docs/.

import pygame
from pygame.locals import *
6. **Configura la resolución de pantalla.** Ahora deberás crear una variable global para la resolución de la pantalla de modo que puedas referenciarla en distintas partes del juego. Generalmente va en la parte superior del archivo para que sea más fácil cambiarla más adelante. En proyectos avanzados, es mejor poner esta información en un archivo separado.

resolution = (400,300)
7. **Define algunos colores.** Los colores en Pygame son , con valores que varían entre 0 y 255. El valor alfa (A) es opcional, pero los demás colores (rojo, azul y verde) son obligatorios.

white = (255,255,255)
black = (0,0,0)
red = (255,0,0)
8. **Inicializa la pantalla.** Utiliza la variable de la resolución que habías definido anteriormente.


screen = pygame.display.set_mode(resolution)
9. **Crea un bucle para el juego.** Repite ciertas acciones en todos los cuadros del juego. Crea un bucle que se repita indefinidamente replicando estas acciones.

while True:
10. **Colorea la pantalla.** screen.fill(white)
11. **Muestra la pantalla.** Si ejecutas el programa, la pantalla se volverá blanca y luego el programa dejará de responder. Esto se debe a que el sistema operativo estará enviándole eventos al juego y el juego no puede hacer nada con ellos. Al recibir tantos eventos no controlados, el juego dejará de responder.


while True:
    ...
    pygame.display.flip()
12. **Controla los eventos.** Obtén una lista de todos los eventos que ocurren en cada cuadro. Por ahora te ocuparás de un solo evento, el evento de salida. Este evento ocurre cuando el usuario cierra el juego. Al hacerlo, evitarás que el programa deje de responder como consecuencia del exceso de eventos.


while True:
    ...
    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
13. **¡Prueba el juego!** Así es como deberá verse tu código ahora:


import pygame
from pygame.locals import *

resolution = (400,300)
white = (255,255,255)
black = (0,0,0)
red = (255,0,0)

screen = pygame.display.set_mode(resolution)

while True:
    screen.fill(white)
    pygame.display.flip()

    for event in pygame.event.get():
        if event.type == QUIT:
            pygame.quit()
14. **Crea una nueva clase y un constructor.** Configura todas las propiedades del objeto. También deberás proporcionar valores para cada una de esas propiedades.

class Ball:
    def __init__(self, xPos =  resolution[0] / 2, yPos = resolution[1] / 2, xVel = 1, yVel = 1, rad = 15):
        self.x = xPos
        self.y = yPos
        self.dx = xVel
        self.dy = yVel
        self.radius = rad
        self.type = "ball"
15. **Determina la forma en la que se dibujará el objeto.** Utiliza las propiedades que definiste en el constructor tanto para dibujar la pelota como un círculo, como para pasarle a la función la superficie sobre la cual se dibujará el objeto. La superficie será el objeto de la pantalla que creaste anteriormente a través de la resolución.

    def draw(self, surface):
        pygame.draw.circle(surface, black, (self.x, self.y), self.radius)
16. **Crea una instancia de la clase y haz que el bucle del juego dibuje la pelota en cada repetición.** ball = Ball()

while True:
	...
    ball.draw(screen)
17. **Haz que el objeto se mueva.** Crea una función que actualice la posición del objeto. Llama a esta función en cada repetición del bucle.

class Ball:
	...
    def update(self):
        self.x += self.dx
        self.y += self.dy
18. **Limita la tasa de cuadros por segundo.** La pelota se moverá rápido porque el bucle del juego se ejecuta cientos de veces por segundo. Utiliza el reloj de Pygame para limitar la tasa a 60 cuadros por segundo.
19. **Organiza todo a través de clases.** El juego se volverá más complicado. Utiliza las técnicas de la programación orientada a objetos para organizar tu código.
20. **Convierte el bucle del juego en una clase.** Como el juego ahora tiene datos que incluyen los objetos y las funciones del juego, sería lógico convertir el bucle del juego en una clase.

class game():
21. **Agrega un constructor.** En él instanciarás algunos objetos del juego, crearás la pantalla y el reloj, e inicializarás Pygame. Es necesario inicializar Pygame para poder usar ciertas características como texto y sonido.

class game():
    def __init__(self):
        pygame.init()
        
        self.screen = pygame.display.set_mode(resolution)
        self.clock = pygame.time.Clock()
22. **Controla los eventos a través de una función.** class game():
	...
	def handleEvents(self):
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
23. **Convierte el bucle del juego en una función.** Llama a la función que controla al evento en cada repetición.

class game():
	...
	def run(self):
        while True:
            self.handleEvents()
			
			self.screen.fill(white)
			
            self.clock.tick(60)
            pygame.display.flip()
24. **Controla varios objetos del juego.** Ahora mismo, el código debe llamar a la función de dibujo y de actualización del objeto en cada cuadro. Esto podría volverse muy complicado si tuvieras muchos objetos, por lo que sería mejor agregar el objeto a un arreglo y posteriormente actualizar y dibujar todos los objetos el arreglo en cada repetición. Así podrás agregar fácilmente otro objeto y asignarle una posición inicial diferente.


class game():
    def __init__(self):
		...
        self.gameObjects = []
        self.gameObjects.append(Ball())
        self.gameObjects.append(Ball(100))

    ...

    def run(self):
        while True:
            self.handleEvents()

            for gameObj in self.gameObjects:
                gameObj.update()

            self.screen.fill(white)

            for gameObj in self.gameObjects:
                gameObj.draw(self.screen)

            self.clock.tick(60)
            pygame.display.flip()
25. **¡Prueba el juego!** Así es como deberá verse tu código ahora:

import pygame
from pygame.locals import *

resolution = (400,300)
white = (255,255,255)
black = (0,0,0)
red = (255,0,0)

screen = pygame.display.set_mode(resolution)

class Ball:
    def __init__(self, xPos =  resolution[0] / 2, yPos = resolution[1] / 2, xVel = 1, yVel = 1, rad = 15):
        self.x = xPos
        self.y = yPos
        self.dx = xVel
        self.dy = yVel
        self.radius = rad
        self.type = "ball"

    def draw(self, surface):
        pygame.draw.circle(surface, black, (self.x, self.y), self.radius)

    def update(self):
        self.x += self.dx
        self.y += self.dy
        if (self.x <= 0 or self.x >= resolution[0]):
            self.dx *= -1
        if (self.y <= 0 or self.y >= resolution[1]):
            self.dy *= -1

class game():
    def __init__(self):
        pygame.init()
        
        self.screen = pygame.display.set_mode(resolution)
        self.clock = pygame.time.Clock()
        self.gameObjects = []
        self.gameObjects.append(Ball())
        self.gameObjects.append(Ball(100))

    def handleEvents(self):
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()

    def run(self):
        while True:
            self.handleEvents()

            for gameObj in self.gameObjects:
                gameObj.update()

            self.screen.fill(white)

            for gameObj in self.gameObjects:
                gameObj.draw(self.screen)

            self.clock.tick(60)
            pygame.display.flip()

game().run()
26. **Crea una clase y un constructor para el jugador.** Ahora deberás crear otro círculo que se controle a través del ratón. Inicializa los valores en el constructor. El radio es el único valor importante.

class Player:
    def __init__(self, rad = 20):
        self.x = 0
        self.y = 0
        self.radius = rad
27. **Define cómo dibujar el objeto del jugador.** Se dibujará del mismo modo que dibujaste los otros objetos del juego.

class Player:
	...
    def draw(self, surface):
        pygame.draw.circle(surface, red, (self.x, self.y), self.radius)
28. **Agrégale el control del ratón al objeto del jugador.** En cada cuadro, revisa la ubicación del ratón y establece la ubicación de los objetos del jugador en ese punto.

class Player:
	...
    def update(self):
        cord = pygame.mouse.get_pos()
        self.x = cord[0]
        self.y = cord[1]
29. **Agrega un objeto del jugador a los objetos del juego (gameObjects).** Crea una nueva instancia de jugador y agrégala a la lista.

class game():
    def __init__(self):
		...
        self.gameObjects.append(Player())
30. **¡Prueba el juego!** Así es como deberá verse tu código ahora:

import pygame
from pygame.locals import *

resolution = (400,300)
white = (255,255,255)
black = (0,0,0)
red = (255,0,0)

screen = pygame.display.set_mode(resolution)

class Ball:
    def __init__(self, xPos =  resolution[0] / 2, yPos = resolution[1] / 2, xVel = 1, yVel = 1, rad = 15):
        self.x = xPos
        self.y = yPos
        self.dx = xVel
        self.dy = yVel
        self.radius = rad
        self.type = "ball"

    def draw(self, surface):
        pygame.draw.circle(surface, black, (self.x, self.y), self.radius)

    def update(self):
        self.x += self.dx
        self.y += self.dy
        if (self.x <= 0 or self.x >= resolution[0]):
            self.dx *= -1
        if (self.y <= 0 or self.y >= resolution[1]):
            self.dy *= -1

class Player:
    def __init__(self, rad = 20):
        self.x = 0
        self.y = 0
        self.radius = rad
        self.type = "player"

    def draw(self, surface):
        pygame.draw.circle(surface, red, (self.x, self.y), self.radius)

    def update(self):
        cord = pygame.mouse.get_pos()
        self.x = cord[0]
        self.y = cord[1]

class game():
    def __init__(self):
        pygame.init()
        
        self.screen = pygame.display.set_mode(resolution)
        self.clock = pygame.time.Clock()
        self.gameObjects = []
        self.gameObjects.append(Player())
        self.gameObjects.append(Ball())
        self.gameObjects.append(Ball(100))

    def handleEvents(self):
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()

    def run(self):
        while True:
            self.handleEvents()

            for gameObj in self.gameObjects:
                gameObj.update()

            self.screen.fill(white)

            for gameObj in self.gameObjects:
                gameObj.draw(self.screen)

            self.clock.tick(60)
            pygame.display.flip()

game().run()
31. **Cambia las funciones de actualización.** Para que los objetos puedan interactuar, es necesario que dichos objetos puedan accederse entre sí. Agrega otro parámetro a la función de actualización para pasarle la lista de objetos del juego (gameObjects). Tendrás que agregársela tanto al objeto del jugador como a los objetos de la pelota. Si tienes muchos objetos de juego, la herencia podría ayudarte a hacer que todas las firmas del método sean iguales.

class Ball:
	...
	def update(self, gameObjects):
	
...

class Player:
	...
	def update(self, gameObjects):
32. **Ocúpate de las colisiones entre el jugador y las pelotas.** Revisa todos los objetos del juego para determinar si son de tipo "ball" (pelota). Luego utiliza las fórmulas de radios y distancias entre dos objetos para determinar si estos colisionarán. Los círculos son formas en las cuales resulta muy sencillo determinar si se producirá una colisión. Esta es la principal razón por la cual no se usan otros tipos de formas en el juego.

class Player:
	...
	def update(self, gameObjects):
		...
		for gameObj in gameObjects:
            if gameObj.type == "ball":
                if (gameObj.x - self.x)**2 + (gameObj.y - self.y)**2 <= (gameObj.radius + self.radius)**2:
33. **Finaliza el juego si el jugador es "golpeado" por una pelota.** Por ahora, simplemente se cerrará el juego si eso sucede.

if (gameObj.x - self.x)**2 + (gameObj.y - self.y)**2 <= (gameObj.radius + self.radius)**2:
	pygame.quit()
34. **¡Prueba el juego!** Así es como deberá verse tu código ahora:

import pygame
from pygame.locals import *

resolution = (400, 300)
white = (255,255,255)
black = (0,0,0)
red = (255,0,0)

screen = pygame.display.set_mode(resolution)

class Ball:
    def __init__(self, xPos =  resolution[0] / 2, yPos = resolution[1] / 2, xVel = 1, yVel = 1, rad = 15):
        self.x = xPos
        self.y = yPos
        self.dx = xVel
        self.dy = yVel
        self.radius = rad
        self.type = "ball"

    def draw(self, surface):
        pygame.draw.circle(surface, black, (self.x, self.y), self.radius)

    def update(self, gameObjects):
        self.x += self.dx
        self.y += self.dy
        if (self.x <= 0 or self.x >= resolution[0]):
            self.dx *= -1
        if (self.y <= 0 or self.y >= resolution[1]):
            self.dy *= -1

class Player:
    def __init__(self, rad = 20):
        self.x = 0
        self.y = 0
        self.radius = rad
        self.type = "player"

    def draw(self, surface):
        pygame.draw.circle(surface, red, (self.x, self.y), self.radius)

    def update(self, gameObjects):
        cord = pygame.mouse.get_pos()
        self.x = cord[0]
        self.y = cord[1]
        for gameObj in gameObjects:
            if gameObj.type == "ball":
                if (gameObj.x - self.x)**2 + (gameObj.y - self.y)**2 <= (gameObj.radius + self.radius)**2:
                    pygame.quit()

class game():
    def __init__(self):
        pygame.init()
        
        self.screen = pygame.display.set_mode(resolution)
        self.clock = pygame.time.Clock()
        self.gameObjects = []
        self.gameObjects.append(Player())
        self.gameObjects.append(Ball())
        self.gameObjects.append(Ball(100))

    def handleEvents(self):
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()

    def run(self):
        while True:
            self.handleEvents()

            for gameObj in self.gameObjects:
                gameObj.update(self.gameObjects)

            self.screen.fill(white)

            for gameObj in self.gameObjects:
                gameObj.draw(self.screen)

            self.clock.tick(60)
            pygame.display.flip()

game().run()
35. **Crea una clase de controlador de juego.** Los controladores de juego son los responsables de mantener el juego "en ejecución". Son distintos a la clase del juego, que se encarga de dibujar y actualizar todos los objetos. El controlador agregará periódicamente otra pelota a la pantalla para que el juego se vuelva más difícil. Agrega un constructor e inicializa algunos valores básicos. El intervalo representará al tiempo que deberá transcurrir antes de agregar una nueva pelota.

class GameController:
    def __init__(self, interval = 5):
        self.inter = interval
        self.next = pygame.time.get_ticks() + (2 * 1000)
        self.type = "game controller"
36. **Agrega la función de actualización.** Esta función se encargará de determinar cuánto tiempo ha transcurrido desde que se agregó la última pelota o desde que se inició el juego. Si el tiempo es mayor al intervalo, se reiniciará el tiempo y se agregará una pelota.

class GameController:
	...
    def update(self, gameObjects):
        if self.next < pygame.time.get_ticks():
            self.next = pygame.time.get_ticks() + (self.inter * 1000)
            gameObjects.append(Ball())
37. **Asígnales velocidades aleatorias a las pelotas.** Deberás utilizar números aleatorios para hacer que el juego varíe a lo largo del tiempo. Sin embargo, las velocidades de las pelotas ahora estarán representadas por un número en punto flotante en lugar de un entero.

class GameController:
    ...
    def update(self, gameObjects):
        if self.next < pygame.time.get_ticks():
            self.next = pygame.time.get_ticks() + (self.inter * 1000)
            gameObjects.append(Ball(xVel=random()*2, yVel=random()*2))
38. **Arregla la función de dibujo.** La función de dibujo no acepta números en punto flotante, por lo que deberás convertir la posición de las pelotas a enteros para que estas se puedan dibujar.

class Ball:
    ...
    def draw(self, surface):
        pygame.draw.circle(surface, black, (int(self.x), int(self.y)), self.radius)
39. **Define un método de dibujo para el controlador del juego.** Al tratarse de un objeto del juego, el bucle principal intentará dibujarlo. Deberás definir una función de dibujo que no haga nada para que el juego no deje de responder.

class GameController:
    ...
    def draw(self, screen):
        pass
40. **Agrega el controlador del juego a "gameObjects" y elimina las dos pelotas.** El juego ahora generará una nueva pelota cada cinco segundos.

class game():
    def __init__(self):
        ...
        self.gameObjects = []
        self.gameObjects.append(GameController())
        self.gameObjects.append(Player())
41. **¡Prueba el juego!** Así es como deberá verse tu código ahora:

import pygame
from random import random
from pygame.locals import *

resolution = (400,300)
white = (255,255,255)
black = (0,0,0)
red = (255,0,0)

screen = pygame.display.set_mode(resolution)

class Ball:
    def __init__(self, xPos =  resolution[0] / 2, yPos = resolution[1] / 2, xVel = 1, yVel = 1, rad = 15):
        self.x = xPos
        self.y = yPos
        self.dx = xVel
        self.dy = yVel
        self.radius = rad
        self.type = "ball"

    def draw(self, surface):
        pygame.draw.circle(surface, black, (int(self.x), int(self.y)), self.radius)

    def update(self, gameObjects):
        self.x += self.dx
        self.y += self.dy
        if (self.x <= 0 or self.x >= resolution[0]):
            self.dx *= -1
        if (self.y <= 0 or self.y >= resolution[1]):
            self.dy *= -1

class Player:
    def __init__(self, rad = 20):
        self.x = 0
        self.y = 0
        self.radius = rad
        self.type = "player"

    def draw(self, surface):
        pygame.draw.circle(surface, red, (self.x, self.y), self.radius)

    def update(self, gameObjects):
        cord = pygame.mouse.get_pos()
        self.x = cord[0]
        self.y = cord[1]
        for gameObj in gameObjects:
            if gameObj.type == "ball":
                if (gameObj.x - self.x)**2 + (gameObj.y - self.y)**2 <= (gameObj.radius + self.radius)**2:
                    pygame.quit()
                    
class GameController:
    def __init__(self, interval = 5):
        self.inter = interval
        self.next = pygame.time.get_ticks() + (2 * 1000)
        self.type = "game controller"

    def update(self, gameObjects):
        if self.next < pygame.time.get_ticks():
            self.next = pygame.time.get_ticks() + (self.inter * 1000)
            gameObjects.append(Ball(xVel=random()*2, yVel=random()*2))

    def draw(self, screen):
        pass

class game():
    def __init__(self):
        pygame.init()
        
        self.screen = pygame.display.set_mode(resolution)
        self.clock = pygame.time.Clock()
        self.gameObjects = []
        self.gameObjects.append(GameController())
        self.gameObjects.append(Player())

    def handleEvents(self):
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()

    def run(self):
        while True:
            self.handleEvents()

            for gameObj in self.gameObjects:
                gameObj.update(self.gameObjects)

            self.screen.fill(white)

            for gameObj in self.gameObjects:
                gameObj.draw(self.screen)

            self.clock.tick(60)
            pygame.display.flip()

game().run()
42. **Agrégale un puntaje a la clase del controlador del juego.** Crea un objeto de fuente y una variable de puntaje. Deberás dibujar la fuente en cada cuadro para que muestre el puntaje e incrementar dicho puntaje en cada cuadro a través de la función de actualización.

class GameController:
    def __init__(self, interval = 5):
        ...
        self.score = 0
        self.scoreText = pygame.font.Font(None, 12)

    def update(self, gameObjects):
        ...
        self.score += 1

    def draw(self, screen):
        screen.blit(self.scoreText.render(str(self.score), True, black), (5,5))
43. **Modifica la finalización del juego.** Ahora en vez de cerrar el juego cuando el jugador sufra una colisión, puedes configurar una variable en el jugador para que el juego pueda verificarla. Cuando la variable "gameOver" (juego terminado) tome el valor "true" (verdadero), deja de actualizar los objetos. Al hacerlo, se congelará la pantalla para que el jugador pueda ver lo que sucedió y revisar su puntaje. Ten en cuenta que los objetos quedarán dibujados, solo que no se actualizarán.

class Player:
    def __init__(self, rad = 20):
        ...
        self.gameOver = False
    
    def update(self, gameObjects):
        ...
        for gameObj in gameObjects:
            if gameObj.type == "ball":
                if (gameObj.x - self.x)**2 + (gameObj.y - self.y)**2 <= (gameObj.radius + self.radius)**2:
                    self.gameOver = True

class game():
    def __init__(self):
        ...
        self.gameOver = False

    def run(self):
        while True:
            self.handleEvents()

            if not self.gameOver:
                for gameObj in self.gameObjects:
                    gameObj.update(self.gameObjects)
                    if gameObj.type == "player":
                        self.gameOver = gameObj.gameOver
44. **¡Prueba el juego!** Así es como deberá verse tu código ahora:

import pygame
from random import random
from pygame.locals import *

resolution = (400,300)
white = (255,255,255)
black = (0,0,0)
red = (255,0,0)

screen = pygame.display.set_mode(resolution)

class Ball:
    def __init__(self, xPos =  resolution[0] / 2, yPos = resolution[1] / 2, xVel = 1, yVel = 1, rad = 15):
        self.x = xPos
        self.y = yPos
        self.dx = xVel
        self.dy = yVel
        self.radius = rad
        self.type = "ball"

    def draw(self, surface):
        pygame.draw.circle(surface, black, (int(self.x), int(self.y)), self.radius)

    def update(self, gameObjects):
        self.x += self.dx
        self.y += self.dy
        if (self.x <= 0 or self.x >= resolution[0]):
            self.dx *= -1
        if (self.y <= 0 or self.y >= resolution[1]):
            self.dy *= -1

class Player:
    def __init__(self, rad = 20):
        self.x = 0
        self.y = 0
        self.radius = rad
        self.type = "player"
        self.gameOver = False

    def draw(self, surface):
        pygame.draw.circle(surface, red, (self.x, self.y), self.radius)

    def update(self, gameObjects):
        cord = pygame.mouse.get_pos()
        self.x = cord[0]
        self.y = cord[1]
        for gameObj in gameObjects:
            if gameObj.type == "ball":
                if (gameObj.x - self.x)**2 + (gameObj.y - self.y)**2 <= (gameObj.radius + self.radius)**2:
                    self.gameOver = True
                    
class GameController:
    def __init__(self, interval = 5):
        self.inter = interval
        self.next = pygame.time.get_ticks() + (2 * 1000)
        self.type = "game controller"
        
        self.score = 0
        self.scoreText = pygame.font.Font(None, 12)

    def update(self, gameObjects):
        if self.next < pygame.time.get_ticks():
            self.next = pygame.time.get_ticks() + (self.inter * 1000)
            gameObjects.append(Ball(xVel=random()*2, yVel=random()*2))

        self.score += 1

    def draw(self, screen):
        screen.blit(self.scoreText.render(str(self.score), True, black), (5,5))

class game():
    def __init__(self):
        pygame.init()
        
        self.screen = pygame.display.set_mode(resolution)
        self.clock = pygame.time.Clock()
        self.gameObjects = []
        self.gameObjects.append(GameController())
        self.gameObjects.append(Player())
        self.gameOver = False

    def handleEvents(self):
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()

    def run(self):
        while True:
            self.handleEvents()

            if not self.gameOver:
                for gameObj in self.gameObjects:
                    gameObj.update(self.gameObjects)
                    if gameObj.type == "player":
                        self.gameOver = gameObj.gameOver

            self.screen.fill(white)

            for gameObj in self.gameObjects:
                gameObj.draw(self.screen)

            self.clock.tick(60)
            pygame.display.flip()

game().run()
{"url": "https://es.wikihow.com/programar-un-juego-en-Python-con-Pygame", "language": "es"}