I am a beginner in Python. This is my first project in Python (that I tried to complete).
There is a grey square that acts as the Flappy 'Bird' with green obstacles spawning with random gap sizes. The difficulty of the game stays the same for now. If an obstacle is hit, the game closes (I haven't implemented any menu thing yet). Pressing Esc pauses/unpauses the game.
How does the code look like and what improvements can I make to it in terms of organization, scalability, best practices, etc?
pygame 2.5.0 (SDL 2.28.0, Python 3.11.4)
- game_objects.py
class Player:
def __init__(self, position: tuple[float, float], size: tuple[int, int], color: tuple[int, int, int]):
self.x, self.y = position
self.width, self.height = size
self.acc_y = 0.0
self.vel_y = 0.0
self.color = color
class Obstacle:
vel_x = -65.0
def __init__(self, position: tuple[float, float], size: tuple[int, int], color: tuple[int, int, int]):
self.x, self.y = position
self.width, self.height = size
self.color = color
- main.py
import pygame, random
from game_objects import *
# Initializing pygame and setting up the screen
pygame.init()
pygame.display.set_caption("Flappy Bird")
SCREEN_WIDTH, SCREEN_HEIGHT = 600, 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT), flags=pygame.SCALED, vsync=1)
# The player
player = Player((85.0, 150.0), (25, 25), (50, 50, 50))
player.acc_y = 500
# Obstacle list
obstacles: list[Obstacle] = []
def spawn_obstacle():
gap_height = random.randint(125, 250)
gap_y = random.randint(0, (SCREEN_HEIGHT - gap_height))
obstacles.append(Obstacle((SCREEN_WIDTH, 0.0), (65, gap_y), (45, 168, 40)))
obstacles.append(Obstacle((SCREEN_WIDTH, (gap_y + gap_height)), (65, (SCREEN_HEIGHT - (gap_y + gap_height))), (45, 168, 40)))
##### The game loop
# For calculating delta_time
last_tick = pygame.time.get_ticks()
# For spawning obstacles at regular intervals
obstacle_interval = 3000
time_since_last_obstacle = obstacle_interval
last_obstacle_tick = (last_tick - time_since_last_obstacle)
paused = False
lost = False
running = True
while (running):
# Handling various events
for event in pygame.event.get():
if (event.type == pygame.QUIT):
running = False
elif (event.type == pygame.KEYDOWN):
if ((event.key == pygame.K_w) or (event.key == pygame.K_UP)):
player.vel_y = -250.
elif (event.key == pygame.K_ESCAPE):
if (paused):
paused = False
last_tick = pygame.time.get_ticks()
last_obstacle_tick = (last_tick - time_since_last_obstacle)
else:
paused = True
if (not paused):
# Check if the player lost the previous iteration
if (lost):
print("You lost!")
running = False
continue
# Calculate delta_time
current_tick = pygame.time.get_ticks()
delta_time = (current_tick - last_tick) / 1000.0
last_tick = current_tick
# Clear the screen for fresh drawing
screen.fill((156, 204, 255))
# Spawning obstacles every obstacle_interval milliseconds
time_since_last_obstacle = (current_tick - last_obstacle_tick)
if (time_since_last_obstacle > obstacle_interval):
last_obstacle_tick = current_tick
spawn_obstacle()
# Updating the player's movement and position
player.vel_y += (player.acc_y * delta_time)
player.y += (player.vel_y * delta_time)
# Check whether the player is colliding with the horizontal edges
if ((player.y < 0.0) or (player.y > (SCREEN_HEIGHT - player.height))):
lost = True
# Obstacle logic
for obstacle in obstacles:
obstacle.x += (Obstacle.vel_x * delta_time)
# Check whether the obstacle is no longer visible
if (obstacle.x < -obstacle.width):
obstacles.remove(obstacle)
continue
# Check whether the player is colliding with the obstacle
if ((player.x > (obstacle.x - player.width)) and (player.x < (obstacle.x + obstacle.width))):
if ((player.y > (obstacle.y - player.height)) and (player.y < (obstacle.y + obstacle.height))):
lost = True
# Drawing the obstacle
pygame.draw.rect(screen, obstacle.color, pygame.Rect(int(obstacle.x), int(obstacle.y), obstacle.width, obstacle.height))
# Drawing the player
pygame.draw.rect(screen, player.color, pygame.Rect(int(player.x), int(player.y), player.width, player.height))
# Update the entire window
pygame.display.flip()