Code for How to Make a Flappy Bird Game in Python Tutorial


View on Github

pipe.py

import pygame

class Pipe(pygame.sprite.Sprite):
	def __init__(self, pos, width, height, flip):
		super().__init__()
		self.width = width
		img_path = 'assets/terrain/pipe.png'
		self.image = pygame.image.load(img_path)
		self.image = pygame.transform.scale(self.image, (width, height))
		if flip:
			flipped_image = pygame.transform.flip(self.image, False, True)
			self.image = flipped_image
		self.rect = self.image.get_rect(topleft = pos)

	# update object position due to world scroll
	def update(self, x_shift):
		self.rect.x += x_shift

		# removes the pipe in the game screen once it is not shown in the screen anymore
		if self.rect.right < (-self.width):
			self.kill()

game.py

import pygame
from settings import WIDTH, HEIGHT

pygame.font.init()

class GameIndicator:
	def __init__(self, screen):
		self.screen = screen
		self.font = pygame.font.SysFont('Bauhaus 93', 60)
		self.inst_font = pygame.font.SysFont('Bauhaus 93', 30)
		self.color = pygame.Color("white")
		self.inst_color = pygame.Color("black")

	def show_score(self, int_score):
		bird_score = str(int_score)
		score = self.font.render(bird_score, True, self.color)
		self.screen.blit(score, (WIDTH // 2, 50))

	def instructions(self):
		inst_text1 = "Press SPACE button to Jump,"
		inst_text2 = "Press \"R\" Button to Restart Game."
		ins1 = self.inst_font.render(inst_text1, True, self.inst_color)
		ins2 = self.inst_font.render(inst_text2, True, self.inst_color)
		self.screen.blit(ins1, (95, 400))
		self.screen.blit(ins2, (70, 450))

settings.py

from os import walk
import pygame

WIDTH, HEIGHT = 600, 650

pipe_pair_sizes = [
	(1, 7),
	(2, 6),
	(3, 5),
	(4, 4),
	(5, 3),
	(6, 2),
	(7, 1)
]
pipe_size = HEIGHT // 10
pipe_gap = (pipe_size * 2) + (pipe_size // 2)
ground_space = 50

def import_sprite(path):
	surface_list = []
	for _, __, img_file in walk(path):
		for image in img_file:
			full_path = f"{path}/{image}"
			img_surface = pygame.image.load(full_path).convert_alpha()
			surface_list.append(img_surface)
	return surface_list

main.py

import pygame, sys
from settings import WIDTH, HEIGHT, ground_space
from world import World

pygame.init()

screen = pygame.display.set_mode((WIDTH, HEIGHT + ground_space))
pygame.display.set_caption("Flappy Bird")

class Main:
	def __init__(self, screen):
		self.screen = screen
		self.bg_img = pygame.image.load('assets/terrain/bg.png')
		self.bg_img = pygame.transform.scale(self.bg_img, (WIDTH, HEIGHT))
		self.ground_img = pygame.image.load('assets/terrain/ground.png')
		self.ground_scroll = 0
		self.scroll_speed = -6
		self.FPS = pygame.time.Clock()
		self.stop_ground_scroll = False

	def main(self):
		world = World(screen)
		while True:
			self.stop_ground_scroll = world.game_over
			self.screen.blit(self.bg_img, (0, 0))

			for event in pygame.event.get():
				if event.type == pygame.QUIT:
					pygame.quit()
					sys.exit()

				elif event.type == pygame.KEYDOWN:
					if not world.playing and not world.game_over:
						world.playing = True
					if event.key == pygame.K_SPACE:
						world.update("jump")
					if event.key == pygame.K_r:
						world.update("restart")

			world.update()

			self.screen.blit(self.ground_img, (self.ground_scroll, HEIGHT))
			if not self.stop_ground_scroll:
				self.ground_scroll += self.scroll_speed
				if abs(self.ground_scroll) > 35:
					self.ground_scroll = 0

			pygame.display.update()
			self.FPS.tick(60)

if __name__ == "__main__":
	play = Main(screen)
	play.main()

bird.py

import pygame
from settings import import_sprite

class Bird(pygame.sprite.Sprite):
	def __init__(self, pos, size):
		super().__init__()
		# bird basic info
		self.frame_index = 0
		self.animation_delay = 3
		self.jump_move = -9

		# bird animation
		self.bird_img = import_sprite("assets/bird")
		self.image = self.bird_img[self.frame_index]
		self.image = pygame.transform.scale(self.image, (size, size))
		self.rect = self.image.get_rect(topleft = pos)
		self.mask = pygame.mask.from_surface(self.image)

		# bird status
		self.direction = pygame.math.Vector2(0, 0)
		self.score = 0

	# for bird's flying animation
	def _animate(self):
		sprites = self.bird_img
		sprite_index = (self.frame_index // self.animation_delay) % len(sprites)
		self.image = sprites[sprite_index]
		self.frame_index += 1
		self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y))
		self.mask = pygame.mask.from_surface(self.image)
		if self.frame_index // self.animation_delay > len(sprites):
			self.frame_index = 0

	# to make the bird fly higher
	def _jump(self):
		self.direction.y = self.jump_move

	# updates the bird's overall state
	def update(self, is_jump):
		if is_jump:
			self._jump()
		self._animate()

world.py

import pygame
from pipe import Pipe
from bird import Bird
from game import GameIndicator
from settings import WIDTH, HEIGHT, pipe_size, pipe_gap, pipe_pair_sizes
import random

class World:
	def __init__(self, screen):
		self.screen = screen
		self.world_shift = 0
		self.current_x = 0
		self.gravity = 0.5
		self.current_pipe = None
		self.pipes = pygame.sprite.Group()
		self.player = pygame.sprite.GroupSingle()
		self._generate_world()
		self.playing = False
		self.game_over = False
		self.passed = True
		self.game = GameIndicator(screen)

	# creates the player and the obstacle
	def _generate_world(self):
		self._add_pipe()
		bird = Bird((WIDTH//2 - pipe_size, HEIGHT//2 - pipe_size), 30)
		self.player.add(bird)

	# adds pipe once the last pipe added reached the desired pipe horizontal spaces
	def _add_pipe(self):
		pipe_pair_size = random.choice(pipe_pair_sizes)
		top_pipe_height, bottom_pipe_height = pipe_pair_size[0] * pipe_size, pipe_pair_size[1] * pipe_size

		pipe_top = Pipe((WIDTH, 0 - (bottom_pipe_height + pipe_gap)), pipe_size, HEIGHT, True)
		pipe_bottom = Pipe((WIDTH, top_pipe_height + pipe_gap), pipe_size, HEIGHT, False)
		self.pipes.add(pipe_top)
		self.pipes.add(pipe_bottom)
		self.current_pipe = pipe_top

	# for moving background/obstacle
	def _scroll_x(self):
		if self.playing:
			self.world_shift = -6
		else:
			self.world_shift = 0

	# add gravity to bird for falling
	def _apply_gravity(self, player):
		if self.playing or self.game_over:
			player.direction.y += self.gravity
			player.rect.y += player.direction.y

	# handles scoring and collision
	def _handle_collisions(self):
		bird = self.player.sprite
		# for collision checking
		if pygame.sprite.groupcollide(self.player, self.pipes, False, False) or bird.rect.bottom >= HEIGHT or bird.rect.top <= 0:
			self.playing = False
			self.game_over = True
		else:
			# if player pass through the pipe gaps
			bird = self.player.sprite
			if bird.rect.x >= self.current_pipe.rect.centerx:
				bird.score += 1
				self.passed = True

	# updates the bird's overall state
	def update(self, player_event = None):
		# new pipe adder
		if self.current_pipe.rect.centerx  <= (WIDTH // 2) - pipe_size:
			self._add_pipe()
		
		# updates, draws pipes
		self.pipes.update(self.world_shift)
		self.pipes.draw(self.screen)

		# applying game physics
		self._apply_gravity(self.player.sprite)
		self._scroll_x()
		self._handle_collisions()

		# configuring player actions
		if player_event == "jump" and not self.game_over:
			player_event = True
		elif player_event == "restart":
			self.game_over = False
			self.pipes.empty()
			self.player.empty()
			self.player.score = 0
			self._generate_world()
		else:
			player_event = False

		if not self.playing:
			self.game.instructions()

		# updates, draws pipes
		self.player.update(player_event)
		self.player.draw(self.screen)

		self.game.show_score(self.player.sprite.score)