Space Invaders con Python y Pygame
Jorge Martínez Garrido
January 31, 2022
Vamos a diseñar el popular juego conocido como “Space Invaders” utilizando para ello la librería Pygame.

No vamos a complicar mucho el juego: bastará con crear una matriz de aliens con forma cuadrada que poco a poco se vayan acercando al límite inferior de la pantalla. En este mismo lugar se encuentra el tanque, modelado como un rectángulo sencillo. El tanque puede disparar misiles sencillos, también modelados como rectángulos.
Las reglas del juego son sencillas:
- Si destruyes todos los aliens habrás ganado la partida.
- Si un alien toca la tierra habrás perdido.
Atacando el problema
En el juego podemos identificar los siguientes objectos:
- Los aliens: aparecen como cuadrados de color amarillo y se poco a poco se acercan a la tierra.
- El tanque: aparece en color gris y puede moverse solamente en horizontal.
- Los misiles: modelandos como puntos en color rojo que se mueven en línea recta.
A continuación se muestra un dibujo que representa todos los elementos anteriores junto con las dimensiones de la pantalla:

Para simplificar el código vamos a utilizar toda la potencia de la programación orientada a objectos que Python nos brinda. Así pues, vamos a crear un total de cinco archivos:
alien.py
: que alojará una clase para modelar a los aliens y todo lo relacionado con ellos.tanque.py
: mismo propósito que para los aliens pero en este caso para el tanque.misil.py
: sigue la misma idea que para los tanques.main.py
: script principal y punto de entrada para todo el código; aquí arranca el juego.
Creando la clase Alien
Vamos a modelar los aliens como rectángulos. Para poder crear un alien necesitamos conocer:
- Dónde se encuentra, es decir, su posición $x$ e $y$.
- Qué dimensión tiene, es decir, su ancho y largo.
- Qué color tiene.
A la clase alien también le podemos añadir un método llamado dibujar(self ,ventana)
. Cuando llamemos a dicho método, el alien será dibujado en la pantalla. Recuerda: en Pygame hay que crear primero el objecto geométrico (rectángulo, polígono, círuclo…) y luego llamar a la función correspondiente para poder dibujarlo en la pantalla.
Por otro lado, la clase también tendrá otro método para comprobar si el alien ha llegado a la Tierra. Para ello, deberemos comprobar que la posición vertical del borde inferior del cuadrado que representa al alien no supera el límite inferior de la pantalla.
Conocido todo lo anterior, podemos escribir la siguiente clase dentro del módulo alien.py
:
# %load src/alien.py
# +
import pygame
class Alien:
"""Una clase para modelar los aliens de Space Invaders."""
def __init__(self, posicion, tamaño, color=(255, 255, 0), velocidad=0.01):
"""
Crea un alien conocidos su posición, tamaño y color.
Parameters
----------
posicion: tuple
Coordenadas x e y para la posición del alien.
tamaño: tuple
Ancho y largo para la forma del alien.
color: tuple
Una tupla con los valores RGB para el color del alien.
velocidad: float
Número de pixeles que avanza el alien en cada refresco del juego.
"""
# Almacenamos los valores proporcionados
self.x_pos, self.y_pos = posicion
self.ancho, self.alto = tamaño
self.color = color
self.velocidad = velocidad
def dibujar(self, ventana):
"""
Permite dibujar el alien en la ventana o superfície de Pygame deseada.
Parameters
----------
ventana: ~pygame.Surface
Ventana o superfícia donde se mostrará el alien.
"""
# Dibujamos el alien en la ventana deseada
self.forma = pygame.Rect(self.x_pos, self.y_pos, self.ancho, self.alto)
pygame.draw.rect(ventana, self.color, self.forma)
# Actualizamos su posición, es decir, forzamos que baje hacia el borde inferior de la pantalla
# Esto simula la invasión.
self.y_pos += self.velocidad
def ha_llegado_al_suelo(self, alto_ventana):
"""
Devuelve verdadero o falso en caso de que el alien haya llegado a la Tierra.
Parameters
----------
alto_ventana: int
Alto de la ventana en pixeles.
"""
# El alien llega a la Tierra cuando la posición de su borde inferior es igual al límite
# inferior de la pantalla.
y_pos_borde_inferior_alien = self.y_pos + self.alto
# Tamaño vertical de la pantalla
return True if y_pos_borde_inferior_alien >= alto_ventana else False
def ha_sido_abatido(self, misil):
"""
Comprueba si el alien ha sido abatido por el misil.
Parameters
----------
misil: ~Misil
Instancia de la clase misil.
"""
return self.forma.collidepoint(misil.x_pos, misil.y_pos)
pygame 2.5.2 (SDL 2.28.2, Python 3.8.10)
Hello from the pygame community. https://www.pygame.org/contribute.html
Creando la clase Tanque
Vamos a seguir la misma idea para generar el tanque. En lugar de actualizar la posición de forma automática como hicimos con los aliens, vamos a añadir en esta clase dos métodos: mover_derecha(self)
y mover_izquierda(self)
. El incremento en píxeles de los dos movimientos anteriores vendrá definido por el parámetro velocidad
, el cual se proporciona a la hora de crear la instancia.
Así pues, podemos generar el siguiente código en el módulo tanque.py
:
# %load src/tanque.py
# +
import pygame
class Tanque:
"""Una clase para modelar el tanque en Space Invaders."""
def __init__(self, posicion, tamaño, color=(128, 128, 128), velocidad=0.15):
"""
Crea un tanque conocidos su posición, tamaño, color y velocidad.
Parameters
----------
posicion: tuple
Coordenadas x e y para la posición del tanque.
tamaño: tuple
Ancho y alto del rectángulo para el tanque.
color: tuple
Color en RGB para el tanque.
velocidad: float
Velocidad horizontal del tanque.
"""
# Asignamos todos los valores anteriores
self.x_pos, self.y_pos = posicion
self.ancho, self.alto = tamaño
self.color = color
self.velocidad = velocidad
def dibujar(self, ventana):
"""
Permite dibujar el tanque en la ventana o superfície de Pygame deseada.
Parameters
----------
ventana: ~pygame.Surface
Ventana o superfícia donde se mostrará el alien.
"""
# Generamos el rectángulo que representa el tanque
self.forma = pygame.Rect(self.x_pos, self.y_pos, self.ancho, self.alto)
# Dibujamos el alien en la ventana deseada
pygame.draw.rect(ventana, self.color, self.forma)
def mover_derecha(self, x_lim):
"""
Actualiza la posición horizontal del tanque moviéndolo a la derecha. Si excede el límite,
la posición no se actualiza.
Parameters
----------
x_lim: float
Límite horizontal izquierdo.
"""
# Actualizamos su posición horizontal hacia la derecha
if not self.x_pos + self.velocidad > x_lim:
self.x_pos += self.velocidad
def mover_izquierda(self, x_lim):
"""
Actualiza la posición horizontal del tanque moviéndolo a la izquierda. Si excede el límite,
la posición no se actualiza.
Parameters
----------
x_lim: float
Límite horizontal izquierdo.
"""
# Actualizamos su posición horizontal hacia la derecha
if not self.x_pos - self.velocidad < x_lim:
self.x_pos -= self.velocidad
Creando la clase Misil
Siguiento la misma línea anteiror, vamos a crear una clase misil. Esta será muy parecidad a la clase Alien
, ya que tiene un desplazamiento vertical que se realiza de forma automática. La única diferencia es que un misíl se mueve de abajo hacia arriba en la pantalla y es el objeto con la mayor velocidad de todos.
A continuación se presenta el código fuente para los misiles:
# %load src/misil.py
# +
import pygame
class Misil:
"""Una clase para modelar los misiles de Space Invaders."""
def __init__(self, posicion, tamaño, color=(255, 0, 0), velocidad=0.35):
"""
Crea un alien conocidos su posición, tamaño y color.
Parameters
----------
posicion: tuple
Coordenadas x e y para la posición del misil.
tamaño: float
Tamaño del radio para la forma del misil.
color: tuple
Una tupla con los valores RGB para el color del misil.
velocidad: float
Número de pixeles que avanza el misil avanza en cada refresco del juego.
"""
# Almacenamos los valores proporcionados
self.x_pos, self.y_pos = posicion
self.radio = tamaño
self.color = color
self.velocidad = velocidad
def dibujar(self, ventana):
"""
Permite dibujar el alien en la ventana o superfície de Pygame deseada.
Parameters
----------
ventana: ~pygame.Surface
Ventana o superfícia donde se mostrará el alien.
"""
# Dibujamos el misil en la ventana deseada. Fíjate que la posición hay que pasarla como
# una tupla tal que (x_pos, y_pos), pues así lo pide Pygame.
pygame.draw.circle(ventana, self.color, (self.x_pos, self.y_pos), self.radio)
# Actualizamos su posición para que avance hacia arriba
self.y_pos -= self.velocidad
Creando la ventana y la lógica del juego
Vamos a comenzar creando una ventana en Pygame que se cierre cuando el jugador así lo indique. Por ejemplo, si el jugador presiona la tecla “ESC” o hace click en botón de cerrar ventana. Los pasos a seguir durante la creación y ejecución de una pantalla son los siguientes:
- Inicializamos Pygame para que se encarge de gestionar todo.
- Creamos la pantalla con la resoluión y título deseados.
- Mostramos la pantalla en un bucle que no termina a menos que el usuario lo indique.
- Gestionamos los eventos: clicks, teclas pulsadas…
- Una vez cerrada la ventana, cerramos Pygame.
Comenzamos importando pygame en nuestro código:
Función para crear una ventana
Vamos a crear una función para generar una ventana con las dimensiones deseadas y el título que nos apezca. Para ello, simplemente llamamos a las rutinas set_mode
y set_caption
de Pygame:
def crear_ventana(resolucion, titulo):
"""
Crea una ventana conocida la resolución y le añade el título deseado.
Parameters
----------
resolucion: tuple
Valores para el ancho y alto de la pantalla.
titulo: str
Texto a mostrar como título de la ventana.
Returns
-------
ventana: pygame.Surface
Devuelve la ventana creada.
"""
ventana = pygame.display.set_mode(resolucion)
pygame.display.set_caption(titulo)
return ventana
Renderizando la ventana y recogiendo los eventos
Renderizar significa, de forma muy simplificada, “mostrar en pantalla”. Así que vamos a renderizar la ventana junto con un color de fondo. El color elegido será el negro, para poder simular el cielo nocturno. Siguiendo los pasos presentados anteriormente podemos obtener el siguiente código:
import pygame
def main():
"""Función principal del juego."""
# Resolución de la pantalla
ANCHO, ALTO = 640, 480
# Creamos la ventana y definimos una variable lógica para comprobar si deseamos cerrarla
resolucion = (ANCHO, ALTO)
ventana = crear_ventana(resolucion, "Space Invaders en la EPM")
cerrar_ventana, game_over = False, False
# Creamos todos los aliens formando una escuadra de 3 columnas x 7 aliens en fila
lista_aliens = [
Alien((50 * x_alien, 50 * y_alien), (25, 25))
for y_alien in range(1, 3+1)
for x_alien in range(1, 11+1)
]
# Creamos el tanque, que inicia en la posición central inferior
tanque = Tanque((ANCHO / 2, 0.95 * ALTO), (40, 10))
# Creamos una lista vacía de misiles
lista_misiles = []
# Creamos el bucle principal para mostrar la ventana
while not cerrar_ventana and not game_over:
# Pintamos el fondo de color negro RGB -> (0, 0, 0)
ventana.fill((0, 0, 0))
# Recogemos los eventos
for evento in pygame.event.get():
# Comprobamos si se desea cerrar la ventanta
if evento.type == pygame.QUIT: cerrar_ventana = True
# Evaluamos los controles
if evento.type == pygame.KEYDOWN:
# Generamos un nuevo misil si es necesario
if evento.key == pygame.K_SPACE:
lista_misiles.append(
Misil((tanque.x_pos + tanque.ancho / 2, tanque.y_pos), 1)
)
# Mueve el tanque de forma fluida mientras se presione la tecla correcta
teclas_presionadas = pygame.key.get_pressed()
if teclas_presionadas[pygame.K_RIGHT]: tanque.mover_derecha(x_lim=0.95 * ANCHO)
if teclas_presionadas[pygame.K_LEFT]: tanque.mover_izquierda(x_lim=0.05 * ANCHO)
# Game over si un alien llega al suelo. Comprobamos impactos de misiles
for alien in lista_aliens:
# Comprobamos si algún alien ha llegado al suelo
if alien.ha_llegado_al_suelo(ALTO):
game_over = True
break
else:
# Comprobamos que el alien no ha sido abatido por ningún misil
if len(lista_misiles) > 0:
for misil in lista_misiles:
if alien.ha_sido_abatido(misil):
# Eliminamos el alien abatido junto con el misil
lista_aliens.remove(alien)
lista_misiles.remove(misil)
# Dibujamos los alient que hayan sobrevivido
for alien in lista_aliens: alien.dibujar(ventana)
# Dibujamos el tanque
tanque.dibujar(ventana)
# Dibujamos todos los misiles
for misil in lista_misiles: misil.dibujar(ventana)
# Actualizamos toda la escena
pygame.display.update()
Finalmente, iniciamos el juego mediante las siguientes líneas de código:
pygame.init()
main()
pygame.quit()
Resultado
Tal y como muestra la animación de abajo, soy bastante malo jugando al Space Invaders! Eso sí, la verdad que el juego está entretenido. Además, con todo lo que hemos aprendido, puedes aplicar la misma lógica por ejemplo para otros juegos clásicos como el Pong Game o el Arkanoid.

¿Puedes mejorar el código?
Fíjate que un misil sólo se elimina de la lista cuando impacta. ¿Qué pasa si no lo hace? ¡Pues que se queda almacenado dentro de la variable lista_misiles
hasta el final.
- Ejercicio: ¿puedes conseguir que el juego elimine un misil si éste no ha impactado en un alien y está fuera de los límites de la pantalla?