Construir un juego de laberinto con Pygame

PythonBeginner
Practicar Ahora

This tutorial is from open-source community. Access the source code

Introducción

En este proyecto, crearemos un juego de laberinto utilizando la librería Pygame en Python. El juego consiste en guiar a un jugador a través de un laberinto para recoger elementos de comida mientras evita los muros. Dividiremos el proceso de desarrollo en múltiples pasos para hacerlo más fácil de entender y seguir.

👀 Vista previa

Captura de pantalla de vista previa del juego de laberinto

🎯 Tareas

En este proyecto, aprenderá:

  • Cómo configurar el entorno del juego utilizando Pygame
  • Cómo crear el laberinto utilizando celdas y muros
  • Cómo agregar elementos de comida para que el jugador los recoja
  • Cómo implementar el movimiento del jugador y la detección de colisiones
  • Cómo manejar la lógica del juego, incluyendo la puntuación y las condiciones de fin de juego
  • Cómo llevar un registro del récord del jugador
  • Cómo mostrar estadísticas del juego como el tiempo, la puntuación y el récord en la pantalla

🏆 Logros

Después de completar este proyecto, podrá:

  • Utilizar la librería Pygame para el desarrollo de juegos
  • Aplicar conceptos de programación orientada a objetos para crear elementos de juego
  • Demostrar pensamiento algorítmico y habilidades de resolución de problemas para la generación de laberintos
  • Manejar el procesamiento de eventos y la entrada del jugador
  • Implementar la detección de colisiones y la mecánica de movimiento en un entorno de juego
  • Administrar el manejo de archivos para almacenar y recuperar registros de juego
  • Mostrar estadísticas e información del juego en la pantalla

Configurar el entorno

Primero, crearemos los archivos del proyecto para el juego de Laberinto.

cd ~/proyecto
touch laberinto.py
sudo pip install pygame

En este paso, configuraremos el entorno de Pygame y definiremos constantes.

import pygame
from random import choice, randrange

## Constantes para las dimensiones de la pantalla y el tamaño de las baldosas
RES = ANCHO, ALTO = 1202, 902
TILE = 100
columnas, filas = ANCHO // TILE, ALTO // TILE

## El resto de su código irá aquí...

En este paso:

  • Importamos las bibliotecas necesarias (Pygame y random).
  • Definimos constantes para las dimensiones de la pantalla y el tamaño de las baldosas.
  • Inicializamos Pygame y configuramos la ventana del juego.
  • Cargamos las imágenes de fondo para el juego.

Creando la clase Celda

En este paso, definiremos la clase Celda para representar las celdas del laberinto.

