Construir un juego de Alienígenas con Pygame

PythonPythonBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este proyecto, lo guiaremos a través del proceso de creación de un juego simple llamado "Aliens" utilizando la biblioteca Pygame. El juego consiste en defenderse de los invasores alienígenas disparándolos. Dividiremos el proceso de desarrollo en múltiples pasos, desde la configuración de los archivos del proyecto hasta la ejecución del juego terminado.

Pygame es una biblioteca popular para crear juegos 2D en Python. Proporciona funciones para manejar gráficos, sonido y entrada del usuario, lo que la convierte en una excelente opción para los principiantes interesados en el desarrollo de juegos.

👀 Vista previa

Juego de Aliens

Este juego se revisó a partir de los ejemplos de Pygame.

🎯 Tareas

En este proyecto, aprenderá:

  • Cómo configurar la estructura inicial del proyecto y cargar los recursos necesarios como imágenes y sonidos.
  • Cómo definir las clases para el personaje del jugador y los invasores alienígenas.
  • Cómo crear clases adicionales para manejar las explosiones, los disparos del jugador, las bombas de los alienígenas y la puntuación del juego.
  • Cómo inicializar el juego, cargar los recursos y configurar la ventana del juego.
  • Cómo implementar el bucle principal del juego, manejar la entrada del usuario, actualizar las entidades del juego, manejar las colisiones y dibujar la escena del juego.

🏆 Logros

Después de completar este proyecto, podrá:

  • Utilizar la biblioteca Pygame para desarrollar un juego 2D.
  • Cargar y mostrar imágenes en Pygame.
  • Manejar la entrada del usuario y controlar el movimiento del jugador.
  • Crear y actualizar entidades del juego utilizando clases de sprites.
  • Manejar las colisiones entre las entidades del juego.
  • Dibujar la escena del juego y actualizar la pantalla.
  • Reproducir efectos de sonido y música en el juego.
  • Implementar un bucle principal del juego para manejar la lógica del juego.

Crear los Archivos del Proyecto

En este paso, configuraremos la estructura inicial del proyecto y cargaremos los recursos necesarios como imágenes y sonidos.

Crea un nuevo archivo llamado aliens.py y agrega el siguiente código:

cd ~/project
touch aliens.py
sudo pip install pygame

Luego, abre el archivo en tu editor de texto favorito y agrega el siguiente código:

import os
import random
from typing import List

## importa los módulos básicos de pygame
import pygame as pg

## ve si podemos cargar más que la imagen BMP estándar
if not pg.image.get_extended():
    raise SystemExit("Lo siento, se requiere el módulo de imagen extendido")


## constantes del juego
MAX_SHOTS = 2  ## máximo de balas del jugador en pantalla
ALIEN_ODDS = 22  ## probabilidad de que aparezca un nuevo alienígena
BOMB_ODDS = 60  ## probabilidad de que caiga una nueva bomba
ALIEN_RELOAD = 12  ## frames entre nuevos alienígenas
SCREENRECT = pg.Rect(0, 0, 640, 480)
SCORE = 0

main_dir = os.path.split(os.path.abspath(__file__))[0]


def load_image(file):
    """carga una imagen, la prepara para el juego"""
    file = os.path.join(main_dir, "data", file)
    try:
        surface = pg.image.load(file)
    except pg.error:
        raise SystemExit(f'No se pudo cargar la imagen "{file}" {pg.get_error()}')
    return surface.convert()


def load_sound(file):
    """porque pygame puede compilarse sin mixer."""
    if not pg.mixer:
        return None
    file = os.path.join(main_dir, "data", file)
    try:
        sound = pg.mixer.Sound(file)
        return sound
    except pg.error:
        print(f"Advertencia, no se pudo cargar, {file}")
    return None

En este paso, importamos los módulos necesarios, definimos las constantes del juego y creamos funciones para cargar imágenes y efectos de sonido. También configuramos el directorio principal del proyecto.

En def load_image y def load_sound, usamos la función os.path.join para unir el directorio principal del proyecto con el directorio data, donde almacenaremos nuestros recursos de juego. Crearemos este directorio en el siguiente paso.

✨ Revisar Solución y Practicar

Definir las Clases del Jugador y los Alienígenas

Ahora, definiremos las clases para los personajes de nuestro juego: Player y Alien. La clase Player representa el personaje del jugador, mientras que la clase Alien representa a los invasores alienígenas.

