flappy_bird/flappy.py

301 lines
9.8 KiB
Python

import pygame
from pygame.locals import *
import random
pygame.init()
#implementing timer
clock = pygame.time.Clock()
fps = 60
#setting screen size based on image assets
screen_width = 864
screen_height = 786
#creating game window
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption('Flappy Bird')
#font
font = pygame.font.SysFont('Bauhaus 93', 60)
#color
white = (255, 255, 255)
#game variables
ground_scroll = 0
scroll_speed = 4 # 4 pixels
flying = False #required to determine the start of gameplay
game_over = False
pipe_gap = 150
pipe_frequency = 1500 #miliseconds
last_pipe = pygame.time.get_ticks() - pipe_frequency
score = 0
pass_pipe = False
#my addition
flappy_collided = False
#loading game assets
bg = pygame.image.load('img/bg.png')
ground_img = pygame.image.load('img/ground.png')
button_img = pygame.image.load('img/restart.png')
die_fx = pygame.mixer.Sound('sound_effects/die.mp3')
flap_fx = pygame.mixer.Sound('sound_effects/flap.mp3')
hit_fx = pygame.mixer.Sound('sound_effects/hit.mp3')
restart_fx = pygame.mixer.Sound('sound_effects/restart.mp3')
score_fx = pygame.mixer.Sound('sound_effects/score.mp3')
#function to handle the display of score onto the screen
def draw_text(text, font, text_col, x, y):
img = font.render(text, True, text_col)
screen.blit(img, (x, y))
def reset_game():
pipe_group.empty()
flappy.rect.x = 100
flappy.rect.y = int(screen_height / 2)
score = 0 #this is local variable, wouldn't affect the global so we will
#return the score so it can be used globally after
return score
#using classes to create the bird
class Bird(pygame.sprite.Sprite):
def __init__(self, x, y):
#add a function to inheret update, draw, etc function from sprite classes
pygame.sprite.Sprite.__init__(self)
self.images = []
self.index = 0
self.counter = 0 #to control animation speed
for num in range(1, 4):
img = pygame.image.load(f'img/bird{num}.png')#load images for bird
self.images.append(img)
self.image = self.images[self.index]
self.rect = self.image.get_rect() #create a rectangle bounding box around the image
self.rectangles = []
self.rect.center = [x, y] #setting the starting point of the rectangle
self.vel = 0 #velocity of the bird (up and down motion)
self.clicked = False #checking when the mouse is released
#overwriting the sprite update function
def update(self):
if flying == True: #start movement on first mouse click
#handle velocity/movemnet/gravity of the bird
self.vel += 0.5 #increase velocity of bird at every iteration
if self.vel > 8: #adding gravity
self.vel = 8
if self.rect.bottom < 618:
self.rect.y += int(self.vel) # if the bird is above ground it will increase in height
#relative to the rate of increase in velocity for each mouse click
if game_over == False:
#jumping
if pygame.mouse.get_pressed()[0] == 1 and self.clicked == False: #returning a array of mouse buttons (selecting 1 for left mouse button)
self.clicked = True
self.vel = -8
flap_fx.play()
if pygame.mouse.get_pressed()[0] == 0:
self.clicked = False
#handle the animation (cycling of images to immitate bird flap)
self.counter += 1 #increase image iteration
flap_cooldown = 5
if self.counter > flap_cooldown:
self.counter = 0 #start loop counter
self.index += 1 #increment through images
if self.index >= len(self.images): #keep index to the max of images in array
self.index = 0
self.image = self.images[self.index]
#rotate the bird
self.image = pygame.transform.rotate(self.images[self.index], self.vel * -2)
else:
#play hit sound on collision
# if flappy_collided == True:
# self.rect.right = 0
#hit_fx.play()
#rotate the bird to the dead position
self.image = pygame.transform.rotate(self.images[self.index], -90)
if self.rect.bottom >= 618 and flying == True:
die_fx.play()
class Pipe(pygame.sprite.Sprite):
def __init__(self, x, y, position):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('img/pipe.png')
self.rect = self.image.get_rect()
#position 1 means top and -1 means bottom
if position == 1:
self.image = pygame.transform.flip(self.image, False, True)
self.rect.bottomleft = [x, y - int(pipe_gap / 2 )]
else:
self.rect.topleft = [x, y + int(pipe_gap / 2 )]
def update(self):
self.rect.x -= scroll_speed #pipes moving at scroll speed
if self.rect.right < 0: #if right side of rect or x coord is less than zero
self.kill()
class Button():
def __init__(self, x, y, image):
self.image = image
self.rect = self.image.get_rect()
self.rect.topleft = (x, y)
def draw(self):
action = False
#get mouse position
pos = pygame.mouse.get_pos()
#check if mouse is over the button by checking for collision
if self.rect.collidepoint(pos):
if pygame.mouse.get_pressed()[0] == 1: #checking that mouse was clicked
action = True
#draw button
screen.blit(self.image, (self.rect.x, self.rect.y))
return action #returning a confirmation that the mouse was clicked to restart the game
#group to keep track of the sprites being added to it
#its like a python list
bird_group = pygame.sprite.Group()
pipe_group = pygame.sprite.Group()
flappy = Bird(100, int(screen_height / 2))
bird_group.add(flappy)
#dealing with the removal of the rectangle on the bird
#to prevent looping of the hit sound
rect1 = flappy.rect
rectangles = [rect1]
removed_rect1 = None
#create restart button instance
button = Button(screen_width // 2 - 50, screen_height // 2 - 100, button_img)
#play sound on collision
if flappy_collided == True:
hit_fx.play()
#game play loop starts here
run = True
while run:
#telling the clock what speed to tick at in order
#to slow the scroll speed
clock.tick(fps)
#blit function is being called to load the bg image to screen
screen.blit(bg, (0,-150))
#add bird to screen
bird_group.draw(screen)
bird_group.update()
#add pipe to screen
pipe_group.draw(screen)
#draw ground
screen.blit(ground_img, (ground_scroll, 618))
#checking score by measuring the bird's travel through each pipe
#by sensing when its bounding rectangle passes the right and left bounding
#rectangle of the pipes and incrementing the score variable by 1 each time
if len(pipe_group) > 0: #ensuring that pipes have been create before starting
if bird_group.sprites()[0].rect.left > pipe_group.sprites()[0].rect.left\
and bird_group.sprites()[0].rect.right < pipe_group.sprites()[0].rect.right\
and pass_pipe == False:
pass_pipe = True
if pass_pipe == True:
if bird_group.sprites()[0].rect.left > pipe_group.sprites()[0].rect.right:
score += 1
score_fx.play()
pass_pipe = False
draw_text(str(score), font, white, int(screen_width/2), 20)
#collision - checking
if pygame.sprite.groupcollide(bird_group, pipe_group, False, False) or flappy.rect.top < 0:
flappy_collided = True
for r1 in rectangles[:]:
hit_fx.play()
removed_rect1 = r1 #sending this value to the reset process
rectangles.remove(rect1)
break
game_over = True
#check if the bird hit the ground
if flappy.rect.bottom >= 618:
game_over = True
flying = False
#stop scrolling the ground if game is over
if game_over == False and flying == True:
#generate new pipes
time_now = pygame.time.get_ticks()
if time_now - last_pipe > pipe_frequency:
pipe_height = random.randint(-100, 100)
btm_pipe = Pipe(screen_width, int(screen_height/2) + pipe_height, -1)
top_pipe = Pipe(screen_width, int(screen_height/2) + pipe_height, 1 )
pipe_group.add(btm_pipe)
pipe_group.add(top_pipe)
last_pipe = time_now #set last pipe to now (being the last time a pipe was created on each iteration)
#scoll ground
ground_scroll -= scroll_speed
#repeating the ground image when it scrolls out of view
if abs(ground_scroll) > 35: #negative value converted to absolute
ground_scroll = 0
pipe_group.update()
#check for game over and reset
if game_over == True:
if button.draw() == True:
restart_fx.play()
game_over = False
rectangles.append(removed_rect1) #replacing the rectangle on the bird
score = reset_game() #calling the reset function to reset gameplay and return the score value
#which is being saved to the global score varibale using this same expression
#looking for game events to determin gameplay
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
if event.type == pygame.MOUSEBUTTONDOWN and flying == False and game_over == False: #determing game should start on first mouse click
flying = True
#update function called to update entire UI is everything in
#the loop above it
pygame.display.update()
pygame.quit()