## Define una clase para representar celdas en el laberinto
class Celda:
    def __init__(self, x, y):
        self.x, self.y = x, y
        ## Las paredes representan los límites de la celda
        self.paredes = {"arriba": True, "derecha": True, "abajo": True, "izquierda": True}
        self.visitada = False
        self.grosor = 4

    ## Dibuja las paredes de la celda
    def dibujar(self, sc):
        x, y = self.x * TILE, self.y * TILE
        if self.paredes["arriba"]:
            pygame.draw.line(
                sc, pygame.Color("naranja oscuro"), (x, y), (x + TILE, y), self.grosor
            )
        if self.paredes["derecha"]:
            pygame.draw.line(
                sc,
                pygame.Color("naranja oscuro"),
                (x + TILE, y),
                (x + TILE, y + TILE),
                self.grosor,
            )
        if self.paredes["abajo"]:
            pygame.draw.line(
                sc,
                pygame.Color("naranja oscuro"),
                (x + TILE, y + TILE),
                (x, y + TILE),
                self.grosor,
            )
        if self.paredes["izquierda"]:
            pygame.draw.line(
                sc, pygame.Color("naranja oscuro"), (x, y + TILE), (x, y), self.grosor
            )

    ## Obtiene los rectángulos que representan cada pared de la celda
    def obtener_rectangulos(self):
        rectangulos = []
        x, y = self.x * TILE, self.y * TILE
        if self.paredes["arriba"]:
            rectangulos.append(pygame.Rect((x, y), (TILE, self.grosor)))
        if self.paredes["derecha"]:
            rectangulos.append(pygame.Rect((x + TILE, y), (self.grosor, TILE)))
        if self.paredes["abajo"]:
            rectangulos.append(pygame.Rect((x, y + TILE), (TILE, self.grosor)))
        if self.paredes["izquierda"]:
            rectangulos.append(pygame.Rect((x, y), (self.grosor, TILE)))
        return rectangulos

    ## Verifica si existe una celda vecina
    def verificar_celda(self, x, y):
        encontrar_indice = lambda x, y: x + y * columnas
        if x < 0 or x > columnas - 1 or y < 0 or y > filas - 1:
            return False
        return self.celdas_grid[encontrar_indice(x, y)]

    ## Obtiene las celdas vecinas que no han sido visitadas
    def verificar_vecinos(self, celdas_grid):
        self.celdas_grid = celdas_grid
        vecinos = []
        arriba = self.verificar_celda(self.x, self.y - 1)
        derecha = self.verificar_celda(self.x + 1, self.y)
        abajo = self.verificar_celda(self.x, self.y + 1)
        izquierda = self.verificar_celda(self.x - 1, self.y)
        if arriba and not arriba.visitada:
            vecinos.append(arriba)
        if derecha and not derecha.visitada:
            vecinos.append(derecha)
        if abajo and not abajo.visitada:
            vecinos.append(abajo)
        if izquierda and not izquierda.visitada:
            vecinos.append(izquierda)
        return choice(vecinos) if vecinos else False

## El resto de su código irá aquí...

En este paso:

  • Definimos la clase Celda con sus propiedades y métodos para dibujar paredes y verificar vecinos.

Eliminando paredes y generando el laberinto

En este paso, crearemos funciones para eliminar paredes y generar el laberinto.

## Función para eliminar las paredes entre dos celdas adyacentes
def eliminar_paredes(actual, siguiente):
    dx = actual.x - siguiente.x
    if dx == 1:
        actual.paredes["izquierda"] = False
        siguiente.paredes["derecha"] = False
    elif dx == -1:
        actual.paredes["derecha"] = False
        siguiente.paredes["izquierda"] = False
    dy = actual.y - siguiente.y
    if dy == 1:
        actual.paredes["arriba"] = False
        siguiente.paredes["abajo"] = False
    elif dy == -1:
        actual.paredes["abajo"] = False
        siguiente.paredes["arriba"] = False


## Función para generar el laberinto
def generar_laberinto():
    celdas_grid = [Celda(col, fila) for fila in range(filas) for col in range(columnas)]
    celda_actual = celdas_grid[0]
    array = []
    contador_rompimiento = 1

    while contador_rompimiento!= len(celdas_grid):
        celda_actual.visitada = True
        siguiente_celda = celda_actual.verificar_vecinos(celdas_grid)
        if siguiente_celda:
            siguiente_celda.visitada = True
            contador_rompimiento += 1
            array.append(celda_actual)
            eliminar_paredes(celda_actual, siguiente_celda)
            celda_actual = siguiente_celda
        elif array:
            celda_actual = array.pop()
    return celdas_grid

## El resto de su código irá aquí...

En este paso:

  • Definimos la función eliminar_paredes para eliminar las paredes entre celdas adyacentes.
  • Creamos la función generar_laberinto para generar el laberinto utilizando un algoritmo de búsqueda en profundidad.

Agregando comida al juego

En este paso, crearemos una clase Comida para agregar elementos de comida al juego.

## Clase para representar la comida en el juego
class Comida:
    def __init__(self):
        ## Cargar la imagen de la comida
        self.img = pygame.image.load("img/comida.png").convert_alpha()
        self.img = pygame.transform.scale(self.img, (TILE - 10, TILE - 10))
        self.rect = self.img.get_rect()
        self.set_pos()

    ## Establecer la posición de la comida de forma aleatoria
    def set_pos(self):
        self.rect.topleft = randrange(columnas) * TILE + 5, randrange(filas) * TILE + 5

    ## Dibujar la comida en la pantalla
    def dibujar(self):
        superficie_juego.blit(self.img, self.rect)

