Create a Space Shooter Game using Python & PyGame

Last Updated on 16 April 2025

Space shooter game in python

Do you love playing the classic Space Shooter Game? What if we build our own? In this tutorial, we will create a Space Shooter Game using Python and the popular Pygame library. Whether you’re a beginner or an experienced programmer, this article will help you understand game mechanics, object-oriented programming (OOP), and most importantly how to structure a complete Python Project.

By the end of this tutorial, you will have a fully functional game with:

  • Player spaceship controls (movement & shooting)
  • Enemy waves with increasing difficulty
  • Collision detection & scoring system
  • Sound effects & visual feedback
  • A beautiful User Interface with health bars and wave tracking

So, let’s start!

Visit Also: Learn How to Create a Game in Python with PyGame

What will we create?

Showing a game window, written text "SPACE SHOOTER", "Press any key to begin", "Arrow keys to move, SPACE to shoot"
Starting Window
Space shooter animated gameplay
Animated Gameplay
space shooter non animated gameplay
Non-Animated Gameplay
Space shooter game over window, showing "GAME OVER", "Final Score: 340", "Press R to restart or ESC to quit"
Game Over

Set up Your Space

Before we start make sure the following are installed on your system:

  1. Python 3.8+
  2. PyGame library: pip install pygame
  3. A code editor (Recommended: VS Code, PyCharm, or IDLE)

Get more information about Pygame library from here.

Download the resources

Want hassle-free game creation? Download the assets for this Space Shooter game using the Download button:

Now create a folder named SpaceShooter with these files:

SpaceShooter/  
│── assets/  
│   ├── player.png  
│   ├── enemy.png
|   ├── alien.png
│   ├── bullet.png  
│   ├── background.png  
│   ├── shoot.wav  
│   ├── explosion.wav  
│   └── background.mp3  
│── main.py  

Source Code

Choose the complete project directory in your code editor and open the main.py program file. Now start writing the code step-by-step.

Import Modules

Let’s import the necessary modules and initialize pygame in our program:

import pygame
import random
import sys
import os
from pygame import mixer

# Initialize pygame
pygame.init()
mixer.init()

Game Settings

Next declare some basic settings, for example: window size, FPS, player, enemy, bullet speed, star count, colors, etc.

WIDTH, HEIGHT = 800, 600
FPS = 60
PLAYER_SPEED = 7
ENEMY_SPEED = 2
BULLET_SPEED = 10
STAR_COUNT = 100

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)

The Player

