This Space Invaders-style game, crafted using Pygame, offers a modern twist on the classic arcade shooter, blending nostalgic gameplay with simple yet engaging graphics. Players control a spaceship, maneuvering left and right across the bottom of the screen, with the mission to defend Earth from an onslaught of descending aliens. Utilizing Pygame’s powerful capabilities, this game brings to life vibrant shapes and movements, creating a visually stimulating experience without relying on complex assets.
At the heart of the game is the player’s spaceship, depicted as a green rectangle, which players navigate to dodge incoming enemy fire and to align shots against the enemies. Enemies, represented by red rectangles, move in a predictable pattern from side to side while gradually descending towards the player. The game introduces a thrilling challenge as players must shoot these enemies using bullets, depicted as white rectangles, fired from the spaceship.
The gameplay mechanics are straightforward yet captivating, requiring players to use keyboard inputs to control the spaceship’s movements and to fire bullets. A collision detection system adds depth to the game, where bullets that successfully hit enemies result in the enemy being reset to a new position at the top of the screen, simulating their destruction. This mechanic not only tests the player’s reaction time and precision but also provides a satisfying feedback loop for successful hits.
The game’s tempo is regulated by a clock mechanism, ensuring that the action remains consistent and fluid across different hardware capabilities. This timing control is crucial for maintaining a balanced challenge, allowing players of all skill levels to enjoy the game without feeling overwhelmed by too rapid movements or actions.
One of the game’s strengths lies in its simplicity and the ease with which it can be modified and expanded. Developers and enthusiasts can easily introduce new features such as multiple enemy types, varying levels of difficulty, scoring systems, and even multiplayer capabilities. This flexibility makes it an excellent project for those looking to dive into game development or for educators seeking a fun and interactive way to teach programming concepts.
In summary, this Space Invaders-style game not only pays homage to the classic arcade era but also serves as a canvas for creativity and learning in the realm of game development. Its reliance on basic shapes for graphics ensures that the focus remains on the gameplay mechanics, making it a timeless and accessible option for both developers and players alike.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
import pygame import random import math # Initialize Pygame pygame.init() # Create a clock object clock = pygame.time.Clock() # Create the screen screen_width = 800 screen_height = 600 screen = pygame.display.set_mode((screen_width, screen_height)) # Background color background_color = (0, 0, 0) # Title and Icon pygame.display.set_caption("Space Invaders - Shape Version") icon = pygame.Surface((32, 32)) # Placeholder for icon icon.fill((255, 255, 255)) pygame.display.set_icon(icon) # Player player_color = (0, 255, 0) player_width = 40 player_height = 30 player_x = 370 player_y = 480 player_x_change = 0 # Enemy enemy_color = (255, 0, 0) enemy_width = 40 enemy_height = 30 enemy_x = random.randint(0, screen_width - enemy_width) enemy_y = random.randint(50, 150) enemy_x_change = 2 enemy_y_change = 40 # Bullet bullet_color = (255, 255, 255) bullet_width = 10 bullet_height = 20 bullet_x = 0 bullet_y = 480 bullet_x_change = 0 bullet_y_change = 10 bullet_state = "ready" def player(x, y): pygame.draw.rect(screen, player_color, (x, y, player_width, player_height)) def enemy(x, y): pygame.draw.rect(screen, enemy_color, (x, y, enemy_width, enemy_height)) def fire_bullet(x, y): global bullet_state bullet_state = "fire" pygame.draw.rect(screen, bullet_color, (x + 15, y - bullet_height, bullet_width, bullet_height)) def is_collision(enemy_x, enemy_y, bullet_x, bullet_y): distance = math.sqrt((math.pow(enemy_x - bullet_x, 2)) + (math.pow(enemy_y - bullet_y, 2))) if distance < 27: return True return False # Game Loop running = True while running: screen.fill(background_color) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Keystroke events to move the spaceship if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: player_x_change = -5 if event.key == pygame.K_RIGHT: player_x_change = 5 if event.key == pygame.K_SPACE: if bullet_state == "ready": bullet_x = player_x fire_bullet(bullet_x, bullet_y) if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT: player_x_change = 0 # Player Movement player_x += player_x_change if player_x <= 0: player_x = 0 elif player_x >= screen_width - player_width: player_x = screen_width - player_width # Enemy Movement enemy_x += enemy_x_change if enemy_x <= 0: enemy_x_change = 2 enemy_y += enemy_y_change elif enemy_x >= screen_width - enemy_width: enemy_x_change = -2 enemy_y += enemy_y_change # Bullet Movement if bullet_y <= 0: bullet_y = player_y bullet_state = "ready" if bullet_state == "fire": fire_bullet(bullet_x, bullet_y) bullet_y -= bullet_y_change # Collision collision = is_collision(enemy_x, enemy_y, bullet_x + 15, bullet_y) if collision: bullet_y = player_y bullet_state = "ready" enemy_x = random.randint(0, screen_width - enemy_width) enemy_y = random.randint(50, 150) player(player_x, player_y) enemy(enemy_x, enemy_y) pygame.display.update() # Limit the frame rate to slow down the game clock.tick(50) # Adjust this value to control game speed |
Let’s break down the Space Invaders-style game with code snippets to illustrate each part of the explanation.
Initialization
First, necessary libraries are imported, Pygame is initialized, and a Clock object is created to manage the game’s framerate.
1 2 3 4 5 6 |
import pygame import random import math pygame.init() clock = pygame.time.Clock() |
Screen Setup
The screen dimensions are set, the display surface is created, and basic window properties like title and icon are configured.
1 2 3 4 5 6 7 |
screen_width = 800 screen_height = 600 screen = pygame.display.set_mode((screen_width, screen_height)) pygame.display.set_caption("Space Invaders - Shape Version") icon = pygame.Surface((32, 32)) # Placeholder for icon icon.fill((255, 255, 255)) pygame.display.set_icon(icon) |
Game Entities
Here’s how the player, enemy, and bullet are set up, including their initial positions and characteristics.
Player Setup:
1 2 3 4 5 6 |
player_color = (0, 255, 0) player_width = 40 player_height = 30 player_x = 370 player_y = 480 player_x_change = 0 |
Enemy Setup:
1 2 3 4 5 6 7 |
enemy_color = (255, 0, 0) enemy_width = 40 enemy_height = 30 enemy_x = random.randint(0, screen_width - enemy_width) enemy_y = random.randint(50, 150) enemy_x_change = 2 enemy_y_change = 40 |
Bullet Setup:
1 2 3 4 5 6 7 8 |
bullet_color = (255, 255, 255) bullet_width = 10 bullet_height = 20 bullet_x = 0 bullet_y = 480 bullet_x_change = 0 bullet_y_change = 10 bullet_state = "ready" |
Functions
Functions for drawing the player, enemy, and firing the bullet, as well as for detecting collisions.
Draw Player:
1 2 |
def player(x, y): pygame.draw.rect(screen, player_color, (x, y, player_width, player_height)) |
Draw Enemy:
1 2 |
def enemy(x, y): pygame.draw.rect(screen, enemy_color, (x, y, enemy_width, enemy_height)) |
Fire Bullet:
1 2 3 4 |
def fire_bullet(x, y): global bullet_state bullet_state = "fire" pygame.draw.rect(screen, bullet_color, (x + 15, y - bullet_height, bullet_width, bullet_height)) |
Collision Detection:
1 2 3 4 5 |
def is_collision(enemy_x, enemy_y, bullet_x, bullet_y): distance = math.sqrt((math.pow(enemy_x - bullet_x, 2)) + (math.pow(enemy_y - bullet_y, 2))) if distance < 27: return True return False |
Game Loop
The main game loop processes events, updates game states, draws elements to the screen, and regulates the framerate.
Event Handling & Movement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: player_x_change = -5 if event.key == pygame.K_RIGHT: player_x_change = 5 if event.key == pygame.K_SPACE: if bullet_state == "ready": bullet_x = player_x fire_bullet(bullet_x, bullet_y) if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT: player_x_change = 0 |
Updating & Drawing:
1 2 3 4 5 6 |
player_x += player_x_change enemy_x += enemy_x_change if bullet_state == "fire": fire_bullet(bullet_x, bullet_y) collision = is_collision(enemy_x, enemy_y, bullet_x + 15, bullet_y) pygame.display.update() |
Framerate Control:
1 |
clock.tick(50) |
This code structure sets the foundation for a Space Invaders-style game, detailing the initialization, game loop, entity management, and event handling using Pygame.
Fire multiple bullets simultaneously
To allow the player to fire multiple bullets without waiting for one to finish its trajectory, you need to manage a list of bullets rather than a single bullet. Each time the player fires, you’ll add a new bullet to the list, and then you’ll update and draw each bullet in the list within the game loop. Here’s how you can adjust your game to support multiple bullets:
Adjusting Bullet Variables
First, change the bullet variables to support multiple bullets. Instead of having a single set of variables for the bullet’s position and state, you’ll use a list to keep track of all bullets:
1 2 3 4 5 |
# Bullet bullet_color = (255, 255, 255) bullet_width = 10 bullet_height = 20 bullets = [] # List to store bullets |
Modifying the Fire Bullet Function
Update the fire_bullet
function to create a new bullet and add it to the bullets
list whenever the player fires:
1 2 3 |
def fire_bullet(x, y): # Global declaration not needed anymore since we're modifying a list, not reassigning it bullets.append({"x": x + 15, "y": y - bullet_height, "state": "fire"}) |
Updating Bullets in the Game Loop
Modify the game loop to update and draw each bullet in the bullets
list. Also, handle removing bullets from the list when they move off-screen:
1 2 3 4 5 6 7 |
# Inside the game loop for bullet in bullets[:]: # Iterate over a copy of the bullets list if bullet["y"] <= -bullet_height: bullets.remove(bullet) # Remove the bullet if it goes off-screen else: bullet["y"] -= bullet_y_change # Move the bullet up pygame.draw.rect(screen, bullet_color, (bullet["x"], bullet["y"], bullet_width, bullet_height)) |
Handling Bullet Collisions
You’ll also need to adjust the collision detection to check each bullet in the bullets
list against each enemy:
1 2 3 4 5 |
for bullet in bullets[:]: # Iterate over a copy of the bullets list collision = is_collision(enemy_x, enemy_y, bullet["x"], bullet["y"]) if collision: bullets.remove(bullet) # Remove the bullet on collision # Reset the enemy and possibly increase the score or play a sound here |
Firing Bullets
Finally, update the event handling section where the SPACE key is pressed to fire a bullet without checking the bullet state:
1 2 3 4 |
# Event handling for player shooting if event.type == pygame.KEYDOWN: if event.key == pygame.K_SPACE: fire_bullet(player_x, player_y) # Fire a new bullet |
With these adjustments, your game now supports firing multiple bullets simultaneously, allowing for a more dynamic and engaging player experience. You’ve replaced the single bullet mechanic with a list that can store and manage multiple bullets, updating each one’s position and handling collisions individually. This approach can be further extended to include features like different bullet types, power-ups, or more complex enemy behaviors.
Here’s a simplified version of the Space Invaders game incorporating the feature that allows the player to fire multiple bullets simultaneously without waiting for one to disappear or hit an enemy. This version focuses on the core mechanics of firing and managing multiple bullets.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
import pygame import random import math # Initialize Pygame pygame.init() # Create the screen screen_width = 800 screen_height = 600 screen = pygame.display.set_mode((screen_width, screen_height)) # Background color background_color = (0, 0, 0) # Title and Icon pygame.display.set_caption("Space Invaders - Multi-Bullet Feature") icon = pygame.Surface((32, 32)) # Placeholder for icon icon.fill((255, 255, 255)) pygame.display.set_icon(icon) # Player player_color = (0, 255, 0) player_width = 40 player_height = 30 player_x = 370 player_y = 480 player_x_change = 0 # Enemy enemy_color = (255, 0, 0) enemy_width = 40 enemy_height = 30 enemy_x = random.randint(0, screen_width - enemy_width) enemy_y = random.randint(50, 150) enemy_x_change = 2 enemy_y_change = 40 # Bullets bullet_color = (255, 255, 255) bullet_width = 10 bullet_height = 20 bullets = [] # List to store bullets' information # Function to draw the player def player(x, y): pygame.draw.rect(screen, player_color, (x, y, player_width, player_height)) # Function to draw the enemy def enemy(x, y): pygame.draw.rect(screen, enemy_color, (x, y, enemy_width, enemy_height)) # Function to fire a bullet def fire_bullet(x, y): bullets.append({"x": x + 15, "y": y - bullet_height, "state": "fire"}) # Function for collision detection def is_collision(enemy_x, enemy_y, bullet_x, bullet_y): distance = math.sqrt((math.pow(enemy_x - bullet_x, 2)) + (math.pow(enemy_y - bullet_y, 2))) return distance < 27 # Game Loop running = True while running: screen.fill(background_color) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: player_x_change = -5 if event.key == pygame.K_RIGHT: player_x_change = 5 if event.key == pygame.K_SPACE: fire_bullet(player_x, player_y) # Allows firing multiple bullets if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT: player_x_change = 0 # Player Movement player_x += player_x_change player_x = max(0, min(player_x, screen_width - player_width)) # Enemy Movement enemy_x += enemy_x_change if enemy_x <= 0 or enemy_x >= screen_width - enemy_width: enemy_x_change *= -1 enemy_y += enemy_y_change # Update and draw all bullets for bullet in bullets[:]: # Iterate over a copy of the bullets list if bullet["y"] <= -bullet_height: bullets.remove(bullet) # Remove bullets that go off-screen else: bullet["y"] -= 10 # Bullet speed pygame.draw.rect(screen, bullet_color, (bullet["x"], bullet["y"], bullet_width, bullet_height)) # Check for collision if is_collision(enemy_x, enemy_y, bullet["x"], bullet["y"]): bullets.remove(bullet) # Reset the enemy position for simplicity; you could also increase score or create an explosion effect enemy_x = random.randint(0, screen_width - enemy_width) enemy_y = random.randint(50, 150) player(player_x, player_y) enemy(enemy_x, enemy_y) pygame.display.update() # Clock to control the game speed pygame.time.Clock().tick(60) # Running the game at 60 FPS |
Key Changes from the Original Version:
- Multiple Bullets: The game now supports firing multiple bullets simultaneously. Each bullet is added to the
bullets
list with itsx
,y
coordinates, and"fire"
state when the spacebar is pressed. - Bullet Movement and Rendering: Each bullet in the
bullets
list is updated and rendered inside the game loop. Bullets that move off-screen are removed from the list. - Collision Detection: The collision detection now iterates through each bullet in the
bullets
list to check for collisions with the enemy. Upon collision, the bullet is removed, and the enemy is reset to a new position.
This version enhances the gameplay by allowing the player to fire multiple bullets, adding a layer of strategy and action to the game.
Multiple enemies
To introduce multiple enemies that descend upon the player, you’ll modify the game to manage a list of enemies, similar to how we handled multiple bullets. Each enemy will have its own position and movement pattern. Here’s how to integrate multiple enemies into the game:
1. Modify Enemy Initialization
Change the enemy setup to use a list of dictionaries, each representing an enemy with its own properties.
1 2 3 4 5 6 7 8 9 10 11 12 |
# Enemies setup enemies = [] num_of_enemies = 5 for i in range(num_of_enemies): enemies.append({ "x": random.randint(0, screen_width - enemy_width), "y": random.randint(50, 150), "x_change": 2, "y_change": 40, "color": (255, 0, 0) # This allows for different colors if desired }) |
2. Update Enemy Functions
Adjust the enemy drawing and movement logic to handle multiple enemies.
Draw Enemies: Loop through the enemies
list to draw each enemy.
1 2 3 |
def draw_enemies(enemies): for enemy in enemies: pygame.draw.rect(screen, enemy["color"], (enemy["x"], enemy["y"], enemy_width, enemy_height)) |
Update Enemy Movement: In the game loop, update each enemy’s position. If an enemy hits the screen edge, change its direction and move it down.
1 2 3 4 5 6 |
for enemy in enemies: enemy["x"] += enemy["x_change"] if enemy["x"] <= 0 or enemy["x"] >= screen_width - enemy_width: enemy["x_change"] *= -1 enemy["y"] += enemy["y_change"] # Optionally, you can add logic here to end the game if enemies reach a certain point. |
3. Update Collision Detection
Modify the collision detection to check each bullet against each enemy.
1 2 3 4 5 6 7 |
for bullet in bullets[:]: # Make a copy to modify the list during iteration for enemy in enemies[:]: # Similarly, iterate over a copy of the enemies list if is_collision(enemy["x"], enemy["y"], bullet["x"], bullet["y"]): bullets.remove(bullet) enemies.remove(enemy) # Here, you could increment the score or play a sound effect break # Break out of the inner loop to avoid modifying the list further |
4. Draw Everything in the Game Loop
Finally, ensure everything is drawn appropriately within the game loop.
1 2 |
# Inside the game loop, after handling events and before updating the display draw_enemies(enemies) |
Complete Integration
Here’s how these pieces integrate into the game loop, illustrating the handling of multiple enemies and bullets:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# Game Loop running = True while running: screen.fill(background_color) # Event handling, player movement, and bullet firing code remains the same # Update bullet positions and check for collisions for bullet in bullets[:]: if bullet["y"] <= -bullet_height: bullets.remove(bullet) else: bullet["y"] -= bullet_y_change pygame.draw.rect(screen, bullet_color, (bullet["x"], bullet["y"], bullet_width, bullet_height)) for enemy in enemies[:]: if is_collision(enemy["x"], enemy["y"], bullet["x"], bullet["y"]): bullets.remove(bullet) enemies.remove(enemy) break # Update enemy positions for enemy in enemies: enemy["x"] += enemy["x_change"] if enemy["x"] <= 0 or enemy["x"] >= screen_width - enemy_width: enemy["x_change"] *= -1 enemy["y"] += enemy["y_change"] player(player_x, player_y) draw_enemies(enemies) pygame.display.update() pygame.time.Clock().tick(60) |
With these adjustments, your game will now feature multiple enemies descending upon the player, enhancing the challenge and engagement of the gameplay. This setup also lays the groundwork for further enhancements, such as introducing varied enemy types, formations, and behaviors.
Incorporating the feature to manage multiple enemies, here’s a complete version of your Space Invaders-style game using Pygame. This code includes the capability for the player to shoot multiple bullets simultaneously and face several enemies descending towards them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
import pygame import random import math # Initialize Pygame pygame.init() # Create the screen screen_width = 800 screen_height = 600 screen = pygame.display.set_mode((screen_width, screen_height)) # Background color background_color = (0, 0, 0) # Title and Icon pygame.display.set_caption("Space Invaders - Enhanced") icon = pygame.Surface((32, 32)) # Placeholder for icon icon.fill((255, 255, 255)) pygame.display.set_icon(icon) # Player player_color = (0, 255, 0) player_width = 40 player_height = 30 player_x = 370 player_y = 480 player_x_change = 0 # Enemies num_of_enemies = 5 enemies = [] for i in range(num_of_enemies): enemies.append({ "x": random.randint(0, screen_width - 40), "y": random.randint(50, 150), "x_change": 2, "y_change": 40, "color": (255, 0, 0) }) # Bullet bullet_color = (255, 255, 255) bullet_width = 10 bullet_height = 20 bullets = [] # Function to draw the player def player(x, y): pygame.draw.rect(screen, player_color, (x, y, player_width, player_height)) # Function to draw enemies def draw_enemies(enemies): for enemy in enemies: pygame.draw.rect(screen, enemy["color"], (enemy["x"], enemy["y"], player_width, player_height)) # Function to fire a bullet def fire_bullet(x, y): bullets.append({"x": x + 15, "y": y - bullet_height}) # Function for collision detection def is_collision(enemy_x, enemy_y, bullet_x, bullet_y): distance = math.sqrt(math.pow(enemy_x - bullet_x, 2) + math.pow(enemy_y - bullet_y, 2)) return distance < 27 # Game Loop running = True while running: screen.fill(background_color) for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: player_x_change = -5 if event.key == pygame.K_RIGHT: player_x_change = 5 if event.key == pygame.K_SPACE: fire_bullet(player_x, player_y) if event.type == pygame.KEYUP: if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT: player_x_change = 0 player_x += player_x_change player_x = max(0, min(player_x, screen_width - player_width)) for bullet in bullets[:]: if bullet["y"] <= -bullet_height: bullets.remove(bullet) else: bullet["y"] -= 10 pygame.draw.rect(screen, bullet_color, (bullet["x"], bullet["y"], bullet_width, bullet_height)) for enemy in enemies[:]: if is_collision(enemy["x"], enemy["y"], bullet["x"], bullet["y"]): bullets.remove(bullet) enemies.remove(enemy) break for enemy in enemies: enemy["x"] += enemy["x_change"] if enemy["x"] <= 0 or enemy["x"] >= screen_width - player_width: enemy["x_change"] *= -1 enemy["y"] += enemy["y_change"] if enemy["y"] >= screen_height: running = False # End game if any enemy reaches the bottom player(player_x, player_y) draw_enemies(enemies) pygame.display.update() pygame.time.Clock().tick(60) # Control the game speed |
Key Enhancements:
- Multiple Enemies: The game now spawns multiple enemies that move horizontally across the screen and descend toward the player. When hit by a bullet, an enemy is removed from the game.
- Multiple Bullets: Players can fire multiple bullets without waiting for one to disappear, enhancing the game’s dynamics and allowing for rapid firing.
- Collision Detection: The game checks for collisions between each bullet and each enemy, removing enemies that are hit.
This code provides a more challenging and engaging gameplay experience by introducing multiple enemies and allowing for rapid bullet firing. Future enhancements can include adding levels, scores, sound effects, and more sophisticated enemy behavior to increase game complexity and enjoyment.