## El resto de su código irá aquí...

En este paso:

  • Definimos la clase Comida con métodos para establecer la posición y dibujar los elementos de comida.

Movimiento del jugador y detección de colisiones

En este paso, configuraremos los controles del jugador, su movimiento y la detección de colisiones.

## Verifica si el jugador choca con las paredes
def es_colision(x, y):
    rect_tmp = rect_jugador.move(x, y)
    if rect_tmp.collidelist(lista_colisiones_paredes) == -1:
        return False
    return True

## El resto de su código irá aquí...

En este paso:

  • Definimos la función es_colision para verificar si el jugador choca con las paredes.

Juego y Puntuación

En este paso, implementaremos la lógica del juego, incluyendo comer comida y puntuar.

## Verifica si el jugador ha comido alguna comida
def comer_comida():
    for comida in lista_comida:
        if rect_jugador.collidepoint(comida.rect.center):
            comida.set_pos()
            return True
    return False


## Verifica si el juego ha terminado (se agota el tiempo)
def esta_terminado_el_juego():
    global tiempo, puntuacion, record, FPS
    if tiempo < 0:
        pygame.time.wait(700)
        rect_jugador.center = TILE // 2, TILE // 2
        [comida.set_pos() for comida in lista_comida]
        establecer_record(record, puntuacion)
        record = obtener_record()
        tiempo, puntuacion, FPS = 60, 0, 60

## El resto de su código irá aquí...

En este paso:

  • Definimos la función comer_comida para verificar si el jugador ha comido alguna comida.
  • Creamos la función esta_terminado_el_juego para verificar si el juego ha terminado cuando se agota el tiempo.

Manejo de registros

En este paso, implementaremos la conservación de registros para el juego.

## Función para obtener el registro actual de un archivo
def obtener_registro():
    try:
        with open("registro") as f:
            return f.readline()
    except FileNotFoundError:
        with open("registro", "w") as f:
            f.write("0")
            return "0"

## Función para establecer y actualizar el registro en un archivo
def establecer_registro(registro, puntuación):
    rec = max(int(registro), puntuación)
    with open("registro", "w") as f:
        f.write(str(rec))

## El resto de su código irá aquí...

En este paso:

  • Definimos funciones para recuperar el registro actual de un archivo y actualizarlo.

Inicialización del juego

En este paso, realizaremos las tareas de inicialización del juego.

## Inicializar Pygame y configurar la ventana del juego
FPS = 60
pygame.init()
superficie_juego = pygame.Surface(RES)
superficie = pygame.display.set_mode((ANCHO + 300, ALTO))
reloj = pygame.time.Clock()

## Cargar las imágenes de fondo
fondo_juego = pygame.image.load("img/fondo_1.jpg").convert()
fondo = pygame.image.load("img/fondo_principal.jpg").convert()

## Generar el laberinto
laberinto = generar_laberinto()

## Configuración del jugador
velocidad_jugador = 5
imagen_jugador = pygame.image.load("img/0.png").convert_alpha()
imagen_jugador = pygame.transform.scale(
    imagen_jugador, (TILE - 2 * laberinto[0].grosor, TILE - 2 * laberinto[0].grosor)
)
rect_jugador = imagen_jugador.get_rect()
rect_jugador.center = TILE // 2, TILE // 2
direcciones = {
    "a": (-velocidad_jugador, 0),
    "d": (velocidad_jugador, 0),
    "w": (0, -velocidad_jugador),
    "s": (0, velocidad_jugador),
}
teclas = {"a": pygame.K_LEFT, "d": pygame.K_RIGHT, "w": pygame.K_UP, "s": pygame.K_DOWN}
direccion = (0, 0)

## Configuración de la comida
lista_comida = [Comida() for i in range(3)]

## Crear una lista de rectángulos que representan las paredes para la detección de colisiones
lista_colisiones_paredes = sum([celda.get_rects() for celda in laberinto], [])

## Temporizador, puntuación y récord
pygame.time.set_timer(pygame.USEREVENT, 1000)
tiempo = 60
puntuacion = 0
record = obtener_record()

