Introduction
In this project, we will break down the code for creating a simple Flappy Bird game using the Pygame library into manageable steps. By following these steps, you will learn how to build the game gradually. Each step will include a brief explanation, code blocks, and comments to help you understand and implement the game. Let's get started!
👀 Preview

🎯 Tasks
In this project, you will learn:
- How to set up the project files for the Flappy Bird game
- How to show the welcome animation for the game
- How to implement the main game logic for Flappy Bird
- How to display the game over screen when the player loses
- How to define helper functions for the game
🏆 Achievements
After completing this project, you will be able to:
- Use the Pygame library to create games
- Understand game development concepts such as game loops, collisions, and animation
Create the Project Files
First, we'll create the project files for the Flappy Bird game.
cd ~/project
touch flappy.py
sudo pip install pygame
In this step, we'll set up the basic project structure and import necessary libraries. We'll also define some constants and load initial game assets.
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",
)
- We import the necessary libraries for the game, including
pygamefor creating the game,randomfor generating random elements,sysfor system-related functions, andpygame.localsfor key constants. - We initialize Pygame with
pygame.init(). - Constants like
FPS,SCREENWIDTH,SCREENHEIGHT,PIPEGAPSIZE, andBASEYare defined to set up the game's dimensions and speed. - We create empty dictionaries (
IMAGESandHITMASKS) to store game assets. - Lists of player, background, and pipe assets are defined using file paths.
Show the Welcome Animation
In this step, we'll create the welcome screen animation of the Flappy Bird game.
def showWelcomeAnimation():
"""Shows welcome screen animation of flappy bird"""
## index of player to blit on screen
playerIndex = 0
playerIndexGen = cycle([0, 1, 2, 1])
## iterator used to change playerIndex after every 5th iteration
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
## amount by which base can maximum shift to left
baseShift = IMAGES["base"].get_width() - IMAGES["background"].get_width()
## player shm for up-down motion on welcome screen
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):
## make first flap sound and return values for mainGame
return {
"playery": playery + playerShmVals["val"],
"basex": basex,
"playerIndexGen": playerIndexGen,
}
## adjust playery, playerIndex, basex
if (loopIter + 1) % 5 == 0:
playerIndex = next(playerIndexGen)
loopIter = (loopIter + 1) % 30
basex = -((-basex + 4) % baseShift)
playerShm(playerShmVals)
## draw 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)
- We define the
showWelcomeAnimationfunction responsible for displaying the welcome screen animation. - The function sets up variables for animation and handles user input to start the game.
- It uses a loop to update the animation frames and check for user input to begin the game.
- The animation includes the bird flapping and a message displayed on the screen.
- The
pygame.display.update()function is used to update the display, andFPSCLOCK.tick(FPS)controls the frame rate.
Main Game Logic
In this step, we'll implement the main game logic for 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()
## get 2 new pipes to add to upperPipes lowerPipes list
newPipe1 = getRandomPipe()
newPipe2 = getRandomPipe()
## list of upper pipes
upperPipes = [
{"x": SCREENWIDTH + 200, "y": newPipe1[0]["y"]},
{"x": SCREENWIDTH + 200 + (SCREENWIDTH / 2), "y": newPipe2[0]["y"]},
]
## list of lowerpipe
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
## player velocity, max velocity, downward acceleration, acceleration on flap
playerVelY = -9 ## player's velocity along Y, default same as playerFlapped
playerMaxVelY = 10 ## max vel along Y, max descend speed
playerMinVelY = -8 ## min vel along Y, max ascend speed
playerAccY = 1 ## players downward acceleration
playerRot = 45 ## player's rotation
playerVelRot = 3 ## angular speed
playerRotThr = 20 ## rotation threshold
playerFlapAcc = -9 ## players speed on flapping
playerFlapped = False ## True when player flaps
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
## check for crash here
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,
}
## check for score
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
## playerIndex basex change
if (loopIter + 1) % 3 == 0:
playerIndex = next(playerIndexGen)
loopIter = (loopIter + 1) % 30
basex = -((-basex + 100) % baseShift)
## rotate the player
if playerRot > -90:
playerRot -= playerVelRot
## player's movement
if playerVelY < playerMaxVelY and not playerFlapped:
playerVelY += playerAccY
if playerFlapped:
playerFlapped = False
## more rotation to cover the threshold (calculated in visible rotation)
playerRot = 45
playerHeight = IMAGES["player"][playerIndex].get_height()
playery += min(playerVelY, BASEY - playery - playerHeight)
## move pipes to left
for uPipe, lPipe in zip(upperPipes, lowerPipes):
uPipe["x"] += pipeVelX
lPipe["x"] += pipeVelX
## add new pipe when first pipe is about to touch left of screen
if 3 > len(upperPipes) > 0 and 0 < upperPipes[0]["x"] < 5:
newPipe = getRandomPipe()
upperPipes.append(newPipe[0])
lowerPipes.append(newPipe[1])
## remove first pipe if its out of the screen
if len(upperPipes) > 0 and upperPipes[0]["x"] < -IMAGES["pipe"][0].get_width():
upperPipes.pop(0)
lowerPipes.pop(0)
## draw 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))
## print score so player overlaps the score
showScore(score)
## Player rotation has a threshold
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)
- We define the
mainGamefunction, which contains the core logic for the Flappy Bird game. - The function handles user input, updates the game state, checks for collisions, and keeps track of the score.
- The game loop runs continuously, updating the game's display and logic.
- Player controls are handled through key events (space or up arrow).
- The function also checks for collisions with pipes and the ground, updates the score, and manages the animation of the bird.
- The game loop continues until the player crashes or exits the game.
Display the Game Over Screen
In this step, we'll create the game over screen that appears when the player loses.
def showGameOverScreen(crashInfo):
"""crashes the player down and shows gameover image"""
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
## player y shift
if playery + playerHeight < BASEY - 1:
playery += min(playerVelY, BASEY - playery - playerHeight)
## player velocity change
if playerVelY < 15:
playerVelY += playerAccY
## rotate only when it's a pipe crash
if not crashInfo["groundCrash"]:
if playerRot > -90:
playerRot -= playerVelRot
## draw 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()
- The
showGameOverScreenfunction displays the game over screen when the player loses. - It shows the player's final score, plays sound effects, and waits for the player to press space or up arrow to restart the game.
- The animation includes the bird falling to the ground and the game over message displayed on the screen.
Define Helper Functions
In this step, we define helper functions that are used in the game.
def playerShm(playerShm):
"""oscillates the value of playerShm['val'] between 8 and -8"""
if abs(playerShm["val"]) == 8:
playerShm["dir"] *= -1
if playerShm["dir"] == 1:
playerShm["val"] += 1
else:
playerShm["val"] -= 1
def getRandomPipe():
"""returns a randomly generated pipe"""
## y of gap between upper and lower pipe
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}, ## upper pipe
{"x": pipeX, "y": gapY + PIPEGAPSIZE}, ## lower pipe
]
def showScore(score):
"""displays score in center of screen"""
scoreDigits = [int(x) for x in list(str(score))]
totalWidth = 0 ## total width of all numbers to be printed
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):
"""returns True if player collides with base or pipes."""
pi = player["index"]
player["w"] = IMAGES["player"][0].get_width()
player["h"] = IMAGES["player"][0].get_height()
## if player crashes into ground
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):
## upper and lower pipe rects
uPipeRect = pygame.Rect(uPipe["x"], uPipe["y"], pipeW, pipeH)
lPipeRect = pygame.Rect(lPipe["x"], lPipe["y"], pipeW, pipeH)
## player and upper/lower pipe hitmasks
pHitMask = HITMASKS["player"][pi]
uHitmask = HITMASKS["pipe"][0]
lHitmask = HITMASKS["pipe"][1]
## if bird collided with upipe or lpipe
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):
"""Checks if two objects collide and not just their rects"""
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):
"""returns a hitmask using an image's alpha."""
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
- The
playerShmfunction oscillates the value ofplayerShm["val"]between 8 and -8. This is used to move the bird up and down on the welcome screen. - The
getRandomPipefunction returns a randomly generated pipe. It generates a random gap between the upper and lower pipes. - The
showScorefunction displays the score in the center of the screen. It uses theIMAGES["numbers"]list to display the score. - The
checkCrashfunction returnsTrueif the player collides with the ground or pipes. It uses thepixelCollisionfunction to check for collisions. - The
pixelCollisionfunction checks if two objects collide and not just their rects. It uses thegetHitmaskfunction to get the hitmask for the player and pipes. - The
getHitmaskfunction returns a hitmask using an image's alpha. It uses theimage.get_atfunction to get the alpha value of each pixel in the image.
The Main Function
In this step, we define the main function that initializes the game and starts the game loop.
def main():
global SCREEN, FPSCLOCK
pygame.init()
FPSCLOCK = pygame.time.Clock()
SCREEN = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
pygame.display.set_caption("Flappy Bird")
## numbers sprites for score display
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(),
)
## game over sprite
IMAGES["gameover"] = pygame.image.load("data/sprites/gameover.png").convert_alpha()
## message sprite for welcome screen
IMAGES["message"] = pygame.image.load("data/sprites/message.png").convert_alpha()
## base (ground) sprite
IMAGES["base"] = pygame.image.load("data/sprites/base.png").convert_alpha()
while True:
## select random background sprites
randBg = random.randint(0, len(BACKGROUNDS_LIST) - 1)
IMAGES["background"] = pygame.image.load(BACKGROUNDS_LIST[randBg]).convert()
## select random player sprites
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(),
)
## select random pipe sprites
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(),
)
## hitmask for pipes
HITMASKS["pipe"] = (
getHitmask(IMAGES["pipe"][0]),
getHitmask(IMAGES["pipe"][1]),
)
## hitmask for player
HITMASKS["player"] = (
getHitmask(IMAGES["player"][0]),
getHitmask(IMAGES["player"][1]),
getHitmask(IMAGES["player"][2]),
)
movementInfo = showWelcomeAnimation()
crashInfo = mainGame(movementInfo)
showGameOverScreen(crashInfo)
- The
mainfunction initializes the game, sets up the display, and starts the game loop. - It loads game assets, including images, and randomly selects background, player, and pipe sprites.
- The game loop handles the welcome animation, the main game, and the game over screen.
- The game continues to loop until the player exits the game or closes the window.
Run the Game
In this step, we'll run the Flappy Bird game.
if __name__ == "__main__":
main()
- The
__name__ == "__main__"condition checks if the current module is being run by itself or imported by another module. - If the current module is being run by itself, the
mainfunction is called to start the game.
Once you've completed all the steps, you can run the Flappy Bird game using the following command:
cd ~/project
python flappy.py

Summary
In this project, we've split the Flappy Bird game code into multiple steps and provided explanations for each step. You've learned how to create a basic game structure, handle player input, update game state, check for collisions, and display game screens. You can now run and play the Flappy Bird game using the provided code. Enjoy your game development journey!



