Introducción
En este proyecto, dividiremos el código para crear un juego simple de Flappy Bird utilizando la librería Pygame en pasos manejables. Siguiendo estos pasos, aprenderás cómo construir el juego gradualmente. Cada paso incluirá una breve explicación, bloques de código y comentarios para ayudarte a entender e implementar el juego. ¡Comencemos!
👀 Vista previa

🎯 Tareas
En este proyecto, aprenderás:
- Cómo configurar los archivos del proyecto para el juego Flappy Bird
- Cómo mostrar la animación de bienvenida del juego
- Cómo implementar la lógica principal del juego de Flappy Bird
- Cómo mostrar la pantalla de fin de juego cuando el jugador pierde
- Cómo definir funciones auxiliares para el juego
🏆 Logros
Después de completar este proyecto, podrás:
- Utilizar la librería Pygame para crear juegos
- Comprender conceptos de desarrollo de juegos como los bucles de juego, las colisiones y la animación
Crear los archivos del proyecto
Primero, crearemos los archivos del proyecto para el juego Flappy Bird.
cd ~/project
touch flappy.py
sudo pip install pygame
En este paso, configuraremos la estructura básica del proyecto e importaremos las bibliotecas necesarias. También definiremos algunas constantes y cargaremos los activos iniciales del juego.
from itertools import cycle
import random
import sys
import pygame
from pygame.locals import *
FPS = 30
SCREENWIDTH = 288
SCREENHEIGHT = 512
PIPEGAPSIZE = 100 ## gap between upper and lower part of pipe
BASEY = SCREENHEIGHT * 0.79
## image and hitmask dicts
IMAGES, HITMASKS = {}, {}
## list of all possible players (tuple of 3 positions of flap)
PLAYERS_LIST = (
## red bird
(
"data/sprites/redbird-upflap.png",
"data/sprites/redbird-midflap.png",
"data/sprites/redbird-downflap.png",
),
## blue bird
(
"data/sprites/bluebird-upflap.png",
"data/sprites/bluebird-midflap.png",
"data/sprites/bluebird-downflap.png",
),
## yellow bird
(
"data/sprites/yellowbird-upflap.png",
"data/sprites/yellowbird-midflap.png",
"data/sprites/yellowbird-downflap.png",
),
)
## list of backgrounds
BACKGROUNDS_LIST = (
"data/sprites/background-day.png",
"data/sprites/background-night.png",
)
## list of pipes
PIPES_LIST = (
"data/sprites/pipe-green.png",
"data/sprites/pipe-red.png",
)
- Importamos las bibliotecas necesarias para el juego, incluyendo
pygamepara crear el juego,randompara generar elementos aleatorios,syspara funciones relacionadas con el sistema ypygame.localspara constantes de teclado. - Inicializamos Pygame con
pygame.init(). - Constantes como
FPS,SCREENWIDTH,SCREENHEIGHT,PIPEGAPSIZEyBASEYse definen para configurar las dimensiones y la velocidad del juego. - Creamos diccionarios vacíos (
IMAGESyHITMASKS) para almacenar los activos del juego. - Listas de activos de jugadores, fondos y tuberías se definen utilizando rutas de archivos.
Mostrar la animación de bienvenida
En este paso, crearemos la animación de la pantalla de bienvenida del juego Flappy Bird.
def showWelcomeAnimation():
"""Muestra la animación de la pantalla de bienvenida del flappy bird"""
## índice del jugador para dibujar en la pantalla
playerIndex = 0
playerIndexGen = cycle([0, 1, 2, 1])
## iterador utilizado para cambiar el playerIndex después de cada 5ta iteración
loopIter = 0
playerx = int(SCREENWIDTH * 0.2)
playery = int((SCREENHEIGHT - IMAGES["player"][0].get_height()) / 2)
messagex = int((SCREENWIDTH - IMAGES["message"].get_width()) / 2)
messagey = int(SCREENHEIGHT * 0.12)
basex = 0
## cantidad por la que la base puede desplazarse como máximo hacia la izquierda
baseShift = IMAGES["base"].get_width() - IMAGES["background"].get_width()
## movimiento vertical del jugador en la pantalla de bienvenida
playerShmVals = {"val": 0, "dir": 1}
while True:
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
## reproducir el primer sonido de flap y devolver valores para mainGame
return {
"playery": playery + playerShmVals["val"],
"basex": basex,
"playerIndexGen": playerIndexGen,
}
## ajustar playery, playerIndex, basex
if (loopIter + 1) % 5 == 0:
playerIndex = next(playerIndexGen)
loopIter = (loopIter + 1) % 30
basex = -((-basex + 4) % baseShift)
playerShm(playerShmVals)
## dibujar sprites
SCREEN.blit(IMAGES["background"], (0, 0))
SCREEN.blit(
IMAGES["player"][playerIndex], (playerx, playery + playerShmVals["val"])
)
SCREEN.blit(IMAGES["message"], (messagex, messagey))
SCREEN.blit(IMAGES["base"], (basex, BASEY))
pygame.display.update()
FPSCLOCK.tick(FPS)
- Definimos la función
showWelcomeAnimationresponsable de mostrar la animación de la pantalla de bienvenida. - La función configura variables para la animación y maneja la entrada del usuario para iniciar el juego.
- Utiliza un bucle para actualizar los fotogramas de la animación y comprobar la entrada del usuario para comenzar el juego.
- La animación incluye el pájaro volando y un mensaje mostrado en la pantalla.
- La función
pygame.display.update()se utiliza para actualizar la pantalla, yFPSCLOCK.tick(FPS)controla la tasa de fotogramas.
Lógica principal del juego
En este paso, implementaremos la lógica principal del juego Flappy Bird.
def mainGame(movementInfo):
score = playerIndex = loopIter = 0
playerIndexGen = movementInfo["playerIndexGen"]
playerx, playery = int(SCREENWIDTH * 0.2), movementInfo["playery"]
basex = movementInfo["basex"]
baseShift = IMAGES["base"].get_width() - IMAGES["background"].get_width()
## obtener 2 nuevas tuberías para agregar a la lista de upperPipes y lowerPipes
newPipe1 = getRandomPipe()
newPipe2 = getRandomPipe()
## lista de tuberías superiores
upperPipes = [
{"x": SCREENWIDTH + 200, "y": newPipe1[0]["y"]},
{"x": SCREENWIDTH + 200 + (SCREENWIDTH / 2), "y": newPipe2[0]["y"]},
]
## lista de tuberías inferiores
lowerPipes = [
{"x": SCREENWIDTH + 200, "y": newPipe1[1]["y"]},
{"x": SCREENWIDTH + 200 + (SCREENWIDTH / 2), "y": newPipe2[1]["y"]},
]
dt = FPSCLOCK.tick(FPS) / 1000
pipeVelX = -128 * dt
## velocidad del jugador, velocidad máxima, aceleración hacia abajo, aceleración al flap
playerVelY = -9 ## velocidad del jugador a lo largo de Y, por defecto igual a playerFlapped
playerMaxVelY = 10 ## velocidad máxima a lo largo de Y, velocidad máxima de descenso
playerMinVelY = -8 ## velocidad mínima a lo largo de Y, velocidad máxima de ascenso
playerAccY = 1 ## aceleración hacia abajo del jugador
playerRot = 45 ## rotación del jugador
playerVelRot = 3 ## velocidad angular
playerRotThr = 20 ## umbral de rotación
playerFlapAcc = -9 ## velocidad del jugador al flap
playerFlapped = False ## True cuando el jugador hace flap
while True:
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
if playery > -2 * IMAGES["player"][0].get_height():
playerVelY = playerFlapAcc
playerFlapped = True
## comprobar si hay colisión aquí
crashTest = checkCrash(
{"x": playerx, "y": playery, "index": playerIndex}, upperPipes, lowerPipes
)
if crashTest[0]:
return {
"y": playery,
"groundCrash": crashTest[1],
"basex": basex,
"upperPipes": upperPipes,
"lowerPipes": lowerPipes,
"score": score,
"playerVelY": playerVelY,
"playerRot": playerRot,
}
## comprobar si hay puntaje
playerMidPos = playerx + IMAGES["player"][0].get_width() / 2
for pipe in upperPipes:
pipeMidPos = pipe["x"] + IMAGES["pipe"][0].get_width() / 2
if pipeMidPos <= playerMidPos < pipeMidPos + 4:
score += 1
## cambio de playerIndex y basex
if (loopIter + 1) % 3 == 0:
playerIndex = next(playerIndexGen)
loopIter = (loopIter + 1) % 30
basex = -((-basex + 100) % baseShift)
## rotar al jugador
if playerRot > -90:
playerRot -= playerVelRot
## movimiento del jugador
if playerVelY < playerMaxVelY and not playerFlapped:
playerVelY += playerAccY
if playerFlapped:
playerFlapped = False
## más rotación para cubrir el umbral (calculado en la rotación visible)
playerRot = 45
playerHeight = IMAGES["player"][playerIndex].get_height()
playery += min(playerVelY, BASEY - playery - playerHeight)
## mover las tuberías hacia la izquierda
for uPipe, lPipe in zip(upperPipes, lowerPipes):
uPipe["x"] += pipeVelX
lPipe["x"] += pipeVelX
## agregar una nueva tubería cuando la primera tubería está a punto de tocar el lado izquierdo de la pantalla
if 3 > len(upperPipes) > 0 and 0 < upperPipes[0]["x"] < 5:
newPipe = getRandomPipe()
upperPipes.append(newPipe[0])
lowerPipes.append(newPipe[1])
## eliminar la primera tubería si está fuera de la pantalla
if len(upperPipes) > 0 and upperPipes[0]["x"] < -IMAGES["pipe"][0].get_width():
upperPipes.pop(0)
lowerPipes.pop(0)
## dibujar sprites
SCREEN.blit(IMAGES["background"], (0, 0))
for uPipe, lPipe in zip(upperPipes, lowerPipes):
SCREEN.blit(IMAGES["pipe"][0], (uPipe["x"], uPipe["y"]))
SCREEN.blit(IMAGES["pipe"][1], (lPipe["x"], lPipe["y"]))
SCREEN.blit(IMAGES["base"], (basex, BASEY))
## mostrar el puntaje para que el jugador ocupe el puntaje
showScore(score)
## La rotación del jugador tiene un umbral
visibleRot = playerRotThr
if playerRot <= playerRotThr:
visibleRot = playerRot
playerSurface = pygame.transform.rotate(
IMAGES["player"][playerIndex], visibleRot
)
SCREEN.blit(playerSurface, (playerx, playery))
pygame.display.update()
FPSCLOCK.tick(FPS)
- Definimos la función
mainGame, que contiene la lógica central del juego Flappy Bird. - La función maneja la entrada del usuario, actualiza el estado del juego, comprueba las colisiones y lleva un registro del puntaje.
- El bucle del juego se ejecuta continuamente, actualizando la pantalla y la lógica del juego.
- Los controles del jugador se manejan a través de eventos de teclado (espacio o flecha hacia arriba).
- La función también comprueba las colisiones con las tuberías y el suelo, actualiza el puntaje y gestiona la animación del pájaro.
- El bucle del juego continúa hasta que el jugador choca o sale del juego.
Mostrar la pantalla de juego terminado
En este paso, crearemos la pantalla de fin de juego que aparece cuando el jugador pierde.
def showGameOverScreen(crashInfo):
"""Hace que el jugador caiga y muestra la imagen de fin de juego"""
score = crashInfo["score"]
playerx = SCREENWIDTH * 0.2
playery = crashInfo["y"]
playerHeight = IMAGES["player"][0].get_height()
playerVelY = crashInfo["playerVelY"]
playerAccY = 2
playerRot = crashInfo["playerRot"]
playerVelRot = 7
basex = crashInfo["basex"]
upperPipes, lowerPipes = crashInfo["upperPipes"], crashInfo["lowerPipes"]
while True:
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYDOWN and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
if event.type == KEYDOWN and (event.key == K_SPACE or event.key == K_UP):
if playery + playerHeight >= BASEY - 1:
return
## desplazamiento de y del jugador
if playery + playerHeight < BASEY - 1:
playery += min(playerVelY, BASEY - playery - playerHeight)
## cambio de velocidad del jugador
if playerVelY < 15:
playerVelY += playerAccY
## girar solo cuando es una colisión con una tubería
if not crashInfo["groundCrash"]:
if playerRot > -90:
playerRot -= playerVelRot
## dibujar sprites
SCREEN.blit(IMAGES["background"], (0, 0))
for uPipe, lPipe in zip(upperPipes, lowerPipes):
SCREEN.blit(IMAGES["pipe"][0], (uPipe["x"], uPipe["y"]))
SCREEN.blit(IMAGES["pipe"][1], (lPipe["x"], lPipe["y"]))
SCREEN.blit(IMAGES["base"], (basex, BASEY))
showScore(score)
playerSurface = pygame.transform.rotate(IMAGES["player"][1], playerRot)
SCREEN.blit(playerSurface, (playerx, playery))
SCREEN.blit(IMAGES["gameover"], (50, 180))
FPSCLOCK.tick(FPS)
pygame.display.update()
- La función
showGameOverScreenmuestra la pantalla de fin de juego cuando el jugador pierde. - Muestra el puntaje final del jugador, reproduce efectos de sonido y espera a que el jugador presione la tecla espacio o la flecha hacia arriba para reiniciar el juego.
- La animación incluye el pájaro cayendo al suelo y el mensaje de fin de juego mostrado en la pantalla.
Definir funciones auxiliares
En este paso, definimos funciones auxiliares que se utilizan en el juego.
def playerShm(playerShm):
"""Hace oscilar el valor de playerShm['val'] entre 8 y -8"""
if abs(playerShm["val"]) == 8:
playerShm["dir"] *= -1
if playerShm["dir"] == 1:
playerShm["val"] += 1
else:
playerShm["val"] -= 1
def getRandomPipe():
"""Devuelve una tubería generada aleatoriamente"""
## y del espacio entre la tubería superior e inferior
gapY = random.randrange(0, int(BASEY * 0.6 - PIPEGAPSIZE))
gapY += int(BASEY * 0.2)
pipeHeight = IMAGES["pipe"][0].get_height()
pipeX = SCREENWIDTH + 10
return [
{"x": pipeX, "y": gapY - pipeHeight}, ## tubería superior
{"x": pipeX, "y": gapY + PIPEGAPSIZE}, ## tubería inferior
]
def showScore(score):
"""Muestra el puntaje en el centro de la pantalla"""
scoreDigits = [int(x) for x in list(str(score))]
totalWidth = 0 ## ancho total de todos los números a imprimir
for digit in scoreDigits:
totalWidth += IMAGES["numbers"][digit].get_width()
Xoffset = (SCREENWIDTH - totalWidth) / 2
for digit in scoreDigits:
SCREEN.blit(IMAGES["numbers"][digit], (Xoffset, SCREENHEIGHT * 0.1))
Xoffset += IMAGES["numbers"][digit].get_width()
def checkCrash(player, upperPipes, lowerPipes):
"""Devuelve True si el jugador choca con la base o las tuberías."""
pi = player["index"]
player["w"] = IMAGES["player"][0].get_width()
player["h"] = IMAGES["player"][0].get_height()
## si el jugador choca contra el suelo
if player["y"] + player["h"] >= BASEY - 1:
return [True, True]
else:
playerRect = pygame.Rect(player["x"], player["y"], player["w"], player["h"])
pipeW = IMAGES["pipe"][0].get_width()
pipeH = IMAGES["pipe"][0].get_height()
for uPipe, lPipe in zip(upperPipes, lowerPipes):
## rectángulos de las tuberías superior e inferior
uPipeRect = pygame.Rect(uPipe["x"], uPipe["y"], pipeW, pipeH)
lPipeRect = pygame.Rect(lPipe["x"], lPipe["y"], pipeW, pipeH)
## máscaras de golpeo del jugador y las tuberías superior e inferior
pHitMask = HITMASKS["player"][pi]
uHitmask = HITMASKS["pipe"][0]
lHitmask = HITMASKS["pipe"][1]
## si el pájaro choca con la tubería superior o inferior
uCollide = pixelCollision(playerRect, uPipeRect, pHitMask, uHitmask)
lCollide = pixelCollision(playerRect, lPipeRect, pHitMask, lHitmask)
if uCollide or lCollide:
return [True, False]
return [False, False]
def pixelCollision(rect1, rect2, hitmask1, hitmask2):
"""Comprueba si dos objetos chocan y no solo sus rectángulos"""
rect = rect1.clip(rect2)
if rect.width == 0 or rect.height == 0:
return False
x1, y1 = rect.x - rect1.x, rect.y - rect1.y
x2, y2 = rect.x - rect2.x, rect.y - rect2.y
for x in range(rect.width):
for y in range(rect.height):
if hitmask1[x1 + x][y1 + y] and hitmask2[x2 + x][y2 + y]:
return True
return False
def getHitmask(image):
"""Devuelve una máscara de golpeo utilizando el canal alfa de una imagen"""
mask = []
for x in range(image.get_width()):
mask.append([])
for y in range(image.get_height()):
mask[x].append(bool(image.get_at((x, y))[3]))
return mask
- La función
playerShmhace oscilar el valor deplayerShm["val"]entre 8 y -8. Esto se utiliza para mover el pájaro hacia arriba y abajo en la pantalla de bienvenida. - La función
getRandomPipedevuelve una tubería generada aleatoriamente. Genera un espacio aleatorio entre la tubería superior e inferior. - La función
showScoremuestra el puntaje en el centro de la pantalla. Utiliza la listaIMAGES["numbers"]para mostrar el puntaje. - La función
checkCrashdevuelveTruesi el jugador choca con el suelo o las tuberías. Utiliza la funciónpixelCollisionpara comprobar las colisiones. - La función
pixelCollisioncomprueba si dos objetos chocan y no solo sus rectángulos. Utiliza la funcióngetHitmaskpara obtener la máscara de golpeo del jugador y las tuberías. - La función
getHitmaskdevuelve una máscara de golpeo utilizando el canal alfa de una imagen. Utiliza la funciónimage.get_atpara obtener el valor alfa de cada píxel en la imagen.
La función principal
En este paso, definimos la función principal que inicializa el juego y comienza el bucle del juego.
def main():
global SCREEN, FPSCLOCK
pygame.init()
FPSCLOCK = pygame.time.Clock()
SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
pygame.display.set_caption("Flappy Bird")
## sprites de números para mostrar el puntaje
IMAGES["numbers"] = (
pygame.image.load("data/sprites/0.png").convert_alpha(),
pygame.image.load("data/sprites/1.png").convert_alpha(),
pygame.image.load("data/sprites/2.png").convert_alpha(),
pygame.image.load("data/sprites/3.png").convert_alpha(),
pygame.image.load("data/sprites/4.png").convert_alpha(),
pygame.image.load("data/sprites/5.png").convert_alpha(),
pygame.image.load("data/sprites/6.png").convert_alpha(),
pygame.image.load("data/sprites/7.png").convert_alpha(),
pygame.image.load("data/sprites/8.png").convert_alpha(),
pygame.image.load("data/sprites/9.png").convert_alpha(),
)
## sprite de fin de juego
IMAGES["gameover"] = pygame.image.load("data/sprites/gameover.png").convert_alpha()
## sprite del mensaje para la pantalla de bienvenida
IMAGES["message"] = pygame.image.load("data/sprites/message.png").convert_alpha()
## sprite de la base (suelo)
IMAGES["base"] = pygame.image.load("data/sprites/base.png").convert_alpha()
while True:
## seleccionar sprites de fondo aleatorios
randBg = random.randint(0, len(BACKGROUNDS_LIST) - 1)
IMAGES["background"] = pygame.image.load(BACKGROUNDS_LIST[randBg]).convert()
## seleccionar sprites de jugador aleatorios
randPlayer = random.randint(0, len(PLAYERS_LIST) - 1)
IMAGES["player"] = (
pygame.image.load(PLAYERS_LIST[randPlayer][0]).convert_alpha(),
pygame.image.load(PLAYERS_LIST[randPlayer][1]).convert_alpha(),
pygame.image.load(PLAYERS_LIST[randPlayer][2]).convert_alpha(),
)
## seleccionar sprites de tubería aleatorios
pipeindex = random.randint(0, len(PIPES_LIST) - 1)
IMAGES["pipe"] = (
pygame.transform.flip(
pygame.image.load(PIPES_LIST[pipeindex]).convert_alpha(), False, True
),
pygame.image.load(PIPES_LIST[pipeindex]).convert_alpha(),
)
## máscara de golpeo para las tuberías
HITMASKS["pipe"] = (
getHitmask(IMAGES["pipe"][0]),
getHitmask(IMAGES["pipe"][1]),
)
## máscara de golpeo para el jugador
HITMASKS["player"] = (
getHitmask(IMAGES["player"][0]),
getHitmask(IMAGES["player"][1]),
getHitmask(IMAGES["player"][2]),
)
movementInfo = showWelcomeAnimation()
crashInfo = mainGame(movementInfo)
showGameOverScreen(crashInfo)
- La función
maininicializa el juego, configura la pantalla y comienza el bucle del juego. - Carga los activos del juego, incluyendo imágenes, y selecciona aleatoriamente los sprites de fondo, jugador y tubería.
- El bucle del juego maneja la animación de bienvenida, el juego principal y la pantalla de fin de juego.
- El juego continúa en un bucle hasta que el jugador sale del juego o cierra la ventana.
Ejecutar el juego
En este paso, ejecutaremos el juego Flappy Bird.
if __name__ == "__main__":
main()
- La condición
__name__ == "__main__"comprueba si el módulo actual se está ejecutando por sí mismo o se ha importado por otro módulo. - Si el módulo actual se está ejecutando por sí mismo, se llama a la función
mainpara iniciar el juego.
Una vez que hayas completado todos los pasos, puedes ejecutar el juego Flappy Bird con el siguiente comando:
cd ~/project
python flappy.py

Resumen
En este proyecto, hemos dividido el código del juego Flappy Bird en múltiples pasos y hemos proporcionado explicaciones para cada paso. Has aprendido cómo crear una estructura básica de juego, manejar la entrada del jugador, actualizar el estado del juego, comprobar colisiones y mostrar pantallas de juego. Ahora puedes ejecutar y jugar al juego Flappy Bird con el código proporcionado. ¡Disfruta de tu viaje de desarrollo de juegos!



