Cómo crear videojuegos con Python y pygame: guía completa paso a paso

Crear un videojuego es una de las mejores formas de aprender a programar, porque cada error se ve en pantalla y cada mejora se nota al instante. Python es un lenguaje fácil de leer y pygame es una biblioteca que se encarga de la ventana, los gráficos, el teclado y el sonido. Con ambos puedes pasar de cero a un juego terminado y publicado.

Esta guía recorre el camino completo: preparar el entorno, entender el bucle de juego, programar tus primeros juegos, diseñar uno propio y compartirlo.

¿Qué es pygame y por qué usarlo?

pygame es una biblioteca de Python para crear juegos 2D. Funciona sobre SDL y ofrece lo básico para empezar: ventana, dibujo de formas e imágenes, eventos de teclado y ratón, sonido y control del tiempo.

Ventajas:

  • Curva de aprendizaje suave: no necesitas un motor gráfico pesado.
  • Tú controlas toda la lógica, lo que enseña cómo funciona realmente un juego.
  • Es gratuito, multiplataforma y tiene una comunidad grande.

Límites: no es la mejor opción para juegos 3D ni para producciones grandes. Para aprender y para juegos 2D pequeños o medianos es excelente.

Existen dos paquetes que se importan igual (import pygame): pygame (el original) y pygame-ce (edición comunitaria con desarrollo muy activo). Elige uno e instala solo ese.

Paso 1: domina los fundamentos de Python

Creación de videojuegos con python y pygame

Antes de abrir pygame, asegúrate de manejar con soltura:

  • Variables, números, cadenas y booleanos.
  • Listas, tuplas y diccionarios.
  • Condicionales (if, elif, else) y bucles (for, while).
  • Funciones con parámetros y return.
  • Clases, atributos y métodos (self, __init__).
  • Módulos e import, y lectura de mensajes de error.

El tutorial oficial de Python (docs.python.org/3/tutorial) es gratuito y suficiente. Con una o dos semanas de práctica diaria basta para comenzar.

Paso 2: prepara el entorno de desarrollo

1. Instala Python. Descarga la versión estable más reciente desde python.org (se recomienda 3.10 o superior). En Windows, marca la casilla Add python.exe to PATH.

2. Crea un entorno virtual. Aísla las bibliotecas de cada proyecto y evita conflictos:

mkdir mi_juego
cd mi_juego
python -m venv .venv

# Activar el entorno
# Windows (PowerShell):  .venv\Scripts\Activate.ps1
# Linux / macOS:         source .venv/bin/activate

3. Instala pygame:

python -m pip install --upgrade pip
python -m pip install pygame

4. Verifica la instalación:

import pygame

pygame.init()
print("pygame", pygame.version.ver)
pygame.quit()

Si aparece ModuleNotFoundError, normalmente el entorno virtual no está activado.

5. Elige un editor. Visual Studio Code (con la extensión de Python) o PyCharm Community son buenas opciones. Selecciona dentro del editor el intérprete de tu carpeta .venv.

Estructura de carpetas recomendada:

mi_juego/
├── .venv/
├── assets/
│   ├── imagenes/
│   ├── sonidos/
│   └── fuentes/
├── main.py
└── requirements.txt

Paso 3: entiende el bucle principal de un juego

Todo juego en pygame, sencillo o complejo, repite cuatro etapas muchas veces por segundo:

  1. Eventos: leer lo que hizo el jugador (teclas, ratón, cerrar la ventana).
  2. Actualizar: mover objetos, aplicar reglas, detectar colisiones.
  3. Dibujar: pintar el estado actual en pantalla.
  4. Controlar el tiempo: mantener una velocidad estable (por ejemplo, 60 cuadros por segundo).

Este es el esqueleto mínimo:

import sys
import pygame

pygame.init()

ANCHO, ALTO = 800, 600
FPS = 60

pantalla = pygame.display.set_mode((ANCHO, ALTO))
pygame.display.set_caption("Mi primer juego")
reloj = pygame.time.Clock()

