Ir al contenido

Showcase

Crear videojuegos

Aprende a hacer juegos 2D en Python con Pyzed, desde tu primer bucle de juego hasta plataformas, shooters y puzles completos: una ruta paso a paso con ejemplos animados, consejos y tres juegos terminados.

Pyzed es una librería de interfaces, pero ya trae todo lo que necesita un juego 2D: un pz.canvas dibujado por la GPU, un bucle que se ejecuta una vez por fotograma, las teclas que están pulsadas ahora mismo y toda la biblioteca de widgets para construir menús y HUD encima de la acción. Cada juego de estas páginas es un programa completo y jugable en examples/games/, comprobado por las pruebas en cada cambio.

Una ruta desde cero hasta juegos completos. Cada paso es un tutorial con un objetivo, el tiempo que lleva, lo que necesitas saber antes, el código completo e ideas para seguir. Si nunca has escrito un juego, empieza por el 1; si ya lo has hecho, salta a lo que necesites.

flowchart LR
    A["1 · Bucle y teclas"] --> B["2 · Sprites y animación"] --> C["3 · Colisiones y física"]
    C --> D["4 · Plataformas"]
    C --> E["5 · Asteroids"]
    B --> F["6 · Puzle: 2048"]
    D --> G["7 · Menús, HUD, ajustes"]
    E --> G
    F --> G
    G --> H["8 · Guardado y sonido"]

Tres juegos más grandes para leer, jugar y desmontar. Crecen en dificultad:

Ejecuta cualquier juego desde la carpeta del repositorio; cambia el nombre del archivo para los demás:

Windows, macOS y Linux
uv run --with pyzed-ui python examples/games/neon_invaders.py
Qué API Para qué
Bucle por fotograma pz.frames(callback) en subscriptions() callback(dt) una vez por fotograma (sincronizado con la pantalla); dt son los segundos desde el anterior.
Temporizador fijo pz.every(segundos, callback) Juegos por turnos o en cuadrícula que avanzan a ritmo fijo (Snake).
Pulsaciones on_key(combo) Acciones puntuales: saltar, pausar, reiniciar ("space", "enter", "ctrl-r").
Soltar teclas on_key_up(key) Mecánicas de cargar y soltar, parar un sonido.
Teclas mantenidas pz.pressed(), pz.is_pressed("left", "a") Movimiento continuo y disparo automático, consultados en cada fotograma.
Movimiento game.axis(neg, pos), game.vector() -1/0/1 a partir de dos grupos de teclas; una dirección normalizada con flechas/WASD.
Dibujo pz.canvas([...]) con pyzed.draw Rectángulos, círculos, trazados, texto, transformaciones.
Sprites draw.image(src, x, y, w, h) y game.sprite(filas, paleta) Imágenes (se decodifican una vez y quedan en caché) o pixel art escrito como texto.
Colisiones game.Rect, game.circle_rect Solapamiento de cajas; por dónde golpea una pelota a una caja, para rebotar.
Efectos game.glow, game.Particles, game.Shake Luces neón, explosiones y chispas, temblor de pantalla.
HUD y menús pz.stack([canvas, widgets]) Puntuaciones, barras de vida y pantallas de pausa con glass, bloom y animaciones.

Un juego es un pz.App normal. Devuelve pz.frames(self.tick) desde subscriptions() y el runtime llama a tick(dt) antes de cada fotograma. Mueve las cosas velocidad × dt para que vayan igual de rápido a 60, 120 o 144 Hz:

import pyzed as pz
from pyzed import draw, game
class Ship(pz.App):
size = (480, 320)
def __init__(self):
self.x, self.y = 240, 160
def subscriptions(self):
return [pz.frames(self.tick)]
def tick(self, dt):
dx, dy = game.vector() # flechas / WASD, diagonales normalizadas
self.x += dx * 300 * dt
self.y += dy * 300 * dt
def view(self):
return pz.canvas([draw.background("#0b0e14"),
*game.glow(self.x, self.y, 14, "#22d3ee")], width=480, height=320)
pz.run(Ship)

dt se limita a un cuarto de segundo, así que arrastrar la ventana o parar en un breakpoint nunca hace que el juego salte hacia delante. Donde la pantalla no espera a la sincronización vertical (renderizado por software), max_fps (240 por defecto) evita que el bucle consuma toda la CPU.

on_key es para acciones que ocurren una vez por pulsación; pz.pressed() para lo que ocurre mientras una tecla está pulsada. Ambos ven todas las teclas, tenga el foco el widget que lo tenga.

def on_key(self, combo): # "space", "p", "shift-r"...
if combo == "p":
self.paused = not self.paused
def tick(self, dt):
if pz.is_pressed("space") and self.cooldown <= 0:
self.fire() # disparo automático mientras Space está pulsada

El ratón llega desde el propio canvas: on_move(x, y), on_press(x, y), on_release(), on_right_press(x, y) y on_scroll(dx, dy), en coordenadas del canvas.

draw.image dibuja un PNG/JPEG (o bytes RGBA) dentro de una caja, opcionalmente girado y con transparencia. Se decodifica una vez por origen y queda en caché, así que dibujar el mismo sprite cientos de veces por fotograma es barato. Usa pixelated=True para pixel art ampliado.

draw.image("assets/ship.png", self.x - 16, self.y - 16, 32, 32, rotation=self.angle)

Para juegos retro ni siquiera hacen falta archivos: game.sprite convierte texto en pixel art, con una forma por color sea cual sea el tamaño del sprite.

INVADER = ["..#.....#..",
"...#...#...",
"..#######..",
".##.###.##.",
"###########"]
game.sprite(INVADER, {"#": "#a3e635"}, x, y, scale=4)

Apila widgets sobre el canvas con pz.stack. Puntuaciones, barras de vida, insignias y menús de pausa son componentes normales de Pyzed, así que tienen temas, paneles glass, bloom y animaciones de entrada sin esfuerzo: la parte difícil de la interfaz de un juego ya está hecha.

hud = pz.container(pz.row([score, pz.hspace(), ui.badge(f"Wave {self.wave}")]),
padding=(8, 14)).style(glass=True, radius=12)
return pz.stack([self.scene(), hud], width=W, height=H)

Más en Consejos para crear juegos.

  • Haz poco trabajo Python por fotograma: listas y números simples, sin crear estructuras grandes cada vez.
  • Agrupa formas: draw.rects dibuja muchos rectángulos de un color como una sola forma (es lo que usa game.sprite).
  • Limita las partículas (game.Particles(limit=...)) y elimina las balas que salen de la pantalla.
  • Solo el canvas cambia en cada fotograma; los widgets del HUD son baratos de reconstruir, pero pz.memo puede cachear los costosos.
  • Prueba la lógica sin ventana: llama a tick(1 / 60) en un bucle y comprueba el estado, como hace tests/test_game.py. App.screenshot renderiza cualquier momento del juego a un PNG.