Can someone review the code for this dodging game I made a while back? I want to see if there are any improvements or optimizations I can make as I am more or less a Python beginner.
# A basic dodging game in Pygame
import pygame, sys
import numpy as np
from pathlib import Path
pygame.init()
# Defines window size
win_size = (900, 600)
window = pygame.display.set_mode(win_size)
pygame.display.set_caption("Dodge")
clock = pygame.time.Clock()
scores = np.loadtxt(str(Path().absolute()) + "/scores.txt").astype(np.int64)
try:
high_score = scores.max()
except ValueError:
high_score = 0
running = True
timer = 0
projectile_speed = 7 # Defines projectile speed
score = 0
score_font = pygame.font.SysFont("dejavusansmono", 24)
game_over_font = pygame.font.SysFont("dejavusansmono", 48)
title_text = score_font.render("Score: ", True, (45, 45, 45))
score_text = score_font.render(str(score), True, (110, 110, 110))
# AABB Collision implementation
def aabb(r1, r2):
return r1.x < r2.x + r2.width and r1.x + r1.width > r2.x and r1.y < r2.y + r2.height and r1.y + r1.height > r2.y
# Displays game over content if input(collision) is true
def game_over(val):
if val:
game_over_text = game_over_font.render("GAME OVER!", True, (0, 0, 0))
window.blit(game_over_text, (win_size[0]/2 - game_over_text.get_width()/2, win_size[1]/2 - game_over_text.get_height()/2))
pygame.display.update()
pygame.time.wait(1000)
if score > high_score:
title_text = score_font.render("New Highscore!", True, (45, 45, 45))
window.blit(title_text, (win_size[0]/2 - game_over_text.get_width()/2, win_size[1]/2 + game_over_text.get_height()/2))
else:
title_text = score_font.render("Final Score: ", True, (45, 45, 45))
score_text = score_font.render(str(score), True, (110, 110, 110))
window.blit(title_text, (win_size[0]/2 - game_over_text.get_width()/2, win_size[1]/2 + game_over_text.get_height()/2))
window.blit(score_text, (win_size[0]/2 - game_over_text.get_width()/2 + title_text.get_width(), win_size[1]/2 + game_over_text.get_height()/2))
pygame.display.update()
pygame.time.wait(1000)
return False
return True
# Object class that parents Player and Projectile classes
class Object:
global objects
objects = []
def __init__(self, x, y, w, h):
self.x = x
self.y = y
self.width = w
self.height = h
objects.append(self)
def draw(self):
pygame.draw.rect(window, "red", (self.x, self.y, self.width, self.height)) # Default draw method
# Defines Projectile class (child of Object)
class Projectile(Object):
# Global projectile list stores all projectile data
global projectiles
projectiles = []
def __init__(self, x, y, w, h, speed):
super().__init__(x, y, w, h)
self.dir = -1 * (x/abs(x)) # Calucates required sprite direction based on input value
self.speed = speed * self.dir
projectiles.append(self)
def update(self):
self.x += self.speed
@classmethod
def update_all(self):
valid_projectiles = [projectile for projectile in projectiles if (projectile.x > 0 - projectile.width and projectile.dir == -1) or (projectile.x < win_size[0] and projectile.dir == 1)] # Projectiles within bounds
invalid_projectiles = [projectiles.pop(projectiles.index(projectile)) for projectile in projectiles if (projectile.x < 0 - projectile.width and projectile.dir == -1) or (projectile.x > win_size[0] and projectile.dir == 1)] # Projectiles NOT within bounds
del invalid_projectiles
# Running all projectile methods
for projectile in valid_projectiles:
projectile.update()
projectile.draw()
# Defines Player class (child of Object)
class Player(Object):
def __init__(self, x, y, w, h, speed):
super().__init__(x, y, w, h)
self.speed = speed
self.touched = False
def draw(self):
pygame.draw.rect(window, "blue", (self.x, self.y, self.width, self.height)) # Player draw method
def update(self):
self.keys = pygame.key.get_pressed()
self.x += self.speed * (self.keys[pygame.K_RIGHT] - self.keys[pygame.K_LEFT]) # Left/Right Key Input
self.y += self.speed * (self.keys[pygame.K_DOWN] - self.keys[pygame.K_UP]) # Down/Up Key Input
def set_bounds(self):
# Restricts player to screen dimensions
if self.x < 0:
self.x = 0
elif self.x > win_size[0] - self.width:
self.x = win_size[0] - self.width
if self.y < 0:
self.y = 0
elif self.y > win_size[1] - self.height:
self.y = win_size[1] - self.height
self.yvel = 0
def collision(self):
# Defines collisions w/ Projectiles
for projectile in projectiles:
if aabb(player, projectile):
return True
return False
def update_all(self):
# Running Player methods
self.update()
self.set_bounds()
self.draw()
player = Player(win_size[0]/2 - 16, win_size[1]/2 - 16, 32, 32, 5) # Defines player
while running: # Game loop
window.fill("white") # Clears screen
timer += 1
if timer % 10 == 0: # New Projectile object every 10 frames
Projectile(np.random.choice((-32, win_size[0])), np.random.randint(0, win_size[1] - 32), 32, 16, projectile_speed - np.random.random())
if timer % 60 == 0: # Score increments by 1 every 60 frames
score += 1
projectile_speed += 0.2 # Increases every second: very basic difficulty scaling
if projectile_speed > 14: # Caps off bullet speed
projectile_speed = 14
player.update_all() # Updates Player methods
Projectile.update_all() # Updates Projectile methods
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.quit()
sys.exit()
title_text = score_font.render("Score: ", True, (45, 45, 45))
score_text = score_font.render(str(score), True, (110, 110, 110))
window.blit(title_text, (0, 0))
window.blit(score_text, (title_text.get_width(), 0))
title_text = score_font.render("Highscore: ", True, (45, 45, 45))
score_text = score_font.render(str(high_score), True, (110, 110, 110))
window.blit(title_text, (0, title_text.get_height()))
window.blit(score_text, (title_text.get_width(), title_text.get_height()))
title_text = score_font.render(str(np.int64(clock.get_fps())), True, (45, 45, 45))
window.blit(title_text, (0, win_size[1] - title_text.get_height()))
running = game_over(player.collision())
# Constantly calls game_over function
# Returns false when player.collision() is true
clock.tick(60)
pygame.display.update()
scores = np.append(scores, score)
np.savetxt("scores.txt", scores)
pygame.quit()
sys.exit()
win_size
andprojectile_speed
. Everything else needs to be moved into functions and classes. Move all of your top-level code intomain()
. Once that's all done (as an edit to this question before any answers are posted, or in a new question if answers are posted on this question) I'd be happy to review it. \$\endgroup\$