ejecutando = True
while ejecutando:
    # 1. Eventos
    for evento in pygame.event.get():
        if evento.type == pygame.QUIT:
            ejecutando = False

    # 2. Actualizar la lógica

    # 3. Dibujar
    pantalla.fill((20, 20, 30))
    pygame.display.flip()

    # 4. Controlar la velocidad
    reloj.tick(FPS)

pygame.quit()
sys.exit()

Qué hace cada pieza:

  • pygame.display.set_mode(...) crea la ventana y devuelve la superficie sobre la que se dibuja todo.
  • pygame.event.get() entrega los eventos pendientes. Si no se llama en cada cuadro, la ventana deja de responder.
  • pantalla.fill(color) borra el cuadro anterior. Sin esto, los objetos dejan estela.
  • pygame.display.flip() muestra en pantalla lo que se dibujó.
  • reloj.tick(FPS) limita la velocidad y devuelve los milisegundos del cuadro anterior.
Te recomendamos leer:  Introducción a CSS: Una Guía Completa

Coordenadas y rectángulos

En pygame el origen (0, 0) está en la esquina superior izquierda y el eje y crece hacia abajo. Subir un objeto significa disminuir su y.

Los objetos suelen representarse con pygame.Rect(x, y, ancho, alto), que ofrece atributos como left, right, top, bottom y center, y métodos como colliderect() para detectar choques. Un Rect guarda números enteros, así que para movimientos suaves conviene acumular la posición en variables decimales y asignarla redondeada.

Mover objetos sin depender de los FPS

Si mueves un objeto «5 píxeles por cuadro», irá más lento en un equipo que corra a 30 FPS que en uno que corra a 120. La solución es medir la velocidad en píxeles por segundo y multiplicarla por dt, la duración del último cuadro en segundos:

posición nueva = posición anterior + velocidad (px/s) × dt (s)
import sys
import pygame

pygame.init()

ANCHO, ALTO = 800, 600
FPS = 60
VELOCIDAD = 300  # píxeles por segundo

pantalla = pygame.display.set_mode((ANCHO, ALTO))
reloj = pygame.time.Clock()

