menator01
Gold Coder
I've been helping someone with their project and decided to give it my own touch by using classes and implementing a scoring system for the game among other things.
Anyhow I've seem to hit a snag. I'm using pygame's collide group to detect collision and for some reason it will collide even if the sprites don't touch. I've checked the radius of the sprites and they do not collide. Any help would be great.
You can get the image and sound files from github
or download from my python scripts site
Updated code
FlappyBird.py
You can get the image and sound files from github
GitHub - clear-code-projects/FlappyBird_Python: Project files for a video game tutorial on creating flappy bird. Link to the video: https://youtu.be/UZg49z76cLw
Project files for a video game tutorial on creating flappy bird. Link to the video: https://youtu.be/UZg49z76cLw - clear-code-projects/FlappyBird_Python
github.com
Updated code
FlappyBird.py
Python:
import pygame
from random import choice
from pathlib import Path
from os import listdir
import sqlite3 as sq
import tkinter as tk
# Get path to executing script
path = Path(__file__).parent
# some pygame variables
pygame.init()
clock = pygame.time.Clock()
fps = 60
screen_size = (1280, 740)
screen = pygame.display.set_mode(screen_size)
# Colors
black = (0,0,0)
white = (255,255,255)
# Create sprite groups
bird_sprite = pygame.sprite.Group()
pipe_sprite = pygame.sprite.Group()
allsprites = pygame.sprite.Group()
# Create channels
channel1 = pygame.mixer.Channel(1)
channel2 = pygame.mixer.Channel(2)
channel3 = pygame.mixer.Channel(3)
channel4 = pygame.mixer.Channel(4)
# Load logo image
logo = pygame.image.load(f'{path}/assets/my-python-logo.png').convert()
logo = pygame.transform.scale(logo,(logo.get_width()/2, logo.get_height()/2))
# Define the start and end page
def page():
screen.fill((0,0,0))
img = pygame.image.load(f'{path}/assets/message.png')
img = pygame.transform.scale2x(img)
screen.blit(img, (screen_size[0]/2 - img.get_width()/2, screen_size[1]/2 - img.get_height()/2))
font = pygame.font.SysFont('04B_19.TTF', 40)
text = 'Press spacebar to begin - esc to exit. During game play press q to end game.'
surf = font.render(text, True, (150,150,150))
screen.blit(surf, (screen_size[0]/2-font.size(text)[0]/2, screen_size[1]-font.size(text)[1]*2.5))
screen.blit(logo, (50,20))
surf = font.render(chr(169), True, (250,130,0))
screen.blit(surf, (35, 20))
surf = font.render(chr(8482), True, (250,130,0))
screen.blit(surf, (160, 25))
pygame.display.update()
def gameover(score):
row = screen_size[1]/2 - screen_size[1]/2*0.2
screen.fill((0,0,0))
screen.blit(logo, (50,20))
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 35)
surf = font.render(chr(169), True, (250,130,0))
screen.blit(surf, (35, 20))
surf = font.render(chr(8482), True, (250,130,0))
screen.blit(surf, (160, 25))
img = pygame.image.load(f'{path}/assets/gameover.png').convert()
img = pygame.transform.scale2x(img)
img.set_colorkey(white)
screen.blit(img, ((screen_size[0]/2-img.get_width()/2, screen_size[1]*0.2)))
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 50)
text1 = f'Your Final Score: {score}'
row_align = screen_size[0]/2 - font.size(text1)[0]/2
surface = font.render(text1, True, (255,180,0))
screen.blit(surface, (screen_size[0]/2 - font.size(text1)[0]/2, screen_size[1]*0.38-35))
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 60)
text2 = 'Press enter to play again or esc to quit'
surface = font.render(text2, True, (255,180,0))
screen.blit(surface, (screen_size[0]/2 - font.size(text2)[0]/2, screen_size[1]*0.85))
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 40)
font.set_underline(True)
text3 = 'Top Scores'
surface = font.render(text3, True, (255,0,0))
screen.blit(surface, (screen_size[0]/2-font.size(text3)[0]/2, row+10))
entries = db.top5()
if entries:
font = pygame.font.SysFont(f'{path}/04B_19.TTF', 40)
for entry in entries:
name, score = entry
row += 40
surface = font.render(f'{name}: {score}', True, (255,255,255))
screen.blit(surface, ((screen_size[0]/2-font.size(text3)[0]/2)+30, row+10))
row = 105
pygame.display.update()
class Database:
def __init__(self):
self.connect = sq.connect(f'{path}/bird_score.db')
self.cursor = self.connect.cursor()
def insert(self, args):
''' Method will write player initials and score to database '''
if len(args) == 2:
query = 'insert into flappy (name, score) values (?,?)'
self.cursor.execute(query, args)
self.connect.commit()
return True
return False
def top5(self):
''' Method will get top 5 scores '''
query = '''select * from flappy order by score desc limit 5'''
data = [info for info in self.cursor.execute(query)]
if len(data) > 0:
return data
return False
def create(self):
''' Method will create a table in the database if not exists '''
query = '''create table if not exists flappy (
name text,
score integer
)'''
self.connect.execute(query)
self.connect.commit()
class Background:
''' Background class handles the creation of backgrounds
be it day or night. Also handles the floor creation '''
def __init__(self):
# set an instance variable for floor x pos
self.floor_x = 0
def day(self):
''' Method changes background to day mode '''
bg = pygame.image.load(f'{path}/assets/background-day.png').convert()
bg = pygame.transform.scale(bg, screen_size)
return bg
def night(self):
''' Method changes background to night mode '''
bg = pygame.image.load(f'{path}/assets/background-night.png').convert()
bg = pygame.transform.scale(bg, screen_size)
return bg
def floor(self):
''' Method displays the floor image '''
_floor = pygame.image.load(f'{path}/assets/base.png').convert()
_floor = pygame.transform.scale(_floor, (screen_size[0], 80))
screen.blit(_floor, (self.floor_x,screen_size[1]-80))
screen.blit(_floor, (self.floor_x + screen_size[0], screen_size[1]-80))
class Pipe(pygame.sprite.Sprite):
''' Class for creating pipes '''
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(f'{path}/assets/pipe-green.png')
self.rect = self.image.get_rect()
self.rect.x = screen_size[0] + self.rect.width
self.rect.bottom = screen_size[1] - 80
self.active = True
# Add pipe object to sprite groups
pipe_sprite.add(self)
allsprites.add(pipe_sprite)
def draw_pipe(self, top, bottom, rotate=False):
''' Method for drawing the pipes '''
if rotate:
self.rect.height = top
self.image = pygame.transform.scale(self.image, (self.rect.width, self.rect.height))
self.image = pygame.transform.flip(self.image, False, True)
self.rect.x = screen_size[0] + self.rect.width
self.rect.top = 0
else:
self.rect.height = bottom
self.image = pygame.transform.scale(self.image, (self.rect.width, self.rect.height))
self.rect = self.image.get_rect()
self.rect.x = screen_size[0] + self.rect.width
self.rect.bottom = screen_size[1] - 80
def update(self):
''' Method updates the sprite object '''
# If sprite goes off screen, kill it
if self.rect.right <= 0:
self.kill()
for sprite in bird_sprite:
if sprite.rect.left >= self.rect.right and self.active:
# Setting score to 0.5 because ther are two pipes.
# We only want one point
score.score += 0.5
self.active = False
channel2.play(sound.play('point'))
# Scrolls the pipes across the screen
self.rect.x -= speed
class Bird(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.gravity = 0.25
# Load the images
down = pygame.image.load(f'{path}/assets/bluebird-downflap.png').convert_alpha()
mid = pygame.image.load(f'{path}/assets/bluebird-midflap.png').convert_alpha()
up = pygame.image.load(f'{path}/assets/bluebird-upflap.png').convert_alpha()
# Scale images
down = pygame.transform.scale2x(down)
mid = pygame.transform.scale2x(mid)
up = pygame.transform.scale2x(up)
# Load the frame list with images and set index
self.frames = [down, mid, up]
self.index = 0
# Load first image
self.image = self.frames[self.index]
# Get image rect and set start position to middle of screen
self.rect = self.image.get_rect()
self.rect.x = screen_size[0]/2 - self.rect.width/2
self.rect.y = screen_size[1]/2 - self.rect.height/2
# Add sprite to sprite groups
# bird_sprite for collison detection and allsprites for animation
bird_sprite.add(self)
allsprites.add(bird_sprite)
def rotate(self, angle):
''' Method for pointer bird up or down '''
img = pygame.transform.rotate(self.frames[self.index], angle)
rect = img.get_rect(center=self.rect.center)
return img
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
self.image = self.rotate(30)
# Speed for when pointing up
self.rect.y -= 3.6
else:
self.image = self.rotate(-30)
# Default speed when going down
self.rect.y += 2
# Keep bird from flying off top of screen
if self.rect.top <= 10:
self.rect.top = 10
self.image = self.frames[self.index]
class Score:
def __init__(self):
self.score = 0
def show(self):
font = pygame.font.SysFont('04B_19.TTF', 35)
text = f'Score: {self}'
surface = font.render(text, True, (0,0,0))
screen.blit(surface, (screen_size[0]*0.01, screen_size[1]*0.02))
def __repr__(self):
score = int(self.score)
return str(score)
class Sound:
def __init__(self):
self.files = listdir(f'{path}/sound')
def play(self, sfx):
for file in self.files:
if sfx in file:
return pygame.mixer.Sound(f'{path}/sound/{file}')
def play_wings(self, fx):
for file in self.files:
if fx in file:
pygame.mixer.music.load(f'{path}/sound/{file}')
pygame.mixer.music.play(-1)
class Window:
def __init__(self, parent):
self.parent = parent
font = (None, 14, 'normal')
label = tk.Label(parent, text='Enter Initials:', font=font)
label.grid(column=0, row=0, padx=(4,2), pady=(4,2))
self.myvar = tk.StringVar()
self.entry = tk.Entry(parent, textvariable=self.myvar, font=font)
self.entry.grid(column=1, row=0, padx=(2,4), pady=2)
self.entry.focus()
self.btn = tk.Button(parent, text='Submit', command=self.submit)
self.btn.grid(column=0, columnspan=2, sticky='e', padx=4, pady=(2,4))
self.entry.bind('<Return>', self.submit)
self.entry.bind('<KP_Enter>', self.submit)
self.myvar.trace('w', self.validate)
def validate(self, var=None, index=None, mode=None):
if len(self.myvar.get().strip()) == 0:
return False
elif len(self.myvar.get().strip()) > 3:
self.entry.delete(3, 'end')
return False
else:
return True
def submit(self, event=None):
if self.validate():
db.insert((self.myvar.get().strip(), score.score))
self.parent.destroy()
else:
return False
# Initialize some classes
sound = Sound()
score = Score()
# Inialize Database class and create the table
# if it doesnt exist
db = Database()
db.create()
# Game Timers
mode = True
# Mode timer changes from day/night every 30 seconds
timer = pygame.USEREVENT + 1
pygame.time.set_timer(timer, 30000)
# Timer for creating pipes
create_pipe = pygame.USEREVENT + 2
pygame.time.set_timer(create_pipe, 4500)
# Timer for bird wing flap
birdflap = pygame.USEREVENT + 3
pygame.time.set_timer(birdflap, 100)
# Set default background to day mode
background = Background()
bg = background.day()
# background speed
speed = 3
# Setting for going to the start page
gamestate = 'startpage'
running = True
active = True
while running:
######## Event Section ##########################
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == birdflap:
bird.index += 1
if bird.index >= len(bird.frames):
bird.index = 0
bird.image = bird.frames[bird.index]
# Event for creating pipes
if event.type == create_pipe:
top = choice([135,275,415])
bottom = 0
# We want the correct size for the bottom pipe to match top
if top == 135:
bottom = 415
elif top == 275:
bottom = 275
else:
bottom = 135
# Create the pipes
pipe = Pipe()
pipe.draw_pipe(top, bottom)
pipe = Pipe()
pipe.draw_pipe(top, bottom, True)
# Away to exit the game with esc key
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
if event.key == pygame.K_SPACE:
if active:
channel3.play(sound.play('swooshing'))
channel4.play(sound.play('wing'), loops = -1)
if event.key == pygame.K_RETURN or event.key == pygame.K_KP_ENTER:
gamestate = 'play'
score.score = 0
if event.key == pygame.K_q:
gamestate = 'startpage'
# Check if we are in day or night mode. Change accordingly
if event.key == pygame.K_m:
if mode:
bg = background.night()
mode = False
elif not mode:
bg = background.day()
mode = True
if event.type == timer:
mode = False if mode else True
if mode:
bg = background.day()
else:
bg = background.night()
################ gamestate section ##################
# Are we on the start page or play
if gamestate == 'play':
screen.fill((0,255,0))
screen.blit(bg, (0,0))
score.show()
active = True
# Disply and scroll the floor background image
background.floor_x -= speed
if background.floor_x <= -screen_size[0]:
background.floor_x = 0
background.floor()
# If bird crashes into ground, go to gameover page
if bird.rect.bottom >= screen_size[1] - 80:
bird.kill()
channel1.play(sound.play('hit'))
channel1.queue(sound.play('die'))
gamestate = 'form'
collisions = pygame.sprite.groupcollide(bird_sprite, pipe_sprite, True, False)
if collisions:
channel1.play(sound.play('hit'))
channel1.queue(sound.play('die'))
gamestate = 'form'
# Update all game sprites
allsprites.update()
allsprites.draw(screen)
pygame.display.update()
clock.tick(fps)
# gamestate has changed go to start/end page
if gamestate == 'startpage':
# Go to start page and clear all sprite groups
page()
pipe_sprite.empty()
allsprites.empty()
bird_sprite.empty()
bird = Bird()
active = False
if gamestate == 'gameover':
# Go to game over page
gameover(int(score.score))
pipe_sprite.empty()
allsprites.empty()
bird_sprite.empty()
bird = Bird()
active = False
if gamestate == 'form':
if score.score > 0:
channel4.stop()
root = tk.Tk()
Window(root)
root.mainloop()
active = False
channel4.stop()
gamestate = 'gameover'
pygame.quit()
Last edited: