6
\$\begingroup\$

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()
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

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!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.