## Fuentes
fuente = pygame.font.SysFont("Impact", 150)
fuente_texto = pygame.font.SysFont("Impact", 80)

## El resto de su código irá aquí...

En este paso:

  • Realizamos varias tareas de inicialización, incluyendo configurar Pygame, cargar imágenes, generar el laberinto e inicializar variables relacionadas con el jugador y la comida.

Bucle principal del juego

En este paso, configuraremos el bucle principal del juego y mostraremos los elementos del juego.

## Bucle principal del juego
while True:
    ## Dibuja las imágenes de fondo
    superficie.blit(fondo, (ANCHO, 0))
    superficie.blit(superficie_juego, (0, 0))
    superficie_juego.blit(fondo_juego, (0, 0))

    for evento in pygame.event.get():
        if evento.type == pygame.QUIT:
            exit()
        if evento.type == pygame.USEREVENT:
            tiempo -= 1

    ## Maneja los controles y el movimiento del jugador
    tecla_presionada = pygame.key.get_pressed()
    for tecla, valor_tecla in teclas.items():
        if tecla_presionada[valor_tecla] and not es_colision(*direcciones[tecla]):
            direccion = direcciones[tecla]
            break
    if not es_colision(*direccion):
        rect_jugador.move_ip(direccion)

    ## Dibuja el laberinto
    [celda.dibujar(superficie_juego) for celda in laberinto]

    ## Juego: Verifica si el jugador ha comido comida y si el juego ha terminado
    if comer_comida():
        FPS += 10
        puntuacion += 1
    esta_terminado_el_juego()

    ## Dibuja al jugador
    superficie_juego.blit(imagen_jugador, rect_jugador)

    ## Dibuja los elementos de comida
    [comida.dibujar() for comida in lista_comida]

    ## El resto de su código irá aquí...

En este paso:

  • Configuramos el bucle principal del juego que maneja los eventos, el movimiento del jugador y la representación gráfica del juego.

Mostrando Estadísticas del Juego

En este paso, mostraremos las estadísticas del juego en la pantalla.

    ## Dibuja las estadísticas del juego
    superficie.blit(
        fuente_texto.render("TIEMPO", True, pygame.Color("cian"), True), (ANCHO + 70, 30)
    )
    superficie.blit(fuente.render(f"{tiempo}", True, pygame.Color("cian")), (ANCHO + 70, 130))
    superficie.blit(
        fuente_texto.render("puntuación:", True, pygame.Color("verde bosque"), True),
        (ANCHO + 50, 350),
    )
    superficie.blit(
        fuente.render(f"{puntuacion}", True, pygame.Color("verde bosque")), (ANCHO + 70, 430)
    )
    superficie.blit(
        fuente_texto.render("récord:", True, pygame.Color("magenta"), True),
        (ANCHO + 30, 620),
    )
    superficie.blit(
        fuente.render(f"{record}", True, pygame.Color("magenta")), (ANCHO + 70, 700)
    )

    pygame.display.flip()
    reloj.tick(FPS)

En este paso:

  • Utilizamos fuentes para mostrar información relacionada con el juego, como el tiempo, la puntuación y el récord.
  • Utilizamos el método blit() para dibujar el texto en la pantalla.
  • Utilizamos el método flip() para actualizar la pantalla.

Ejecutar el juego

Ahora que hemos completado todos los pasos, podemos ejecutar el juego del laberinto con el siguiente comando:

cd ~/proyecto
python laberinto.py

Captura de pantalla de la ejecución del juego del laberinto

Resumen

En este proyecto, hemos dividido el proceso de construir un juego de laberinto utilizando Pygame en diez pasos claros y manejables. Aprenderás cómo configurar el entorno del juego, crear las celdas del laberinto, generar el laberinto, manejar el movimiento del jugador y la detección de colisiones, implementar la dinámica del juego y la puntuación, gestionar los registros y mucho más. Siguiendo estos pasos, podrás crear un juego de laberinto completamente funcional en Python.

✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar✨ Revisar Solución y Practicar