Now create the main character of our game which is our hero Spaceship. We can control left and right and shoot bullets with it.

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        try:
            # Try to load player image
            self.image = pygame.image.load(os.path.join('assets', 'player.png')).convert_alpha()
            self.image = pygame.transform.scale(self.image, (50, 40))
        except:
            # If image not found, a blank image canvas will create
            self.image = pygame.Surface((50, 40))
            self.image.fill(GREEN)

        self.rect = self.image.get_rect(center=(WIDTH//2, HEIGHT-50))
        self.speed = PLAYER_SPEED
        self.health = 100
        self.max_health = 100
        self.shoot_delay = 250  # milliseconds
        self.last_shot = pygame.time.get_ticks()
    
    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and self.rect.left > 0:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT] and self.rect.right < WIDTH:
            self.rect.x += self.speed
    
    def shoot(self):
        now = pygame.time.get_ticks()
        if now - self.last_shot > self.shoot_delay:
            self.last_shot = now
            bullet = Bullet(self.rect.centerx, self.rect.top)
            all_sprites.add(bullet)
            bullets.add(bullet)
            shoot_sound.play()

In the above code, we declared all the functionalities of the player spaceship. Here, we loaded our custom-made spaceship image named “player.png.” If the image fails to load, it will create a blank green canvas in place of the spaceship and work the same way.

The Enemies

Now, we will add our enemy spaceships that move downward at random speeds.

class Enemy(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        try:
            # Try to load enemy image
            self.image = pygame.image.load(os.path.join('assets', 'enemy.png')).convert_alpha()
            self.image = pygame.transform.scale(self.image, (40, 40))
        except:
            # If image not found, a blank image canvas will create
            self.image = pygame.Surface((40, 40))
            self.image.fill(RED)

        self.rect = self.image.get_rect(center=(x, y))
        self.speed = random.randint(1, 3)
        self.health = 30
    
    def update(self):
        self.rect.y += self.speed
        if self.rect.top > HEIGHT:
            self.kill()

In the above code, we declared all the functionalities of enemy spaceships. These enemy spaceships are destroyed by player bullets and automatically removed when off-screen.

Here, we loaded our custom-made enemy spaceship image named “enemy.png.” If the image fails to load, it will create a red square canvas in place of the enemy spaceship and work the same.

The bullets

Implement Bullet class to add the bullets to our space shooter game.

class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        try:
            # Try to load bullet image
            self.image = pygame.image.load(os.path.join('assets', 'bullet.png')).convert_alpha()
            self.image = pygame.transform.scale(self.image, (5, 15))
        except:
            # If image not found, a blank image canvas will create
            self.image = pygame.Surface((5, 15))
            self.image.fill(BLUE)
        self.rect = self.image.get_rect(center=(x, y))
        self.speed = BULLET_SPEED
    
    def update(self):
        self.rect.y -= self.speed
        if self.rect.bottom < 0:
            self.kill()

Same as the previous one – we added an image “bullet.png” here and resized it to a suitable size. Pressing the SPACE button will shoot bullets from the top of our player spaceship while playing.

Star Animation

Let’s create a star animation for our space shooter game. If you don’t place the background image in the assets folder, this animation will play in the background. By the way, this animation also gives an exciting experience for this game.

class Star:
    def __init__(self):
        self.x = random.randint(0, WIDTH)
        self.y = random.randint(0, HEIGHT)
        self.speed = random.randint(1, 3)
        self.size = random.randint(1, 3)
    
    def update(self):
        self.y += self.speed
        if self.y > HEIGHT:
            self.y = 0
            self.x = random.randint(0, WIDTH)
    
    def draw(self, screen):
        pygame.draw.circle(screen, WHITE, (self.x, self.y), self.size)

The above code creates a black screen and drops random stars of different sizes with different speeds, from the top.

Enemy Waves

This is the most interesting part of our space shooter game. Here we will create a class named EnemyWave that will manage spawning enemies in controlled waves with progressive difficulty.

class EnemyWave:
    def __init__(self):
        self.wave_number = 0
        self.enemies_in_wave = 5
        self.enemies_spawned = 0
        self.spawn_delay = 1000  # milliseconds between spawns
        self.last_spawn = 0
        self.wave_complete = False
    
    def start_new_wave(self):
        self.wave_number += 1
        # Increase enemies gradually (capped at 15 per wave)
        self.enemies_in_wave = min(5 + self.wave_number, 15)
        self.enemies_spawned = 0
        self.spawn_delay = max(300, 1000 - (self.wave_number * 50))  # Faster spawns as waves progress
        self.wave_complete = False
    
    def update(self):
        now = pygame.time.get_ticks()
        if (self.enemies_spawned < self.enemies_in_wave and 
            now - self.last_spawn > self.spawn_delay):
            self.last_spawn = now
            self.spawn_enemy()
            self.enemies_spawned += 1
            if self.enemies_spawned >= self.enemies_in_wave:
                self.wave_complete = True
    
    def spawn_enemy(self):
        # Spawn enemies in different patterns
        if self.wave_number % 3 == 0:
            # V formation
            cols = min(5, self.enemies_in_wave)
            spacing = WIDTH // (cols + 1)
            x = spacing * ((self.enemies_spawned % cols) + 1)
            y = -40 - (20 * (self.enemies_spawned // cols))
        else:
            # Random spread
            x = random.randint(50, WIDTH-50)
            y = -40
        
        enemy = Enemy(x, y)
        all_sprites.add(enemy)
        enemies.add(enemy)

The above code sends a group of enemies. In each wave, the enemy count increases, and enemies spawn get faster.

self.enemies_in_wave = min(5 + self.wave_number, 15): This line increases enemy count (capped at 15)

  • Wave 1: 6 enemies
  • Wave 2: 7 enemies
  • Wave 10+: 15 enemies (max).

self.spawn_delay = max(300, 1000 - (self.wave_number * 50)): Reduces spawn delay (enemies spawn faster each wave)

  • Wave 1: 950ms
  • Wave 2: 900ms
  • Caps at 300ms (minimum delay).

Health Bar

Let’s declare a normal function to show the health bar at the top-left corner of the gaming window.

def draw_health_bar(surface, x, y, health, max_health):
    BAR_LENGTH = 100
    BAR_HEIGHT = 10
    fill = (health / max_health) * BAR_LENGTH
    outline_rect = pygame.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
    fill_rect = pygame.Rect(x, y, fill, BAR_HEIGHT)
    pygame.draw.rect(surface, GREEN, fill_rect)
    pygame.draw.rect(surface, WHITE, outline_rect, 2)

Start Screen

Before the game starts, our game will show a welcoming window displaying the name of the game in bold text and some instructions to start and play the game. Let’s declare a function named show_start_screen.

def show_start_screen():
    screen.fill(BLACK)
    if background:
        screen.blit(background, (0, 0))
    else:
        for star in stars:
            star.draw(screen)
    
    title = big_font.render("SPACE SHOOTER", True, WHITE)
    start = font.render("Press any key to begin", True, WHITE)
    controls = font.render("Arrow keys to move, SPACE to shoot", True, WHITE)
    
    screen.blit(title, (WIDTH//2 - title.get_width()//2, HEIGHT//4))
    screen.blit(start, (WIDTH//2 - start.get_width()//2, HEIGHT//2))
    screen.blit(controls, (WIDTH//2 - controls.get_width()//2, HEIGHT*3//4))
    
    pygame.display.flip()
    
    waiting = True
    while waiting:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYUP:
                waiting = False

Game Over

As we’ve created the start screen, now we will declare a function for showing another window when the game ends. The window will display a “Game Over” message in a big red color including the Total Score and how to restart the game again.

def show_game_over_screen():
    screen.fill(BLACK)
    if background:
        screen.blit(background, (0, 0))
    else:
        for star in stars:
            star.draw(screen)
    
    game_over = big_font.render("GAME OVER", True, RED)
    final_score = font.render(f"Final Score: {score}", True, WHITE)
    restart = font.render("Press R to restart or ESC to quit", True, WHITE)
    
    screen.blit(game_over, (WIDTH//2 - game_over.get_width()//2, HEIGHT//3))
    screen.blit(final_score, (WIDTH//2 - final_score.get_width()//2, HEIGHT//2))
    screen.blit(restart, (WIDTH//2 - restart.get_width()//2, HEIGHT*2//3))
    
    pygame.display.flip()
    
    waiting = True
    while waiting:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYUP:
                if event.key == pygame.K_r:
                    return True  # Restart
                elif event.key == pygame.K_ESCAPE:
                    return False  # Quit

The Main Block

This section is the heart of the game. Here, we will tie all the components to create a real gameplay experience.

if __name__ == "__main__":
    # Create game window
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Space Shooter")
    clock = pygame.time.Clock()

    # Try to load background image
    try:
        background = pygame.image.load(os.path.join('assets', 'background.jpg')).convert()
        background = pygame.transform.scale(background, (WIDTH, HEIGHT))
    except:
        background = None

    # Create sprite groups
    all_sprites = pygame.sprite.Group()
    enemies = pygame.sprite.Group()
    bullets = pygame.sprite.Group()

    # Create player
    player = Player()
    all_sprites.add(player)

    # Create stars for background (if no background image)
    stars = [Star() for _ in range(STAR_COUNT)] if background is None else []

    # Create enemy wave controller
    wave_controller = EnemyWave()
    wave_controller.start_new_wave()

    # Load sounds
    try:
        shoot_sound = mixer.Sound(os.path.join('assets', 'shoot.wav'))
        explosion_sound = mixer.Sound(os.path.join('assets', 'explosion.wav'))
        mixer.music.load(os.path.join('assets', 'background.mp3'))
        mixer.music.set_volume(0.5)
        mixer.music.play(loops=-1)
    except:
        print("Sound files not found - continuing without sound")
        shoot_sound = mixer.Sound(buffer=bytearray(44))
        explosion_sound = mixer.Sound(buffer=bytearray(44))

    # Game variables
    score = 0
    game_over = False
    paused = False
    font = pygame.font.Font(None, 36)
    big_font = pygame.font.Font(None, 72)

    # Game loop
    running = True
    show_start_screen()

    while running:
        # Keep loop running at the right speed
        clock.tick(FPS)
        
        # Process input
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE and not game_over and not paused:
                    player.shoot()
                elif event.key == pygame.K_p and not game_over:
                    paused = not paused
                elif event.key == pygame.K_ESCAPE:
                    running = False
        
        if not game_over and not paused:
            # Update
            # Update enemy wave
            wave_controller.update()
            
            # Start new wave if current one is complete
            if wave_controller.wave_complete and len(enemies) == 0:
                wave_controller.start_new_wave()
            
            # Update all sprites
            all_sprites.update()
            
            # Update stars (if no background image)
            if background is None:
                for star in stars:
                    star.update()
            
            # Check for bullet-enemy collisions
            hits = pygame.sprite.groupcollide(enemies, bullets, True, True)
            for hit in hits:
                explosion_sound.play()
                score += 50 - hit.speed * 10  # Faster enemies give less points
            
            # Check for player-enemy collisions
            hits = pygame.sprite.spritecollide(player, enemies, True)
            for hit in hits:
                explosion_sound.play()
                player.health -= 20
                if player.health <= 0:
                    game_over = True
        
        # Draw
        if background:
            screen.blit(background, (0, 0))
        else:
            screen.fill(BLACK)
            for star in stars:
                star.draw(screen)
        
        # Draw all sprites
        all_sprites.draw(screen)
        
        # Draw UI
        draw_health_bar(screen, 5, 5, player.health, player.max_health)
        score_text = font.render(f"Score: {score}", True, WHITE)
        wave_text = font.render(f"Wave: {wave_controller.wave_number}", True, WHITE)
        screen.blit(score_text, (WIDTH - score_text.get_width() - 10, 10))
        screen.blit(wave_text, (WIDTH - wave_text.get_width() - 10, 50))
        
        if paused:
            pause_text = big_font.render("PAUSED", True, WHITE)
            screen.blit(pause_text, (WIDTH//2 - pause_text.get_width()//2, HEIGHT//2))
        
        if game_over:
            if show_game_over_screen():
                # Reset game
                game_over = False
                score = 0
                player.health = player.max_health
                
                # Clear all sprites
                for sprite in all_sprites:
                    sprite.kill()
                
                # Recreate player
                player = Player()
                all_sprites.add(player)
                
                # Reset wave controller
                wave_controller = EnemyWave()
                wave_controller.start_new_wave()
            else:
                running = False
        
        # Flip the display
        pygame.display.flip()

    pygame.quit()
    sys.exit()

In the above code, we did the following tasks:

Game Setup

  • Creates the game window (800×600 pixels)
  • Loads visual and audio assets (with fallback options if missing)
  • Prepares all game objects:
    • Player spaceship
    • Enemy wave system
    • Background (image or starfield)

Core Gameplay Loop (Runs 60 Times/Second)

  • Listens for player input (movement, shooting, pausing)
  • Manages enemies:
    • Spawns them in waves
    • Increases difficulty progressively
  • Handles collisions:
    • Bullets destroy enemies (score increases)
    • Enemies damage player (health decreases)
  • Controls game states:
    • Normal play
    • Paused mode
    • Game over screen

Visual Display

  • Draws everything in the correct order:
    • Background
    • Game objects (player, enemies, bullets)
    • UI elements (health bar, score, wave counter)
  • Shows special screens when needed (paused/game over messages)

Clean Management

  • Automatically restarts the game if players choose to continue
  • Properly closes the game when exiting

Complete Source Code

Here is the complete source code for your convenience:

import pygame
import random
import sys
import os
from pygame import mixer

# Initialize pygame
pygame.init()
mixer.init()

WIDTH, HEIGHT = 800, 600
FPS = 60
PLAYER_SPEED = 7
ENEMY_SPEED = 2
BULLET_SPEED = 10
STAR_COUNT = 100

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)

class Player(pygame.sprite.Sprite):
    def __init__(self):
        super().__init__()
        try:
            # Try to load player image
            self.image = pygame.image.load(os.path.join('assets', 'player.png')).convert_alpha()
            self.image = pygame.transform.scale(self.image, (50, 40))
        except:
            # If image not found, a blank image canvas will create
            self.image = pygame.Surface((50, 40))
            self.image.fill(GREEN)

        self.rect = self.image.get_rect(center=(WIDTH//2, HEIGHT-50))
        self.speed = PLAYER_SPEED
        self.health = 100
        self.max_health = 100
        self.shoot_delay = 250  # milliseconds
        self.last_shot = pygame.time.get_ticks()
    
    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT] and self.rect.left > 0:
            self.rect.x -= self.speed
        if keys[pygame.K_RIGHT] and self.rect.right < WIDTH:
            self.rect.x += self.speed
    
    def shoot(self):
        now = pygame.time.get_ticks()
        if now - self.last_shot > self.shoot_delay:
            self.last_shot = now
            bullet = Bullet(self.rect.centerx, self.rect.top)
            all_sprites.add(bullet)
            bullets.add(bullet)
            shoot_sound.play()

class Enemy(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        try:
            # Try to load enemy image
            self.image = pygame.image.load(os.path.join('assets', 'enemy.png')).convert_alpha()
            self.image = pygame.transform.scale(self.image, (40, 40))
        except:
            # If image not found, a blank image canvas will create
            self.image = pygame.Surface((40, 40))
            self.image.fill(RED)

        self.rect = self.image.get_rect(center=(x, y))
        self.speed = random.randint(1, 3)
        self.health = 30
    
    def update(self):
        self.rect.y += self.speed
        if self.rect.top > HEIGHT:
            self.kill()

class Bullet(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        try:
            # Try to load bullet image
            self.image = pygame.image.load(os.path.join('assets', 'bullet.png')).convert_alpha()
            self.image = pygame.transform.scale(self.image, (10, 15))
        except:
            # If image not found, a blank image canvas will create
            self.image = pygame.Surface((5, 15))
            self.image.fill(BLUE)
        self.rect = self.image.get_rect(center=(x, y))
        self.speed = BULLET_SPEED
    
    def update(self):
        self.rect.y -= self.speed
        if self.rect.bottom < 0:
            self.kill()

class Star:
    def __init__(self):
        self.x = random.randint(0, WIDTH)
        self.y = random.randint(0, HEIGHT)
        self.speed = random.randint(1, 3)
        self.size = random.randint(1, 3)
    
    def update(self):
        self.y += self.speed
        if self.y > HEIGHT:
            self.y = 0
            self.x = random.randint(0, WIDTH)
    
    def draw(self, screen):
        pygame.draw.circle(screen, WHITE, (self.x, self.y), self.size)

class EnemyWave:
    def __init__(self):
        self.wave_number = 0
        self.enemies_in_wave = 5
        self.enemies_spawned = 0
        self.spawn_delay = 1000  # milliseconds between spawns
        self.last_spawn = 0
        self.wave_complete = False
    
    def start_new_wave(self):
        self.wave_number += 1
        # Increase enemies gradually (capped at 15 per wave)
        self.enemies_in_wave = min(5 + self.wave_number, 15)
        self.enemies_spawned = 0
        self.spawn_delay = max(300, 1000 - (self.wave_number * 50))  # Faster spawns as waves progress
        self.wave_complete = False
    
    def update(self):
        now = pygame.time.get_ticks()
        if (self.enemies_spawned < self.enemies_in_wave and 
            now - self.last_spawn > self.spawn_delay):
            self.last_spawn = now
            self.spawn_enemy()
            self.enemies_spawned += 1
            if self.enemies_spawned >= self.enemies_in_wave:
                self.wave_complete = True
    
    def spawn_enemy(self):
        # Spawn enemies in different patterns
        if self.wave_number % 3 == 0:
            # V formation
            cols = min(5, self.enemies_in_wave)
            spacing = WIDTH // (cols + 1)
            x = spacing * ((self.enemies_spawned % cols) + 1)
            y = -40 - (20 * (self.enemies_spawned // cols))
        else:
            # Random spread
            x = random.randint(50, WIDTH-50)
            y = -40
        
        enemy = Enemy(x, y)
        all_sprites.add(enemy)
        enemies.add(enemy)

def draw_health_bar(surface, x, y, health, max_health):
    BAR_LENGTH = 100
    BAR_HEIGHT = 10
    fill = (health / max_health) * BAR_LENGTH
    outline_rect = pygame.Rect(x, y, BAR_LENGTH, BAR_HEIGHT)
    fill_rect = pygame.Rect(x, y, fill, BAR_HEIGHT)
    pygame.draw.rect(surface, GREEN, fill_rect)
    pygame.draw.rect(surface, WHITE, outline_rect, 2)

def show_start_screen():
    screen.fill(BLACK)
    if background:
        screen.blit(background, (0, 0))
    else:
        for star in stars:
            star.draw(screen)
    
    title = big_font.render("SPACE SHOOTER", True, WHITE)
    start = font.render("Press any key to begin", True, WHITE)
    controls = font.render("Arrow keys to move, SPACE to shoot", True, WHITE)
    
    screen.blit(title, (WIDTH//2 - title.get_width()//2, HEIGHT//4))
    screen.blit(start, (WIDTH//2 - start.get_width()//2, HEIGHT//2))
    screen.blit(controls, (WIDTH//2 - controls.get_width()//2, HEIGHT*3//4))
    
    pygame.display.flip()
    
    waiting = True
    while waiting:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYUP:
                waiting = False

def show_game_over_screen():
    screen.fill(BLACK)
    if background:
        screen.blit(background, (0, 0))
    else:
        for star in stars:
            star.draw(screen)
    
    game_over = big_font.render("GAME OVER", True, RED)
    final_score = font.render(f"Final Score: {score}", True, WHITE)
    restart = font.render("Press R to restart or ESC to quit", True, WHITE)
    
    screen.blit(game_over, (WIDTH//2 - game_over.get_width()//2, HEIGHT//3))
    screen.blit(final_score, (WIDTH//2 - final_score.get_width()//2, HEIGHT//2))
    screen.blit(restart, (WIDTH//2 - restart.get_width()//2, HEIGHT*2//3))
    
    pygame.display.flip()
    
    waiting = True
    while waiting:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
            if event.type == pygame.KEYUP:
                if event.key == pygame.K_r:
                    return True  # Restart
                elif event.key == pygame.K_ESCAPE:
                    return False  # Quit
        
if __name__ == "__main__":
    # Create game window
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    pygame.display.set_caption("Space Shooter")
    clock = pygame.time.Clock()

    # Try to load background image
    try:                                         # background.jpg
        background = pygame.image.load(os.path.join('assets', '')).convert()
        background = pygame.transform.scale(background, (WIDTH, HEIGHT))
    except:
        background = None

    # Create sprite groups
    all_sprites = pygame.sprite.Group()
    enemies = pygame.sprite.Group()
    bullets = pygame.sprite.Group()

    # Create player
    player = Player()
    all_sprites.add(player)

    # Create stars for background (if no background image)
    stars = [Star() for _ in range(STAR_COUNT)] if background is None else []

    # Create enemy wave controller
    wave_controller = EnemyWave()
    wave_controller.start_new_wave()

    # Load sounds
    try:
        shoot_sound = mixer.Sound(os.path.join('assets', 'shoot.wav'))
        explosion_sound = mixer.Sound(os.path.join('assets', 'explosion.wav'))
        mixer.music.load(os.path.join('assets', 'background.mp3'))
        mixer.music.set_volume(0.5)
        mixer.music.play(loops=-1)
    except:
        print("Sound files not found - continuing without sound")
        shoot_sound = mixer.Sound(buffer=bytearray(44))
        explosion_sound = mixer.Sound(buffer=bytearray(44))

    # Game variables
    score = 0
    game_over = False
    paused = False
    font = pygame.font.Font(None, 36)
    big_font = pygame.font.Font(None, 72)

    # Game loop
    running = True
    show_start_screen()

    while running:
        # Keep loop running at the right speed
        clock.tick(FPS)
        
        # Process input
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE and not game_over and not paused:
                    player.shoot()
                elif event.key == pygame.K_p and not game_over:
                    paused = not paused
                elif event.key == pygame.K_ESCAPE:
                    running = False
        
        if not game_over and not paused:
            # Update
            # Update enemy wave
            wave_controller.update()
            
            # Start new wave if current one is complete
            if wave_controller.wave_complete and len(enemies) == 0:
                wave_controller.start_new_wave()
            
            # Update all sprites
            all_sprites.update()
            
            # Update stars (if no background image)
            if background is None:
                for star in stars:
                    star.update()
            
            # Check for bullet-enemy collisions
            hits = pygame.sprite.groupcollide(enemies, bullets, True, True)
            for hit in hits:
                explosion_sound.play()
                score += 50 - hit.speed * 10  # Faster enemies give less points
            
            # Check for player-enemy collisions
            hits = pygame.sprite.spritecollide(player, enemies, True)
            for hit in hits:
                explosion_sound.play()
                player.health -= 20
                if player.health <= 0:
                    game_over = True
        
        # Draw
        if background:
            screen.blit(background, (0, 0))
        else:
            screen.fill(BLACK)
            for star in stars:
                star.draw(screen)
        
        # Draw all sprites
        all_sprites.draw(screen)
        
        # Draw UI
        draw_health_bar(screen, 5, 5, player.health, player.max_health)
        score_text = font.render(f"Score: {score}", True, WHITE)
        wave_text = font.render(f"Wave: {wave_controller.wave_number}", True, WHITE)
        screen.blit(score_text, (WIDTH - score_text.get_width() - 10, 10))
        screen.blit(wave_text, (WIDTH - wave_text.get_width() - 10, 50))
        
        if paused:
            pause_text = big_font.render("PAUSED", True, WHITE)
            screen.blit(pause_text, (WIDTH//2 - pause_text.get_width()//2, HEIGHT//2))
        
        if game_over:
            if show_game_over_screen():
                # Reset game
                game_over = False
                score = 0
                player.health = player.max_health
                
                # Clear all sprites
                for sprite in all_sprites:
                    sprite.kill()
                
                # Recreate player
                player = Player()
                all_sprites.add(player)
                
                # Reset wave controller
                wave_controller = EnemyWave()
                wave_controller.start_new_wave()
            else:
                running = False
        
        # Flip the display
        pygame.display.flip()

    pygame.quit()
    sys.exit()

Summary

In this tutorial, we built a fully functional computer game called Space Shooter using Python and the Pygame library. We learned how to create game logic, sprite controls, sound controls, user input, and all the core concepts needed to build a 2D computer game.

The entire article is divided into multiple segments that help beginners to learn faster. This project is best for those who want to build their Python game development portfolio.

So, why wait to? Launch your spaceship and shoot all the enemies coming your way in the space.

If you have any queries related to this Python project, let me know at contact@pyseek.com.

Happy Shooting!

Share your love
Subhankar Rakshit
Subhankar Rakshit

Hey there! I’m Subhankar Rakshit, the brains behind PySeek. I’m a Post Graduate in Computer Science. PySeek is where I channel my love for Python programming and share it with the world through engaging and informative blogs.

Articles: 215