Welcome, aspiring game developers and Python enthusiasts! Have you ever played the incredibly addictive game “Flappy Bird” and wondered how it works? Or maybe you just want to build something fun and interactive using Python? You’re in the right place!
In this tutorial, we’re going to dive into the exciting world of game development with Python and create our very own simple clone of Flappy Bird. This project is perfect for beginners, as it covers fundamental game development concepts like game loops, player movement, collision detection, and scorekeeping. We’ll be using a fantastic Python library called Pygame, which makes creating games surprisingly straightforward.
Get ready to make a bird flap, pipes scroll, and a high score climb!
What is Flappy Bird, Anyway?
For those who might not know, Flappy Bird is a simple yet incredibly challenging mobile game. You control a little bird that constantly falls due to gravity. Your goal is to tap the screen (or press a key) to make the bird flap its wings and move upwards, navigating through gaps in a series of pipes that move towards it. If the bird touches a pipe, the ground, or the top of the screen, it’s game over! The longer you survive, the higher your score.
It’s a perfect game to recreate for learning because it involves several core game mechanics in a simple package.
Getting Started: Setting Up Your Environment
Before we can start coding, we need to make sure you have Python installed on your computer. If you don’t, head over to the official Python website and follow the installation instructions.
Once Python is ready, our next step is to install Pygame.
Installing Pygame
Pygame is a set of Python modules designed for writing video games. It includes computer graphics and sound libraries. Installing it is super easy using Python’s package installer, pip.
Open your terminal or command prompt and type the following command:
pip install pygame
Supplementary Explanation:
* pip (Python Package Installer): This is a tool that helps you install and manage additional Python libraries and packages that aren’t included with Python by default. Think of it as an app store for Python!
* pygame: This is the specific library we’re installing. It provides all the tools we need to draw things on the screen, play sounds, handle user input, and manage the timing of our game.
After a moment, Pygame should be installed and ready to go!
The Game’s Foundation: Pygame Setup and Game Loop
Every game has a main loop that continuously runs, checking for inputs, updating game elements, and drawing everything on the screen. Let’s set up the basic structure.
import pygame
import sys
import random
pygame.init()
SCREEN_WIDTH = 576
SCREEN_HEIGHT = 1024
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Flappy Python")
clock = pygame.time.Clock()
game_active = True # To control if the game is running or in a "game over" state
while True:
# 4. Event Handling (Checking for user input, like closing the window)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit() # Uninitialize Pygame modules
sys.exit() # Exit the program
# 5. Drawing (e.g., background)
screen.fill((78, 192, 204)) # Fill the screen with a light blue color
# 6. Update the display
pygame.display.update()
# 7. Control the frame rate
clock.tick(120) # Our game will run at a maximum of 120 frames per second
Supplementary Explanations:
* import pygame and import sys and import random: These lines bring in the necessary libraries. pygame for game functions, sys for system-specific parameters and functions (like exiting the program), and random for generating random numbers (useful for pipe positions).
* pygame.init(): This line initializes all the Pygame modules. You need to call this before using any Pygame functions.
* pygame.display.set_mode((width, height)): This creates the game window. We’re setting it to 576 pixels wide and 1024 pixels tall.
* pygame.display.set_caption("Flappy Python"): This sets the title that appears in the window’s title bar.
* pygame.time.Clock(): This object helps us control the frame rate of our game, ensuring it runs smoothly on different computers.
* while True:: This is our main game loop. Everything inside this loop will run repeatedly as long as the game is active.
* for event in pygame.event.get():: Pygame uses an “event system” to detect things like keyboard presses, mouse clicks, or the user closing the window. This loop checks for any new events that have occurred.
* if event.type == pygame.QUIT:: This checks if the specific event that occurred was the user clicking the ‘X’ button to close the window.
* pygame.quit() and sys.exit(): These lines gracefully shut down Pygame and then terminate the Python program. It’s important to clean up resources properly.
* screen.fill((78, 192, 204)): This command fills the entire screen with a solid color. The numbers (78, 192, 204) represent an RGB color code for a light blue.
* pygame.display.update(): This command takes everything you’ve drawn in the current frame and makes it visible on the screen. Without this, you wouldn’t see anything!
* clock.tick(120): This tells Pygame to pause the loop if it’s running too fast, so the game doesn’t exceed 120 frames per second (FPS). This keeps the game speed consistent.
If you run this code now, you’ll see an empty light blue window pop up – that’s our game canvas!
Bringing the Bird to Life
Now for our star character: the bird! We’ll represent it as a rectangle for simplicity and give it some basic movement.
Add these variables after your clock definition:
bird_surface = pygame.Rect(100, SCREEN_HEIGHT / 2 - 25, 50, 50) # x, y, width, height
bird_movement = 0
gravity = 0.25
And update your while True loop to include the bird’s logic after screen.fill():
if game_active:
# 1. Apply gravity to the bird
bird_movement += gravity
bird_surface.centery += bird_movement
# 2. Draw the bird
pygame.draw.rect(screen, (255, 255, 0), bird_surface) # Yellow bird
# 3. Check for collisions with top/bottom (simplified for now)
if bird_surface.top < 0 or bird_surface.bottom > SCREEN_HEIGHT:
game_active = False # Game over!
Supplementary Explanations:
* pygame.Rect(x, y, width, height): This creates a Rect object, which is a very useful Pygame object for representing rectangular areas. It’s great for drawing simple shapes and checking for collisions. Here, we create a 50×50 pixel rectangle for our bird.
* bird_movement: This variable will store the bird’s vertical speed. A positive value means falling, a negative value means rising.
* gravity: This constant value will be added to bird_movement in each frame, simulating the constant downward pull of gravity.
* bird_surface.centery += bird_movement: This line updates the bird’s vertical position based on its current bird_movement. centery refers to the y-coordinate of the center of the rectangle.
* pygame.draw.rect(screen, color, rect_object): This function draws a filled rectangle on the screen. We’re drawing our bird_surface in yellow (255, 255, 0).
* bird_surface.top < 0 and bird_surface.bottom > SCREEN_HEIGHT: These conditions check if the bird has gone above the top edge or below the bottom edge of the screen. If it has, game_active becomes False, effectively ending the game.
Now, if you run the code, you’ll see a yellow square falling down and disappearing off the bottom, then the window will freeze (because game_active is False and no new drawing happens). This is a good start!
Making the Bird Flap: User Input
Our bird needs to flap! We’ll add an event check to make it jump when the spacebar is pressed.
Modify the for event loop to include this:
# Inside the Game Loop -> for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN: # Check for any key press
if event.key == pygame.K_SPACE and game_active: # If the pressed key is SPACE
bird_movement = 0 # Reset any current downward movement
bird_movement = -8 # Give the bird an upward push
Supplementary Explanations:
* event.type == pygame.KEYDOWN: This checks if the event that occurred was a key being pressed down.
* event.key == pygame.K_SPACE: This specifically checks if the pressed key was the spacebar. Pygame uses K_ followed by the key name for keyboard constants.
* bird_movement = -8: When the spacebar is pressed, we set the bird’s vertical movement to a negative value. Remember, in computer graphics, smaller Y-values are usually higher up on the screen, so a negative movement value makes the bird go upwards.
Now, when you run the game, you can press the spacebar to make the bird jump! Try to keep it from hitting the top or bottom of the screen.
Introducing the Pipes
The pipes are crucial for the Flappy Bird challenge. We’ll create a list to hold multiple pipes and make them move.
Add these variables after your bird_movement and gravity definitions:
pipe_list = []
PIPE_SPEED = 3
PIPE_WIDTH = 70
PIPE_GAP = 200 # Vertical gap between top and bottom pipe
SPAWNPIPE = pygame.USEREVENT
pygame.time.set_timer(SPAWNPIPE, 1200) # Spawn a pipe every 1200 milliseconds (1.2 seconds)
We need a function to create a new pipe pair:
def create_pipe():
random_pipe_pos = random.choice([300, 400, 500, 600, 700]) # Y-center of the gap
bottom_pipe = pygame.Rect(SCREEN_WIDTH, random_pipe_pos + PIPE_GAP / 2, PIPE_WIDTH, SCREEN_HEIGHT - random_pipe_pos - PIPE_GAP / 2)
top_pipe = pygame.Rect(SCREEN_WIDTH, 0, PIPE_WIDTH, random_pipe_pos - PIPE_GAP / 2)
return bottom_pipe, top_pipe
And functions to move and draw the pipes:
def move_pipes(pipes):
for pipe in pipes:
pipe.centerx -= PIPE_SPEED
# Remove pipes that have moved off screen to save resources
return [pipe for pipe in pipes if pipe.right > -50] # Keep pipes visible until they are way off screen
def draw_pipes(pipes):
for pipe in pipes:
if pipe.bottom >= SCREEN_HEIGHT: # This is a bottom pipe
pygame.draw.rect(screen, (0, 128, 0), pipe) # Green pipe
else: # This is a top pipe
pygame.draw.rect(screen, (0, 128, 0), pipe) # Green pipe
Now, integrate these into your game loop.
* Add a new if event.type == SPAWNPIPE: condition to your event loop.
* Call move_pipes and draw_pipes within the game_active block.
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE and game_active:
bird_movement = 0
bird_movement = -8
if event.type == SPAWNPIPE and game_active: # If our custom pipe spawn event occurs
pipe_list.extend(create_pipe()) # Add the new pipe pair to our list
if game_active:
# ... bird movement and drawing ...
# Pipe logic
pipe_list = move_pipes(pipe_list)
draw_pipes(pipe_list)
Supplementary Explanations:
* pipe_list: This will store all the pygame.Rect objects for our pipes.
* PIPE_SPEED, PIPE_WIDTH, PIPE_GAP: Constants for controlling how fast pipes move, their width, and the size of the gap between top and bottom pipes.
* pygame.USEREVENT: This is a special event type you can define for your own custom events. We use it to create a timer.
* pygame.time.set_timer(SPAWNPIPE, 1200): This tells Pygame to trigger our SPAWNPIPE event every 1200 milliseconds (1.2 seconds). This way, new pipes appear automatically.
* random.choice([...]): This function from the random module picks a random item from a list. We use it to get a random vertical position for our pipe gaps.
* create_pipe(): This function calculates the positions for the top and bottom pipes based on a random gap center and returns them as two Rect objects.
* move_pipes(pipes): This function iterates through all pipes in the pipes list and moves each one to the left by PIPE_SPEED. It also creates a new list, only keeping pipes that are still on-screen or just about to enter (pipe.right > -50).
* draw_pipes(pipes): This function draws all the pipes in the list as green rectangles. We use a simple check (pipe.bottom >= SCREEN_HEIGHT) to differentiate between top and bottom pipes for visual clarity, even though they are drawn the same for now.
Now you have a bird that flaps and pipes that endlessly scroll!
Collision Detection and Game Over
The bird needs to crash! We’ll add a more robust collision check between the bird and the pipes.
Add this function after your draw_pipes function:
def check_collision(pipes):
for pipe in pipes:
if bird_surface.colliderect(pipe): # Check if bird's rect overlaps with pipe's rect
return False # Collision detected, game over
return True # No collision
Now, within your game_active block in the game loop, modify the collision check:
if game_active:
# ... bird movement, drawing, pipe movement, drawing ...
# Collision check
game_active = check_collision(pipe_list) # Check for pipe collisions
# Also check for collision with top/bottom screen edges
if bird_surface.top < 0 or bird_surface.bottom > SCREEN_HEIGHT:
game_active = False # Game over!
Supplementary Explanation:
* bird_surface.colliderect(pipe): This is a super useful Pygame method for Rect objects. It returns True if two rectangles are overlapping (colliding) and False otherwise. This makes collision detection between simple objects incredibly easy!
With this, your game now properly ends when the bird touches a pipe or goes off-screen.
Adding a Score
What’s a game without a score? We’ll track how many pipes the bird successfully passes.
Add a score variable after your game_active variable:
game_active = True
score = 0
high_score = 0
And add these functions for displaying score after your check_collision function:
def display_score(game_state):
if game_state == 'main_game':
score_surface = game_font.render(str(int(score)), True, (255, 255, 255)) # Render score text
score_rect = score_surface.get_rect(center = (SCREEN_WIDTH / 2, 100))
screen.blit(score_surface, score_rect)
if game_state == 'game_over':
score_surface = game_font.render(f'Score: {int(score)}', True, (255, 255, 255))
score_rect = score_surface.get_rect(center = (SCREEN_WIDTH / 2, 100))
screen.blit(score_surface, score_rect)
high_score_surface = game_font.render(f'High Score: {int(high_score)}', True, (255, 255, 255))
high_score_rect = high_score_surface.get_rect(center = (SCREEN_WIDTH / 2, 850))
screen.blit(high_score_surface, high_score_rect)
We need a font for the score. Add this after clock = pygame.time.Clock():
game_font = pygame.font.Font('freesansbold.ttf', 40) # Use a default font, size 40
Supplementary Explanations:
* pygame.font.Font(): This function loads a font. freesansbold.ttf is a common default font usually available on systems. You can also specify your own font file.
* font.render(text, antialias, color): This method creates a “surface” (an image) from text. antialias=True makes the text smoother.
* surface.get_rect(center = (...)): This gets a Rect object for the rendered text and centers it at a specific point.
* screen.blit(source_surface, destination_rect): This is how you draw one surface (like our text surface) onto another surface (our main screen).
Now, let’s update the score in the game loop. We’ll introduce a pipe_passed variable to make sure we only score once per pipe pair.
Add this variable after high_score = 0:
pipe_passed = False
Update your game loop’s game_active block:
# ... bird, pipe movement, drawing, collision check ...
# Score logic
if pipe_list: # If there are pipes on screen
# Check if bird has passed the pipe's x-coordinate (center of the pipe's gap)
# We need to make sure it's the right pipe for scoring
for pipe in pipe_list:
if pipe.bottom >= SCREEN_HEIGHT and bird_surface.centerx > pipe.centerx - PIPE_SPEED and bird_surface.centerx < pipe.centerx + PIPE_SPEED and not pipe_passed:
score += 0.5 # Each pipe is a pair, so score 0.5 for a bottom/top pipe
pipe_passed = True
if bird_surface.centerx < pipe.centerx - PIPE_SPEED: # Reset for next pipe
pipe_passed = False
display_score('main_game')
And outside the if game_active: block, add the game over screen logic:
else: # If game_active is False (game over)
if score > high_score:
high_score = score
display_score('game_over')
Important Note for Scoring: This scoring logic is a bit simplified. A more robust solution might track which pipes have already been scored. For beginners, a simple check like this gets the job done for now!
Making It Restart
When the game is over, we need a way to restart. We’ll reuse the spacebar for this.
Modify your event.type == pygame.KEYDOWN block:
# Inside the Game Loop -> for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if game_active:
bird_movement = 0
bird_movement = -8
else: # If game is not active, restart the game
game_active = True
pipe_list.clear() # Clear all old pipes
bird_surface.center = (100, SCREEN_HEIGHT / 2) # Reset bird position
bird_movement = 0 # Reset bird movement
score = 0 # Reset score
Now you can restart the game by pressing the spacebar after a game over!
Putting It All Together (Complete Code Structure)
Here’s how your full script should generally look:
import pygame
import sys
import random
def create_pipe():
# ... (function body as defined above) ...
random_pipe_pos = random.choice([300, 400, 500, 600, 700])
bottom_pipe = pygame.Rect(SCREEN_WIDTH, random_pipe_pos + PIPE_GAP / 2, PIPE_WIDTH, SCREEN_HEIGHT - random_pipe_pos - PIPE_GAP / 2)
top_pipe = pygame.Rect(SCREEN_WIDTH, 0, PIPE_WIDTH, random_pipe_pos - PIPE_GAP / 2)
return bottom_pipe, top_pipe
def move_pipes(pipes):
# ... (function body as defined above) ...
for pipe in pipes:
pipe.centerx -= PIPE_SPEED
return [pipe for pipe in pipes if pipe.right > -50]
def draw_pipes(pipes):
# ... (function body as defined above) ...
for pipe in pipes:
if pipe.bottom >= SCREEN_HEIGHT:
pygame.draw.rect(screen, (0, 128, 0), pipe)
else:
pygame.draw.rect(screen, (0, 128, 0), pipe)
def check_collision(pipes):
# ... (function body as defined above) ...
for pipe in pipes:
if bird_surface.colliderect(pipe):
return False
return True
def display_score(game_state):
# ... (function body as defined above) ...
if game_state == 'main_game':
score_surface = game_font.render(str(int(score)), True, (255, 255, 255))
score_rect = score_surface.get_rect(center = (SCREEN_WIDTH / 2, 100))
screen.blit(score_surface, score_rect)
if game_state == 'game_over':
score_surface = game_font.render(f'Score: {int(score)}', True, (255, 255, 255))
score_rect = score_surface.get_rect(center = (SCREEN_WIDTH / 2, 100))
screen.blit(score_surface, score_rect)
high_score_surface = game_font.render(f'High Score: {int(high_score)}', True, (255, 255, 255))
high_score_rect = high_score_surface.get_rect(center = (SCREEN_WIDTH / 2, 850))
screen.blit(high_score_surface, high_score_rect)
pygame.init()
SCREEN_WIDTH = 576
SCREEN_HEIGHT = 1024
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Flappy Python")
clock = pygame.time.Clock()
game_font = pygame.font.Font('freesansbold.ttf', 40) # Load font
game_active = True
score = 0
high_score = 0
pipe_passed = False # To ensure score is incremented once per pipe
bird_surface = pygame.Rect(100, SCREEN_HEIGHT / 2 - 25, 50, 50)
bird_movement = 0
gravity = 0.25
pipe_list = []
PIPE_SPEED = 3
PIPE_WIDTH = 70
PIPE_GAP = 200
SPAWNPIPE = pygame.USEREVENT
pygame.time.set_timer(SPAWNPIPE, 1200)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if game_active:
bird_movement = 0
bird_movement = -8
else: # Restart the game
game_active = True
pipe_list.clear()
bird_surface.center = (100, SCREEN_HEIGHT / 2)
bird_movement = 0
score = 0
pipe_passed = False # Reset this too!
if event.type == SPAWNPIPE and game_active:
pipe_list.extend(create_pipe())
screen.fill((78, 192, 204)) # Light blue background
if game_active:
# Bird logic
bird_movement += gravity
bird_surface.centery += bird_movement
pygame.draw.rect(screen, (255, 255, 0), bird_surface)
# Pipe logic
pipe_list = move_pipes(pipe_list)
draw_pipes(pipe_list)
# Collision check
game_active = check_collision(pipe_list)
if bird_surface.top < 0 or bird_surface.bottom > SCREEN_HEIGHT:
game_active = False
# Score logic (simplified)
if pipe_list:
for pipe in pipe_list:
# Assuming the bottom pipe dictates scoring for the pair
if pipe.bottom >= SCREEN_HEIGHT and bird_surface.centerx > pipe.centerx and not pipe_passed:
score += 0.5
pipe_passed = True
if pipe.bottom >= SCREEN_HEIGHT and bird_surface.centerx < pipe.centerx and pipe_passed:
pipe_passed = False # Reset for the next pipe when bird passes its x-center
display_score('main_game')
else: # Game Over
if score > high_score:
high_score = score
display_score('game_over')
pygame.display.update()
clock.tick(120)
Next Steps and Improvements
You’ve built a functional Flappy Bird clone! This is a fantastic achievement for a beginner. From here, you can add many improvements to make your game even better:
- Images: Replace the colored rectangles with actual bird and pipe images for a more polished look.
- Sounds: Add sound effects for flapping, collisions, and scoring.
- Animations: Give the bird flapping animations.
- Difficulty: Increase pipe speed or decrease gap size as the score gets higher.
- Scrolling Background: Make the background scroll to give a better sense of movement.
- More Advanced Score Logic: Refine the scoring to be more robust.
Experiment, have fun, and keep coding!
Leave a Reply
You must be logged in to post a comment.