Simple game where you control space shuttle with mouse click and try to avoid meteors moving in random speed toward you. There are some things I am still developing like rotation and menus, but wanted to get some tips/recommendations on what I have so far.
Thank you in advance!
# Import pygame and random
import pygame
import random
# Initialize the game engine
pygame.init()
#Define colors
WHITE = (255, 255, 255)
# Define done
done = False
# Define function to create new meteor
def create_meteor():
meteor = Meteor(WHITE, width, height)
meteor_sprites_list.add(meteor)
# Define player class
class Player(pygame.sprite.Sprite):
def __init__(self, filename, color, HW, HH):
super().__init__()
# Set height, width
self.image = pygame.image.load("player.png").convert_alpha()
# Set background color to transparent
self.image.set_colorkey(color)
# Make top-left corner the passed in locatin
self.rect = pygame.rect.Rect((HW, HH), self.image.get_size())
# Gravity
self.dy = 0
def ignite(self):
self.dy = -400
def update(self, dt, screen):
# Apply gravity
self.dy = min(400, self.dy + 40)
self.rect.y += self.dy * dt
# What happens if go to border of screen
if(self.rect.top <= 0): #top
self.rect.y = 0
self.dy = -4
elif(self.rect.bottom >= height): #ground
self.rect.y = (526-self.rect.height)
#blit image to screen
screen.blit(self.image, self.rect)
# Define new clas for meteor
class Meteor(pygame.sprite.Sprite):
def __init__(self, color, width, height):
# Takes in parameters for color, width (x position) , and height (y postion)
# Call the parent class
super().__init__()
# Make list of image file location
self.meteor_list = ["meteors/meteor1.png"]
# Randomly select meteor from above list
self.new_meteor = random.choice(self.meteor_list)
# Load graphic that is in file folder
self.image = pygame.image.load(self.new_meteor).convert_alpha()
# Set background to transparent
self.image.set_colorkey(color)
# Fetch the rectangle object that has the dimensions of the image
self.rect = self.image.get_rect()
# Random starting location
self.rect.x = random.randrange(width, (width + 300))
self.rect.y = random.randrange(0, height)
# Random movement to the left
self.change_x = random.randrange(-10,-5)
self.change_y = random.randrange(-4,3)
# Set angle for rotate attribute
# ---- Attributes
def reset_pos(self, screen):
# List of meteors
self.meteor_list = ["meteors/meteor1.png"]
# Pick random meteor from above list
self.new_meteor = random.choice(self.meteor_list)
# Load graphic that is in file folder
self.image = pygame.image.load(self.new_meteor).convert_alpha()
# Set background to transparent
self.image.set_colorkey(WHITE)
# Fetch the rectangle object that has the dimensions of the image
self.rect = self.image.get_rect()
# Reset postion of bad block when they reach the bottom of the screen
self.rect.x = random.randrange(width, (width +100))
self.rect.y = random.randrange(0, height)
# Random movement to the left
self.change_x = random.randrange(-10,-5)
self.change_y = random.randrange(-4,3)
# What meteor does each cycle through
def update(self):
# Add rotation
#pygame.transform.rotate(self.image, 115)
# Move bad block down 3 at a time
self.rect.x += self.change_x
self.rect.y += self.change_y
#blit image to screen
screen.blit(self.image, self.rect)
# Reset if falls off screen
if self.rect.right < 0:
self.reset_pos(screen)
if self.rect.top > height:
self.reset_pos(screen)
if self.rect.bottom < 0:
self.reset_pos(screen)
# Class to show the how far rocketship has travelled
class Distance(pygame.sprite.Sprite):
def __init__(self):
# Call the parent class
super().__init__()
# Get time since init was called
time = pygame.time.get_ticks()
# Convert milliseconds to seconds, 1 second = 1 km
travel_distance = round(time/1000, 2)
# Format the font to be used
font = pygame.font.SysFont ("transistor", 25, True, False)
# Create text to be displayed
text = font.render("You've travelled " + str(travel_distance) + " kms", True, WHITE)
# Center text
text_rect = text.get_rect(center=(HW, HH))
# Blit text to the screen
screen.blit(text, text_rect)
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
background_size = pygame.image.load("background.png")
# Get dimensions of background
width = background_size.get_width()
height = background_size.get_height()
HW, HH = width/2, height/2
size = (width, height)
screen = pygame.display.set_mode(size)
x = 0
# Load image for star background
background = pygame.image.load("background.png").convert()
# Seperate becuase error when placed before screen
#Set caption
pygame.display.set_caption("")
# Creates a list of sprites. Each object in program is added to list. Managed by a class called "group"
meteor_sprites_list = pygame.sprite.Group()
# Create spaceship
player = Player("player.png", WHITE, HW, HH)
# Create meteor sprites on the screen
for i in range(4):
create_meteor()
#-----Main Program Loop
while not done:
dt = clock.tick(30)
# Main event Loop
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.MOUSEBUTTONDOWN:
player.ignite()
#-----Game Logic
# Draw background and move to the left
rel_x = x % width
screen.blit(background, (rel_x - width, 0))
if rel_x < width:
screen.blit(background, (rel_x, 0))
x -= 2
# Display text on screen
Distance()
# Update Sprites
# Update meteor sprite
meteor_sprites_list.update()
# Update player sprite
player.update(dt/1000. , screen)
# Check to see if player has collided with meteor
meteor_hit_list = pygame.sprite.spritecollide(player, meteor_sprites_list, True, pygame.sprite.collide_circle)
# Event if player collides with meteor
for item in meteor_hit_list:
print("Shuttle hit")
create_meteor()
meteor_hit_list.remove(item)
# Make into game over screen and display distance
# Update the screen with what we've drawn.
pygame.display.flip()
# Make sure to quit
pygame.quit()
1 Answer 1
Good job! Here are my suggestions (roughly ordered from important to less important):
Separate the game logic from the drawing/blitting code. That will make your main loop cleaner and the code will probably be easier to extend. So don't blit the sprite images in their update
methods.
Sprites in a sprite group can be blitted by calling the draw
method of the group: meteor_group.draw(screen)
.
I've renamed the meteor_sprites_list
to meteor_group
, because pygame groups are not lists.
If you want to keep the player sprite separate, just blit it in the while loop: screen.blit(player.image, player.rect)
.
Load the images globally or in a separate module, because it's inefficient to load them repeatedly from the hard disk in the main loop. The self.meteor_list
can also be a global list:
PLAYER_SURFACE = pygame.image.load("player.png").convert_alpha()
METEOR_LIST = [
pygame.image.load("meteors/meteor1.png").convert_alpha(),
pygame.image.load("meteors/meteor2.png").convert_alpha(),
# etc.
]
# If you have a lot of images, better load them with a for loop and append them.
Then choose and assign a random image in the __init__
method:
self.image = random.choice(METEOR_LIST)
I'm not sure if your images already have transparent backgrounds, if yes, you can just call convert_alpha()
and omit this line:
self.image.set_colorkey(color)
You're loading the background
image twice. Just replace background_size
with background
. And don't forget to convert the surfaces/images, because unconverted surfaces can slow the game down a lot: background = pygame.image.load("background.png").convert()
.
The Distance
class can just be a function.
Also, if you pass the coords as the topleft
argument it doesn't jitter so much: text.get_rect(topleft=(HW-100, HH))
. Or you could just blit the text
at these coordinates: screen.blit(text, (HW-100, HH))
.
def distance(screen):
"""Show how far the rocketship has travelled."""
# Get time since init was called
time = pygame.time.get_ticks()
# Convert milliseconds to seconds, 1 second = 1 km
travel_distance = round(time/1000, 2)
text = font.render("You've travelled " + str(travel_distance) + " kms", True, WHITE)
screen.blit(text, (HW-100, HH))
The while loop and the corresponding variables could be put into a main
function or a class to clean up the global namespace.
You've got a lot of comments in your example which make the code a bit more difficult to read. Add comments only if something could be unclear (explain why the code is needed). Some comments are even wrong or outdated.
This one is unnecessary, since the code is self-explanatory:
# Set caption
pygame.display.set_caption("")
This one is more useful:
# Reset if falls off screen
if self.rect.right < 0:
self.reset_pos(screen)
You don't need to remove the hit meteor from the meteor_hit_list
because you'll create a new list in the next frame anyway:
meteor_hit_list.remove(item) # Just remove this line.
To stop the player at the bottom of the screen:
elif self.rect.bottom >= height:
self.rect.bottom = screen.get_height()
There are some unnecessary parentheses:
if(self.rect.top <= 0):
self.rect.y = (526-self.rect.height)
Take a look at PEP 8.
And happy coding!