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

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.
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.
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.
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.
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
Ejecutando el juego
Ahora, podemos ejecutar el juego ejecutando el siguiente comando:
cd ~/project
python aliens.py

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.