#... (código del paso 1)

## Define la clase Player
class Player(pg.sprite.Sprite):
    """Representando al jugador como un tipo de carro de buggy lunar."""

    speed = 10
    bounce = 24
    gun_offset = -11
    images: List[pg.Surface] = []

    def __init__(self, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect(midbottom=SCREENRECT.midbottom)
        self.reloading = 0
        self.origtop = self.rect.top
        self.facing = -1

    def move(self, direction):
        if direction:
            self.facing = direction
        self.rect.move_ip(direction * self.speed, 0)
        self.rect = self.rect.clamp(SCREENRECT)
        if direction < 0:
            self.image = self.images[0]
        elif direction > 0:
            self.image = self.images[1]
        self.rect.top = self.origtop - (self.rect.left // self.bounce % 2)

    def gunpos(self):
        pos = self.facing * self.gun_offset + self.rect.centerx
        return pos, self.rect.top

Aquí, hemos definido la clase Player. Incluye atributos como velocidad, rebote y desplazamiento de la pistola. El método move maneja el movimiento del jugador. El método gunpos devuelve la posición de la pistola del jugador. La clase Player hereda de la clase pg.sprite.Sprite, que es una clase base para los objetos visibles del juego.

## Define la clase Alien
class Alien(pg.sprite.Sprite):
    """Una nave espacial alienígena. Que se mueve lentamente hacia abajo por la pantalla."""

    speed = 13
    animcycle = 12
    images: List[pg.Surface] = []

    def __init__(self, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect()
        self.facing = random.choice((-1, 1)) * Alien.speed
        self.frame = 0
        if self.facing < 0:
            self.rect.right = SCREENRECT.right

    def update(self):
        self.rect.move_ip(self.facing, 0)
        if not SCREENRECT.contains(self.rect):
            self.facing = -self.facing
            self.rect.top = self.rect.bottom + 1
            self.rect = self.rect.clamp(SCREENRECT)
        self.frame = self.frame + 1
        self.image = self.images[self.frame // self.animcycle % 3]

La clase Alien representa a los invasores alienígenas. Tiene atributos para velocidad y animación. Los alienígenas se mueven hacia la izquierda y la derecha, y sus imágenes se ciclan. El método update maneja el movimiento y la animación de los alienígenas. La clase Alien hereda de la clase pg.sprite.Sprite, que es una clase base para los objetos visibles del juego.

✨ Revisar Solución y Practicar

Definir Clases Adicionales del Juego

En este paso, definiremos clases adicionales relacionadas con el juego: Explosion, Shot, Bomb y Score. Estas clases manejan las explosiones, los disparos del jugador, las bombas de los alienígenas y la puntuación del juego.

#... (código del paso 2)

## Define la clase Explosion
class Explosion(pg.sprite.Sprite):
    """Una explosión. ¡Ojalá sea el alienígena y no el jugador!"""

    defaultlife = 12
    animcycle = 3
    images: List[pg.Surface] = []

    def __init__(self, actor, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect(center=actor.rect.center)
        self.life = self.defaultlife

    def update(self):
        """llamado cada vez que se recorre el bucle del juego.

        Muestra la superficie de la explosión durante 'defaultlife'.
        En cada actualización del juego (tick), disminuimos la 'life'.

        También animamos la explosión.
        """
        self.life = self.life - 1
        self.image = self.images[self.life // self.animcycle % 2]
        if self.life <= 0:
            self.kill()

## Define la clase Shot
class Shot(pg.sprite.Sprite):
    """una bala que dispara el sprite del jugador."""

    speed = -11
    images: List[pg.Surface] = []

    def __init__(self, pos, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect(midbottom=pos)

    def update(self):
        """llamado cada vez que se recorre el bucle del juego.

        En cada tick movemos la bala hacia arriba.
        """
        self.rect.move_ip(0, self.speed)
        if self.rect.top <= 0:
            self.kill()

## Define la clase Bomb
class Bomb(pg.sprite.Sprite):
    """Una bomba que dejan caer los alienígenas."""

    speed = 9
    images: List[pg.Surface] = []

    def __init__(self, alien, explosion_group, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect(midbottom=alien.rect.move(0, 5).midbottom)
        self.explosion_group = explosion_group

    def update(self):
        """llamado cada vez que se recorre el bucle del juego.

        En cada fotograma movemos el rectángulo del sprite hacia abajo.
        Cuando llega al fondo:

        - hacemos una explosión.
        - eliminamos la Bomba.
        """
        self.rect.move_ip(0, self.speed)
        if self.rect.bottom >= 470:
            Explosion(self, self.explosion_group)
            self.kill()

## Define la clase Score
class Score(pg.sprite.Sprite):
    """para llevar un registro de la puntuación."""

    def __init__(self, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.font = pg.font.Font(None, 20)
        self.font.set_italic(1)
        self.color = "white"
        self.lastscore = -1
        self.update()
        self.rect = self.image.get_rect().move(10, 450)

    def update(self):
        """Solo actualizamos la puntuación en update() cuando ha cambiado."""
        if SCORE!= self.lastscore:
            self.lastscore = SCORE
            msg = f"Puntuación: {SCORE}"
            self.image = self.font.render(msg, 0, self.color)

Estas clases manejan diferentes aspectos del juego, como las explosiones, los disparos, las bombas y la visualización de la puntuación del jugador.

✨ Revisar Solución y Practicar

Inicializar el Juego y Cargar los Recursos

Ahora, continuaremos inicializando el juego y cargando los recursos necesarios como imágenes y sonidos.

#... (código del paso 3)

def main(winstyle=0):
    ## Inicializar pygame
    if pg.get_sdl_version()[0] == 2:
        pg.mixer.pre_init(44100, 32, 2, 1024)
    pg.init()
    if pg.mixer and not pg.mixer.get_init():
        print("Advertencia, no hay sonido")
        pg.mixer = None

    fullscreen = False
    ## Establecer el modo de visualización
    winstyle = 0  ## |FULLSCREEN
    bestdepth = pg.display.mode_ok(SCREENRECT.size, winstyle, 32)
    screen = pg.display.set_mode(SCREENRECT.size, winstyle, bestdepth)

    ## Cargar imágenes, asignar a las clases de sprites
    ## (haz esto antes de usar las clases, después de la configuración de la pantalla)
    img = load_image("player1.gif")
    Player.images = [img, pg.transform.flip(img, 1, 0)]
    img = load_image("explosion1.gif")
    Explosion.images = [img, pg.transform.flip(img, 1, 1)]
    Alien.images = [load_image(im) for im in ("alien1.gif", "alien2.gif", "alien3.gif")]
    Bomb.images = [load_image("bomb.gif")]
    Shot.images = [load_image("shot.gif")]

Aquí, inicializamos Pygame, configuramos la ventana del juego y cargamos imágenes para el jugador, los alienígenas, las explosiones, las bombas y los disparos.

✨ Revisar Solución y Practicar

Completar la Configuración del Juego y el Bucle Principal

Ahora, completaremos la configuración del juego y crearemos el bucle principal del juego, donde tendrá lugar toda la lógica y las interacciones del juego.

#... (código del paso 4)

def main(winstyle=0):
    #... (código anterior)

    ## decorar la ventana del juego
    icon = pg.transform.scale(Alien.images[0], (32, 32))
    pg.display.set_icon(icon)
    pg.display.set_caption("Pygame Aliens")
    pg.mouse.set_visible(0)

    ## crear el fondo, repetir la imagen de fondo
    bgdtile = load_image("background.gif")
    background = pg.Surface(SCREENRECT.size)
    for x in range(0, SCREENRECT.width, bgdtile.get_width()):
        background.blit(bgdtile, (x, 0))
    screen.blit(background, (0, 0))
    pg.display.flip()

    ## cargar los efectos de sonido
    boom_sound = load_sound("boom.wav")
    shoot_sound = load_sound("car_door.wav")
    if pg.mixer:
        music = os.path.join(main_dir, "data", "house_lo.wav")
        pg.mixer.music.load(music)
        pg.mixer.music.play(-1)

    ## Inicializar los Grupos del Juego
    aliens = pg.sprite.Group()
    shots = pg.sprite.Group()
    bombs = pg.sprite.Group()
    all = pg.sprite.RenderUpdates()
    lastalien = pg.sprite.GroupSingle()

    ## Crear Algunos Valores Iniciales
    alienreload = ALIEN_RELOAD
    clock = pg.time.Clock()

    ## inicializar nuestros sprites de inicio
    global SCORE
    player = Player(all)
    Alien(
        aliens, all, lastalien
    )  ## nota, esto 'vive' porque entra en un grupo de sprites
    if pg.font:
        all.add(Score(all))

    ## Ejecutar nuestro bucle principal mientras el jugador esté vivo.
    while player.alive():
        ## obtener la entrada
        for event in pg.event.get():
            if event.type == pg.QUIT:
                return
            if event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE:
                return
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_f:
                    if not fullscreen:
                        print("Cambiando a Pantalla Completa")
                        screen_backup = screen.copy()
                        screen = pg.display.set_mode(
                            SCREENRECT.size, winstyle | pg.FULLSCREEN, bestdepth
                        )
                        screen.blit(screen_backup, (0, 0))
                    else:
                        print("Cambiando al modo ventana")
                        screen_backup = screen.copy()
                        screen = pg.display.set_mode(
                            SCREENRECT.size, winstyle, bestdepth
                        )
                        screen.blit(screen_backup, (0, 0))
                    pg.display.flip()
                    fullscreen = not fullscreen

        keystate = pg.key.get_pressed()

        ## limpiar/borrar los sprites dibujados anteriormente
        all.clear(screen, background)

        ## actualizar todos los sprites
        all.update()

        ## manejar la entrada del jugador
        direction = keystate[pg.K_RIGHT] - keystate[pg.K_LEFT]
        player.move(direction)
        firing = keystate[pg.K_SPACE]
        if not player.reloading and firing and len(shots) < MAX_SHOTS:
            Shot(player.gunpos(), shots, all)
            if pg.mixer and shoot_sound is not None:
                shoot_sound.play()
        player.reloading = firing

        ## Crear un nuevo alienígena
        if alienreload:
            alienreload = alienreload - 1
        elif not int(random.random() * ALIEN_ODDS):
            Alien(aliens, all, lastalien)
            alienreload = ALIEN_RELOAD

        ## Dejar caer bombas
        if lastalien and not int(random.random() * BOMB_ODDS):
            Bomb(lastalien.sprite, all, bombs, all)

        ## Detectar colisiones entre alienígenas y jugadores.
        for alien in pg.sprite.spritecollide(player, aliens, 1):
            if pg.mixer and boom_sound is not None:
                boom_sound.play()
            Explosion(alien, all)
            Explosion(player, all)
            SCORE = SCORE + 1
            player.kill()

        ## Ver si los disparos impactan a los alienígenas.
        for alien in pg.sprite.groupcollide(aliens, shots, 1, 1).keys():
            if pg.mixer and boom_sound is not None:
                boom_sound.play()
            Explosion(alien, all)
            SCORE = SCORE + 1

        ## Ver si las bombas de los alienígenas impactan al jugador.
        for bomb in pg.sprite.spritecollide(player, bombs, 1):
            if pg.mixer and boom_sound is not None:
                boom_sound.play()
            Explosion(player, all)
            Explosion(bomb, all)
            player.kill()

        ## dibujar la escena
        dirty = all.draw(screen)
        pg.display.update(dirty)

        ## limitar la tasa de fotogramas a 40fps. También llamada 40HZ o 40 veces por segundo.
        clock.tick(40)

    if pg.mixer:
        pg.mixer.music.fadeout(1000)
    pg.time.wait(1000)


## llamar a la función "main" si se está ejecutando este script
if __name__ == "__main__":
    main()
    pg.quit()

En la función principal, inicializamos los grupos del juego para varias entidades, creamos valores iniciales y implementamos el bucle principal del juego. La lógica del juego, el manejo de la entrada del jugador, las colisiones y la representación de la escena del juego se realizan dentro de este bucle.

Ejecutar el Juego

Ahora, podemos ejecutar el juego ejecutando el siguiente comando:

cd ~/project
python aliens.py
✨ Revisar Solución y Practicar

Ejecutando el Juego

Ahora, podemos ejecutar el juego ejecutando el siguiente comando:

cd ~/project
python aliens.py
Captura de pantalla de la ejecución del juego

Resumen

En este proyecto, hemos dividido el proceso de construcción de un sencillo juego de "Alienígenas" utilizando Pygame en múltiples pasos. Hemos configurado los archivos del proyecto, definido las clases del jugador y los alienígenas, creado clases adicionales relacionadas con el juego e inicializado el juego con los recursos. Finalmente, hemos implementado el bucle principal del juego para manejar la lógica y las interacciones del juego.

En los pasos siguientes, continuaremos agregando funcionalidad a las clases del jugador y los alienígenas, manejando el movimiento y el disparo y actualizando los elementos visuales del juego.