Break Into Program – Space Invaders
During CoderDojo #20 we looked at programming Space Invaders in Python, using the excellent PyGame library. Here’s the finished code. If you want to progress this further, I’d look at:
- Added scoring
- Animate the Space Invaders as they march along the screen
- A Game Over message
- Sound
The graphics for this demo can be downloaded here:
Unzip into the graphics directory (we put them in C:\Python\Dev\CoderDojo\Graphics).
Enjoy!
The Science Bit
# Import the libraries # import pygame import random # Initialise PyGame # pygame.init() # Some global variables # win_w = 800 # Window width win_h = 600 # Window height sprites = [] # List of sprites invader_flip = False # Flag to reverse invader direction invader_count = 0 # Invader count invader_missile_count = 0 # Invader missile count # Create a screen surface to draw on, and a clock # screen = pygame.display.set_mode((win_w, win_h)) clock = pygame.time.Clock() # Set the key repeat to 1ms delay, 1ms repeat # pygame.key.set_repeat(1,1) # Load some images ------------------------------------------------------------- image_invader = pygame.image.load("../Graphics/space_invader.png").convert() image_player = pygame.image.load("../Graphics/player.png").convert() image_missile = pygame.image.load("../Graphics/missile.png").convert() # This bit sets anything white (RGB=255,255,255) in the graphics to be transparent # image_invader.set_colorkey((255,255,255)) image_player.set_colorkey((255,255,255)) image_missile.set_colorkey((255,255,255)) # The Sprite class ------------------------------------------------------------- class Sprite: # # Some basic variables, just for this sprite # active = None # Whether the sprite is active or off xp = None # X position of the sprite yp = None # Y position of the sprite image = None # Sprite image type = None # Sprite type (string) fn_move = None # Function to move the sprite # # This is called when we initialise the Sprite class # def __init__(self): self.active = False # # A class function to move the sprite # def move(self): if(self.fn_move is not None): self.fn_move(self) return # # A class function to draw the sprite # def draw(self): screen.blit(self.image, (self.xp, self.yp)) # Set up a list of Sprites # max_sprites = 200 # Number of sprites we want to create sprites = [Sprite() for i in range(max_sprites)] # Create a list of Sprites # Helper functions ------------------------------------------------------------ # Find index of next available free sprite slot in list # Returns index in array of sprites, or -1 if no slots left # Start from 2, as 0 and 1 are reserved for player and player missile # def find_next_available_sprite(): for i in range(2, max_sprites): if sprites[i].active == False: return i return -1 # Movement functions ---------------------------------------------------------- # Move an invader # def move_invader(self): global invader_flip global invader_count global invader_missile_count global win_w global done speed = 1 # Invader speed multiplier # Change the speed multiplier based upon number of invaders # if invader_count < 40: speed = 2 if invader_count < 30: speed = 3 if invader_count < 20: speed = 4 if invader_count < 5: speed = 6 if invader_count == 1: speed = 8 # Move the invader horizontally # self.xp += self.xv * speed # Basic velocity times the speed multiplier # Check whether invader hit side of screen and flip all invaders # if self.xp >= win_w - 35 or self.xp <= 0: invader_flip = True # Check whether invader has reached the ground and end game # if self.yp >= sprites[0].yp: print("Game Over: Invaders have landed!") done = 3 # Check whether invader has hit the missile, if active, and make bullet and invader inactive # if sprites[1].active: if sprites[1].xp > self.xp and sprites[1].xp < self.xp + 35 and sprites[1].yp > self.yp and sprites[1].yp < self.yp + 35: self.active = False sprites[1].active = False invader_count -= 1 # Randomly drop an invader missile # if invader_missile_count < 5 and random.randint(0, 1000) > 990: i = find_next_available_sprite() if i != -1: sprites[i].active = True sprites[i].type = "invader missile" sprites[i].image = image_missile sprites[i].xp = self.xp + 14 sprites[i].yp = self.yp + 30 sprites[i].fn_move = move_invader_missile invader_missile_count += 1 # Move the player # def move_player(self): keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: self.xp -= 4 if keys[pygame.K_RIGHT]: self.xp += 4 if keys[pygame.K_SPACE]: if sprites[1].active == False: sprites[1].active = True sprites[1].xp = self.xp + 14 sprites[1].yp = self.yp # Move a player missile # def move_player_missile(self): self.yp -= 12 if self.yp < 0: self.active = False # Move an invader missile # def move_invader_missile(self): global invader_missile_count global done self.yp += 4 if self.yp > win_h: self.active = False invader_missile_count -= 1 else: if self.xp > sprites[0].xp and self.xp < sprites[0].xp + 35 and self.yp > sprites[0].yp and self.yp < sprites[0].yp + 35: print("Game Over: Hit by missile!") done = 2 # If any of the invaders have tripped the invader flip flag, then reverse invader direction # and move the invaders down # def check_invader_flip(): global invader_flip global max_sprites if invader_flip: invader_flip = False for i in range(0, max_sprites): if sprites[i].active and sprites[i].type == "invader": sprites[i].xv = -sprites[i].xv sprites[i].yp += 12 # Initialise the sprites ------------------------------------------------------ # Create the player sprite # sprites[0].sprite_type = "player" # This is the sprite type sprites[0].active = True # Set the sprite to be active sprites[0].xp = win_w//2 # Set X position sprites[0].yp = win_h - 40 # Set Y position sprites[0].image = image_player # Set image (loaded previously) sprites[0].fn_move = move_player # Set function to move sprite # Create the player missile sprite, but don't make it active yet! # sprites[1].type = "player missile" sprites[1].image = image_missile sprites[1].fn_move = move_player_missile while True: # Loop through the list and initialise each Sprite in it # i = 2 for row in range(0, 5): for col in range(0, 11): sprites[i].type = "invader" sprites[i].active = True sprites[i].xp = col * 50 sprites[i].yp = row * 50 sprites[i].xv = 1 sprites[i].image = image_invader sprites[i].fn_move = move_invader i += 1 invader_count += 1 # Main Game Loop --------------------------------------------------------------- done = 0 while done == 0: # # Pygame event handler # for event in pygame.event.get(): # Check for window close if event.type == pygame.QUIT: done = -1 # Move everything by calling the Sprite's move function # for i in range(0, max_sprites): if sprites[i].active: sprites[i].move() # Call function to reverse all invaders direction if invader_flip flag set # check_invader_flip() # Clear the screen # screen.fill((255,255,255)) # Draw the Sprites by calling their draw function # for i in range(0, max_sprites): if sprites[i].active: sprites[i].draw() # Update the surface to the screen and lock at 60fps # pygame.display.flip() clock.tick(60) # Check for end of game # if invader_count == 0: print("Level Up: Killed all invaders!") done = 1 # Exit unless we are levelling up (done=1) # if done != 1: break # Clear any missiles before we loop around again! # for i in range(0, max_sprites): if sprites[i].active and (sprites[i].type == "player missile" or sprites[i].type == "invader_missile"): sprites[i].active = False pygame.quit()