jugador = pygame.Rect(0, 0, 50, 50)
jugador.center = (ANCHO // 2, ALTO // 2)
x, y = float(jugador.x), float(jugador.y)

ejecutando = True
while ejecutando:
    dt = reloj.tick(FPS) / 1000

    for evento in pygame.event.get():
        if evento.type == pygame.QUIT:
            ejecutando = False

    teclas = pygame.key.get_pressed()
    dx = teclas[pygame.K_RIGHT] - teclas[pygame.K_LEFT]
    dy = teclas[pygame.K_DOWN] - teclas[pygame.K_UP]

    x += dx * VELOCIDAD * dt
    y += dy * VELOCIDAD * dt

    # Limitar a los bordes de la ventana
    x = max(0, min(ANCHO - jugador.width, x))
    y = max(0, min(ALTO - jugador.height, y))
    jugador.topleft = (round(x), round(y))

    pantalla.fill((20, 20, 30))
    pygame.draw.rect(pantalla, (80, 200, 120), jugador)
    pygame.display.flip()

pygame.quit()
sys.exit()

Este patrón (dt, posición decimal, límites con max y min, asignación redondeada) se reutiliza en casi todos los juegos.

Teclado: mantener frente a pulsar

MétodoDetectaÚsalo para
pygame.key.get_pressed()Tecla mantenida ahora mismoCaminar, mover una paleta
Evento pygame.KEYDOWNEl instante de la pulsaciónSaltar, disparar, pausar, menús
pygame.mouse.get_pos() y MOUSEBUTTONDOWNPosición y clics del ratónMenús, apuntar

Imágenes, texto y sonido

from pathlib import Path
BASE = Path(__file__).parent

# Imagen (cargar una sola vez, antes del bucle)
nave = pygame.image.load(BASE / "assets" / "imagenes" / "nave.png").convert_alpha()
pantalla.blit(nave, (300, 400))

# Texto
fuente = pygame.font.Font(None, 36)
texto = fuente.render("Puntos: 10", True, (255, 255, 255))
pantalla.blit(texto, (10, 10))

# Sonido
pygame.mixer.init()
disparo = pygame.mixer.Sound(BASE / "assets" / "sonidos" / "disparo.wav")
disparo.play()

Dos reglas importantes: carga imágenes y sonidos una vez antes del bucle (hacerlo en cada cuadro vuelve el juego lento) y usa convert() o convert_alpha() tras cargar una imagen. Si usas recursos de terceros, revisa siempre su licencia.

Paso 4: tus primeros proyectos

Construye juegos pequeños y completos. Cada uno enseña una pieza distinta.

Proyecto 1: Pong

Enseña velocidad como vector, rebotes, ángulo de salida según el punto de impacto, una inteligencia artificial sencilla y un marcador.

import random
import sys
import pygame

ANCHO, ALTO = 800, 500
FPS = 60
FONDO = (15, 15, 25)
BLANCO = (240, 240, 240)

VEL_PALA = 420    # px/s, jugador
VEL_IA = 290      # px/s, rival (menor que la bola: se puede ganar)
VEL_BOLA_X = 320  # px/s
VEL_MAX_X = 700


def reiniciar_bola(sentido):
    pos = pygame.Vector2(ANCHO / 2, ALTO / 2)
    vel = pygame.Vector2(sentido * VEL_BOLA_X, random.uniform(-180, 180))
    return pos, vel


def main():
    pygame.init()
    pantalla = pygame.display.set_mode((ANCHO, ALTO))
    pygame.display.set_caption("Pong")
    reloj = pygame.time.Clock()
    fuente = pygame.font.Font(None, 64)

    pala_j = pygame.Rect(30, 0, 12, 100)
    pala_r = pygame.Rect(ANCHO - 42, 0, 12, 100)
    y_j = y_r = ALTO / 2 - 50
    bola = pygame.Rect(0, 0, 14, 14)
    pos_bola, vel_bola = reiniciar_bola(random.choice([-1, 1]))
    puntos_j = puntos_r = 0

    corriendo = True
    while corriendo:
        dt = reloj.tick(FPS) / 1000

        for evento in pygame.event.get():
            if evento.type == pygame.QUIT:
                corriendo = False

        # Jugador (W/S o flechas)
        teclas = pygame.key.get_pressed()
        mov = (teclas[pygame.K_s] or teclas[pygame.K_DOWN]) - (
            teclas[pygame.K_w] or teclas[pygame.K_UP]
        )
        y_j += mov * VEL_PALA * dt

        # Rival: sigue la bola con velocidad limitada
        objetivo = pos_bola.y - pala_r.height / 2
        paso = VEL_IA * dt
        y_r += max(-paso, min(paso, objetivo - y_r))

        y_j = max(0, min(ALTO - pala_j.height, y_j))
        y_r = max(0, min(ALTO - pala_r.height, y_r))
        pala_j.y, pala_r.y = round(y_j), round(y_r)

        # Bola
        pos_bola += vel_bola * dt
        bola.center = (round(pos_bola.x), round(pos_bola.y))

        if bola.top <= 0:
            bola.top = 0
            pos_bola.y = bola.centery
            vel_bola.y = abs(vel_bola.y)
        elif bola.bottom >= ALTO:
            bola.bottom = ALTO
            pos_bola.y = bola.centery
            vel_bola.y = -abs(vel_bola.y)

        for pala, sentido in ((pala_j, 1), (pala_r, -1)):
            if bola.colliderect(pala) and vel_bola.x * sentido < 0:
                desvio = (bola.centery - pala.centery) / (pala.height / 2)
                vel_bola.x = sentido * min(abs(vel_bola.x) * 1.06, VEL_MAX_X)
                vel_bola.y = desvio * 320

        # Puntos
        if bola.right < 0:
            puntos_r += 1
            pos_bola, vel_bola = reiniciar_bola(1)
        elif bola.left > ANCHO:
            puntos_j += 1
            pos_bola, vel_bola = reiniciar_bola(-1)

        # Dibujo
        pantalla.fill(FONDO)
        pygame.draw.line(pantalla, (60, 60, 80), (ANCHO // 2, 0), (ANCHO // 2, ALTO), 2)
        pygame.draw.rect(pantalla, BLANCO, pala_j)
        pygame.draw.rect(pantalla, BLANCO, pala_r)
        pygame.draw.ellipse(pantalla, BLANCO, bola)
        marcador = fuente.render(f"{puntos_j}   {puntos_r}", True, BLANCO)
        pantalla.blit(marcador, marcador.get_rect(midtop=(ANCHO // 2, 15)))
        pygame.display.flip()

    pygame.quit()
    sys.exit()


if __name__ == "__main__":
    main()

Claves del código:

  • El rebote contra una paleta solo se procesa si la bola se mueve hacia ella (vel_bola.x * sentido < 0). Así se evitan rebotes múltiples en un mismo golpe.
  • desvio va de -1 (borde superior de la paleta) a 1 (borde inferior) y define el ángulo de salida.
  • El rival es ganable porque su velocidad máxima es menor que la de la bola.
Te recomendamos leer:  Introducción a CSS: Una Guía Completa

Retos: termina la partida a 7 puntos, añade sonido al golpear, crea un modo para dos jugadores.

Proyecto 2: la serpiente (Snake)

Enseña a trabajar con una cuadrícula, a guardar el cuerpo en una lista y a separar el ritmo del juego de los FPS: la serpiente avanza en pasos discretos (por ejemplo, cada 110 ms) mientras la pantalla se redibuja a 60 FPS. La técnica es un acumulador de tiempo:

acumulador += reloj.tick(FPS)          # milisegundos

while vivo and acumulador >= PASO_MS:
    acumulador -= PASO_MS
    # mover la serpiente una celda

Tres detalles que suelen fallar:

  • Giro de 180°: si el jugador pulsa dos teclas muy rápido, la serpiente puede girar sobre sí misma. Compara la nueva dirección con la última dirección aplicada, no con la última tecla pulsada.
  • Choque con la cola: al avanzar sin comer, la cola se libera, así que la cabeza puede entrar en la celda que la cola va a dejar. Por eso se comprueba la colisión contra serpiente[:-1].
  • Comida sobre el cuerpo: genera la comida solo en celdas libres.

Retos: guardar la puntuación máxima en un archivo, aumentar la velocidad cada 5 puntos, permitir cruzar los bordes.

Paso 5: proyectos intermedios

Proyecto 3: Invasores (estilo Space Invaders)

Introduce los sprites y los grupos. Un pygame.sprite.Sprite es un objeto con una imagen (image) y una posición (rect). Un Group permite actualizar y dibujar muchos de ellos con una sola línea:

class Bala(pygame.sprite.Sprite):
    def __init__(self, centro_x, y):
        super().__init__()
        self.image = pygame.Surface((4, 14))
        self.image.fill((255, 240, 120))
        self.rect = self.image.get_rect(midbottom=(centro_x, y))
        self.y = float(self.rect.y)

    def update(self, dt):
        self.y -= 520 * dt
        self.rect.y = round(self.y)
        if self.rect.bottom < 0:
            self.kill()

# En el bucle principal:
todos.update(dt)
todos.draw(pantalla)

# Colisión entre dos grupos: se eliminan ambos elementos
pygame.sprite.groupcollide(balas, enemigos, True, True)

Otras ideas del juego: una cadencia de disparo comparando pygame.time.get_ticks() con el instante del último disparo, y una formación de enemigos que avanza por pasos, baja de fila y cambia de sentido al tocar un borde.

Retos: enemigos que disparen, varias vidas, niveles cada vez más rápidos, barreras destructibles.

Proyecto 4: juego de plataformas

Introduce la gravedad, el salto y la resolución de colisiones por ejes: primero se mueve en x y se corrigen choques; después se mueve en y y se corrigen. Hacerlo por separado evita atascos en esquinas y que el personaje atraviese el suelo.

GRAVEDAD = 1800       # px/s²
VEL_SALTO = 650       # px/s

# Eje X
self.pos.x += self.vel.x * dt
self.rect.x = round(self.pos.x)
for p in plataformas:
    if self.rect.colliderect(p):
        if self.vel.x > 0:
            self.rect.right = p.left
        elif self.vel.x < 0:
            self.rect.left = p.right
        self.pos.x = self.rect.x

# Eje Y
self.vel.y += GRAVEDAD * dt
self.pos.y += self.vel.y * dt
self.rect.y = round(self.pos.y)
for p in plataformas:
    if self.rect.colliderect(p):
        if self.vel.y > 0:          # cayendo: aterriza
            self.rect.bottom = p.top
        elif self.vel.y < 0:        # subiendo: golpea el techo
            self.rect.top = p.bottom
        self.vel.y = 0
        self.pos.y = self.rect.y

# ¿Hay suelo justo debajo? (evita saltos que fallan por redondeo)
self.en_suelo = any(self.rect.move(0, 1).colliderect(p) for p in plataformas)

La altura máxima de un salto sale de la física básica:

h = v² / (2g)
h = (650 px/s)² / (2 × 1800 px/s²)
h ≈ 117 px

En la práctica el salto resulta algo menor por el redondeo a píxeles enteros. Si quieres una altura concreta h, despeja v = √(2gh). Un truco útil: limita dt (por ejemplo min(dt, 0.05)) para que un cuadro muy largo no haga atravesar el suelo al personaje.

Te recomendamos leer:  Introducción a CSS: Una Guía Completa

Retos: monedas, enemigos que patrullen, «coyote time» (permitir saltar unos milisegundos después de dejar una plataforma), niveles definidos con una lista de cadenas de texto.

Paso 6: diseña y desarrolla tu propio juego

Diseña antes de programar

Escribe una página con las respuestas a estas preguntas:

  • ¿Qué hace el jugador? El verbo central en una frase: esquivar, disparar, resolver, saltar.
  • ¿Cómo se gana y cómo se pierde? Vidas, tiempo, puntuación.
  • ¿Cuáles son los controles?
  • ¿Qué objetos existen? Jugador, enemigos, proyectos, obstáculos, con sus atributos.
  • ¿Qué pantallas hay? Menú, juego, pausa, fin de partida.
  • ¿Cuál es el alcance mínimo? La versión más pequeña que ya sea divertida.

El error más común de quien empieza es diseñar un juego enorme y abandonarlo. Define un producto mínimo jugable que puedas terminar en dos o tres semanas.

Maneja las pantallas con una máquina de estados

Una variable de estado y una función por pantalla mantienen el código ordenado:

estado = "menu"   # "menu", "jugando", "pausa", "fin"

while ejecutando:
    dt = reloj.tick(FPS) / 1000
    eventos = pygame.event.get()

    if estado == "menu":
        estado = pantalla_menu(pantalla, eventos)
    elif estado == "jugando":
        estado = pantalla_juego(pantalla, eventos, dt)
    elif estado == "pausa":
        estado = pantalla_pausa(pantalla, eventos)
    elif estado == "fin":
        estado = pantalla_fin(pantalla, eventos)

    pygame.display.flip()

Cada función procesa eventos, dibuja y devuelve el nombre del estado siguiente.

Organiza el código

  • Pon velocidades, tamaños y colores en constantes con nombre, en un único archivo (config.py).
  • Crea una clase por tipo de objeto, con métodos actualizar(dt) y dibujar(pantalla).
  • Separa la lógica del dibujo: podrás cambiar los gráficos sin romper las reglas.
  • Agrupa el estado del juego en un objeto en lugar de usar variables globales.

Desarrolla por iteraciones

  1. Prototipo gris: solo rectángulos de color y la mecánica central. Si así no es divertido, no lo será con gráficos.
  2. Reglas completas: puntuación, vidas, victoria y derrota.
  3. Contenido: niveles, enemigos y objetos.
  4. Gráficos y sonido.
  5. Pulido: menús, pausa, dificultad.
  6. Pruebas con otras personas: observa a alguien jugar sin darle instrucciones.

Depuración

  • Muestra los FPS con reloj.get_fps() durante el desarrollo.
  • Dibuja los Rect de colisión (pygame.draw.rect(pantalla, (255, 0, 0), rect, 1)) para ver qué choca realmente.
  • Si el juego va lento, mide antes de optimizar: casi siempre se cargan recursos dentro del bucle o se dibuja de más.

Paso 7: errores frecuentes y cómo resolverlos

SíntomaCausa probable y solución
La ventana «no responde»No se llama a pygame.event.get() en cada cuadro
Pantalla negraFalta pygame.display.flip()
Los objetos dejan estelaNo se borra la pantalla con fill()
El juego va lentoSe cargan imágenes dentro del bucle; falta convert_alpha()
Los objetos solo se mueven de 1 en 1 píxelRect guarda enteros: acumula la posición en decimales
La velocidad cambia entre equiposNo se usa dt
El personaje atraviesa el sueloResuelve colisiones por ejes y limita dt
FileNotFoundError al cargar imágenesUsa Path(__file__).parent para construir las rutas
Teclas que fallan o se repitenConfusión entre KEYDOWN y get_pressed()

Paso 8: versiona, empaqueta y publica

Control de versiones con Git

git init
git add .
git commit -m "Primera versión jugable"

Crea un archivo .gitignore con al menos:

.venv/
__pycache__/
build/
dist/

Después puedes subir el repositorio a GitHub para compartir el código.

Crear un ejecutable con PyInstaller

python -m pip install pyinstaller
pyinstaller --onefile --windowed --name MiJuego --add-data "assets;assets" main.py

En Linux y macOS el separador de --add-data es : en lugar de ;. El ejecutable queda en la carpeta dist/. Debes empaquetar en el sistema operativo de destino: un ejecutable de Windows solo funciona en Windows.

Al empaquetar, las rutas a assets/ pueden dejar de funcionar. Se soluciona con una función que decide la ruta base:

import sys
from pathlib import Path

def ruta_recurso(relativa):
    base = Path(getattr(sys, "_MEIPASS", Path(__file__).parent))
    return base / relativa

Prueba siempre el ejecutable en otro equipo antes de distribuirlo; algunos antivirus marcan falsos positivos en archivos generados con PyInstaller.

Publicar y compartir

  1. Crea una página de proyecto en itch.io (o Game Jolt).
  2. Sube un .zip por sistema operativo, con capturas de pantalla, controles y créditos.
  3. Declara las licencias de los recursos de terceros.
  4. Publica el código en GitHub con un README que explique cómo ejecutarlo.
  5. Recoge comentarios, corrige errores y lanza nuevas versiones.

Terminar y publicar un juego pequeño enseña más que empezar diez grandes. Las game jams (por ejemplo, las de itch.io) son una buena forma de practicar con plazos cortos.

Recursos para seguir aprendiendo

  • Documentación oficial de pygame: pygame.org/docs, para consultar módulos, clases y funciones.
  • Tutorial oficial de Python: docs.python.org/3/tutorial.
  • Libros: Automate the Boring Stuff with Python y Python Crash Course para el lenguaje; Making Games with Python & Pygame, de Al Sweigart, para juegos completos (comprueba que el código corresponda a pygame 2.x).
  • Comunidades: Stack Overflow y r/pygame. Al pedir ayuda, incluye el mensaje de error completo y el fragmento mínimo de código que falla.

Ruta de aprendizaje orientativa

EtapaDuración aproximada
Python básico1 a 3 semanas
Entorno y fundamentos de pygame1 semana
Pong y Serpiente1 a 2 semanas
Invasores y Plataformas2 a 4 semanas
Juego propio3 a 8 semanas
Publicación1 semana

Conclusión

Crear videojuegos con Python y pygame se reduce a dominar una idea (el bucle de eventos, lógica, dibujo y control del tiempo) y a practicarla con proyectos cada vez más ambiciosos. Empieza pequeño, escribe tú mismo el código en lugar de copiarlo, termina lo que empiezas y publica. Con constancia, en pocas semanas tendrás un juego propio que cualquiera pueda jugar.

Nota: los fragmentos de código de Pong, el esqueleto y el movimiento con dt los ejecuté y funcionan con pygame 2.6.1. Los de sprites y plataformas se probaron como parte de programas completos. Contrasta las ediciones de los libros citados con la que consultes.

Deja un comentario