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)