Recently, I have been trying to improve my programming and coding skills in python so I have decided to make some games with the pygame module.
I have made this flappy bird clone with pygame and would love to get review of the code, what should I change/improve, any tips are welcome.
Code is here, in this github link : click here
import pygame
import random
import os
pygame.init()
SIZE = [400, 708]
FONT = pygame.font.SysFont('arialrounded', 50)
class Bird:
def __init__(self):
self.x = 50
self.y = 350
self.jump = 0
self.jump_speed = 10
self.gravity = 10
self.dead = False
self.sprite = 0
self.bird_sprites = [pygame.image.load("images/1.png").convert_alpha(),
pygame.image.load("images/2.png").convert_alpha(),
pygame.image.load("images/dead.png").convert_alpha()]
# self.img_rect =
def move(self):
if self.dead: # dead bird
self.sprite = 2 # change to dead.png
# keeps falling until it hits the ground
if self.y < SIZE[1] - 30:
self.y += self.gravity
elif self.y > 0:
# handling movement while jumping
if self.jump:
self.sprite = 1 # change to 2.png
self.jump_speed -= 1
self.y -= self.jump_speed
else:
# regular falling (increased gravity)
self.gravity += 0.2
self.y += self.gravity
else:
# in-case where the bird reaches the top
# of the screen
self.jump = 0
self.y += 3
def bottom_check(self):
# bird hits the bottom = DEAD
if self.y >= SIZE[1] - 30:
self.dead = True
def get_rect(self):
# updated bird image rectangle
img_rect = self.bird_sprites[self.sprite].get_rect()
img_rect[0] = self.x
img_rect[1] = self.y
return img_rect
class Pillar:
def __init__(self, pos):
# pos == True is top , pos == False is bottom
self.pos = pos
self.img = self.get_image()
def get_rect(self):
# returns the pillar image rect
return self.img.get_rect()
def get_image(self):
if self.pos: # image for the top pillar
return pygame.image.load("images/top.png").convert_alpha()
else: # image for the bottom pillar
return pygame.image.load("images/bottom.png").convert_alpha()
class Options:
def __init__(self):
self.score_img = pygame.image.load("images/score.png").convert_alpha() # score board image
self.play_img = pygame.image.load("images/play.png").convert_alpha() # play button image
self.play_rect = self.play_img.get_rect()
self.score_rect = self.score_img.get_rect()
self.align_position()
self.score = 0
self.font = FONT
def align_position(self):
# aligns the "menu" in certain positions
self.play_rect.center = (200, 330)
self.score_rect.center = (200, 220)
def inc(self):
# score increased by 1
self.score += 1
class Game:
def __init__(self):
self.screen = pygame.display.set_mode((SIZE[0], SIZE[1]))
pygame.display.set_caption("Flappy Bird")
self.background = pygame.image.load("images/background.png").convert() # background image
self.pillar_x = 400
self.offset = 0
self.top_p = Pillar(1) # top pillar
self.bot_p = Pillar(0) # bottom pillar
self.pillar_gap = 135 # gap between pillars, (can be randomised as well)
self.bird = Bird() # bird object
self.score_board = Options()
self.passed = False # allows to keep track of the score
def pillar_move(self):
# handling pillar movement in the background
if self.pillar_x < -100:
self.offset = random.randrange(-120, 120)
self.passed = False
self.pillar_x = 400
self.pillar_x -= 5
def run(self):
clock = pygame.time.Clock()
done = True
while done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
# bird jumps
self.bird.jump = 17
self.bird.gravity = 5
self.bird.jump_speed = 10
if event.type == pygame.MOUSEBUTTONDOWN:
# clicking on the play button (game reset)
if self.bird.dead and self.score_board.play_rect.collidepoint(event.pos):
self.bird.dead = False
self.reset()
self.screen.blit(self.background, (0, 0))
self.screen.blit(self.top_p.img, (self.pillar_x, 0 - self.pillar_gap - self.offset))
self.screen.blit(self.bot_p.img, (self.pillar_x, 360 + self.pillar_gap - self.offset))
self.screen.blit(self.bird.bird_sprites[self.bird.sprite], (self.bird.x, self.bird.y))
self.pillar_move()
self.bird.move()
self.bird.bottom_check()
if not self.bird.dead:
self.collision()
self.show_score()
else:
self.game_over()
pygame.display.flip()
def get_pillar_rect(self, pillar):
# returns current pillar rectangle on display
rect = pillar.get_image().get_rect()
rect[0] = self.pillar_x
if pillar.pos:
# current rect y position for top pillar
rect[1] = 0 - self.pillar_gap - self.offset
else:
# current rect y position for bottom pillar
rect[1] = 360 + self.pillar_gap - self.offset
return rect
def collision(self):
top_rect = self.get_pillar_rect(self.top_p)
bot_rect = self.get_pillar_rect(self.bot_p)
# collision check bird <> pillars
if top_rect.colliderect(self.bird.get_rect()) or bot_rect.colliderect(self.bird.get_rect()):
# print(self.bird.bird_sprites[self.bird.sprite].get_rect())
self.bird.dead = True
# if bird passed the pillars
elif not self.passed and top_rect.right < self.bird.x:
self.score_board.inc()
self.passed = True
def reset(self):
# game values reset
self.score_board.score = 0
self.bird = Bird()
self.top_p = Pillar(1)
self.bot_p = Pillar(0)
self.pillar_x = 400
self.bird.gravity = 10
def show_score(self):
# score font
score_font = FONT.render("{}".format(self.score_board.score),
True, (255, 80, 80))
# score font rectangle
font_rect = score_font.get_rect()
font_rect.center = (200, 50)
self.screen.blit(score_font, font_rect) # show score board font
def game_over(self):
# score font
score_font = FONT.render("{}".format(self.score_board.score),
True, (255, 80, 80))
# score font rectangle
font_rect = score_font.get_rect()
score_rect = self.score_board.score_rect
play_rect = self.score_board.play_rect # play button rectangle
font_rect.center = (200, 230)
self.screen.blit(self.score_board.play_img, play_rect) # show play button
self.screen.blit(self.score_board.score_img, score_rect) # show score board image
self.screen.blit(score_font, font_rect) # show score font
os.chdir(os.path.dirname(__file__))
if __name__ == "__main__":
game = Game()
game.run()
-
\$\begingroup\$ Welcome to Code Review! Unfortunately your question is off-topic as of now, as the code to be reviewed must be present in the question. Please add the code you want reviewed in your question. Thanks! \$\endgroup\$Malachi– Malachi2017年08月24日 18:34:27 +00:00Commented Aug 24, 2017 at 18:34
1 Answer 1
Nice, not many things to improve, so only some tips.
You use many pictures. Instead of hard-coding their names spread over your code, consider using constant variables near the top of your code, something like
import os.path
FOLDER = 'images'
PICS = dict(BACKGROUND='background.png',
SCORE='score.png',
PLAY='play.png'
)
PICS = {k: os.path.join(FOLDER, v) for k, v in PICS.items()}
to make your code easy maintainable.
Note the construction of dictionary with keywords to avoid put the key names in apostrophes (BTW, apostrophes are preferred over quotes in Python) and dictionary comprehension to avoid repeated use of 'images'
.
It's useful to follow the DRY principle - Don't Repeat Yourself.
You repeatedly use the long construction
pygame.image.load("images/score.png").convert_alpha()
Why don't dedicate a function to it, e. g.
def image_load(name):
return pygame.image.load(name).convert_alpha()
and then use it - together with my previous tip - instead of commands as this one
self.score_img = pygame.image.load("images/score.png").convert_alpha() # score board image
to shorten it to
self.score_img = image_load(PICS[SCORE]) # score board image
Sometimes you use so called magic numbers, e. g.
self.pillar_x = 400 # did you mean 'self-pillar_x = SIZE[0]' ?
or
(255, 80, 80) # did you mean something which may have the name FONT_COLOR?
Instead of the superfluous construction
pygame.display.set_mode((SIZE[0], SIZE[1]))
you may simply use
pygame.display.set_mode(SIZE)
as the list of coordinates is as good as a tuple of them.
-
\$\begingroup\$ thanks a lot for your review and your time devoting to it! I will take these tips into account when writing my next programs. \$\endgroup\$david plotkin– david plotkin2017年08月26日 09:30:56 +00:00Commented Aug 26, 2017 at 9:30