Build Aliens Game Using Pygame

PythonPythonBeginner
Practice Now

Introduction

In this project, we will guide you through the process of creating a simple game called "Aliens" using the Pygame library. The game involves defending against alien invaders by shooting them down. We'll break down the development process into multiple steps, from setting up the project files to running the completed game.

Pygame is a popular library for creating 2D games in Python. It provides functions for handling graphics, sound, and user input, making it an excellent choice for beginners interested in game development.

👀 Preview

Aliens Game

This game is revised from the Pygame examples.

ðŸŽŊ Tasks

In this project, you will learn:

  • How to set up the initial project structure and load necessary resources like images and sounds.
  • How to define the classes for the player character and alien invaders.
  • How to create additional classes for handling explosions, player shots, alien bombs, and game score.
  • How to initialize the game, load resources, and set up the game window.
  • How to implement the main game loop, handle player input, update game entities, handle collisions, and draw the game scene.

🏆 Achievements

After completing this project, you will be able to:

  • Use the Pygame library to develop a 2D game.
  • Load and display images in Pygame.
  • Handle user input and control player movement.
  • Create and update game entities using sprite classes.
  • Handle collisions between game entities.
  • Draw the game scene and update the screen.
  • Play sound effects and music in the game.
  • Implement a main game loop to manage the game logic.

Create the Project Files

In this step, we'll set up the initial project structure and load necessary resources like images and sounds.

Create a new file called aliens.py and add the following code:

cd ~/project
touch aliens.py
sudo pip install pygame

Then, open the file in your favorite text editor and add the following code:

import os
import random
from typing import List

## import basic pygame modules
import pygame as pg

## see if we can load more than standard BMP
if not pg.image.get_extended():
    raise SystemExit("Sorry, extended image module required")


## game constants
MAX_SHOTS = 2  ## most player bullets onscreen
ALIEN_ODDS = 22  ## chances a new alien appears
BOMB_ODDS = 60  ## chances a new bomb will drop
ALIEN_RELOAD = 12  ## frames between new aliens
SCREENRECT = pg.Rect(0, 0, 640, 480)
SCORE = 0

main_dir = os.path.split(os.path.abspath(__file__))[0]


def load_image(file):
    """loads an image, prepares it for play"""
    file = os.path.join(main_dir, "data", file)
    try:
        surface = pg.image.load(file)
    except pg.error:
        raise SystemExit(f'Could not load image "{file}" {pg.get_error()}')
    return surface.convert()


def load_sound(file):
    """because pygame can be compiled without mixer."""
    if not pg.mixer:
        return None
    file = os.path.join(main_dir, "data", file)
    try:
        sound = pg.mixer.Sound(file)
        return sound
    except pg.error:
        print(f"Warning, unable to load, {file}")
    return None

In this step, we import necessary modules, define game constants, and create functions to load images and sound effects. We also set up the main project directory.

In def load_image and def load_sound, we use the os.path.join function to join the main project directory with the data directory, where we'll store our game resources. We'll create this directory in the next step.

âœĻ Check Solution and Practice

Define Player and Alien Classes

Now, we'll define the classes for our game's characters: Player and Alien. The Player class represents the player's character, while the Alien class represents the alien invaders.

## ... (code from step 1)

## Define the Player class
class Player(pg.sprite.Sprite):
    """Representing the player as a moon buggy type car."""

    speed = 10
    bounce = 24
    gun_offset = -11
    images: List[pg.Surface] = []

    def __init__(self, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect(midbottom=SCREENRECT.midbottom)
        self.reloading = 0
        self.origtop = self.rect.top
        self.facing = -1

    def move(self, direction):
        if direction:
            self.facing = direction
        self.rect.move_ip(direction * self.speed, 0)
        self.rect = self.rect.clamp(SCREENRECT)
        if direction < 0:
            self.image = self.images[0]
        elif direction > 0:
            self.image = self.images[1]
        self.rect.top = self.origtop - (self.rect.left // self.bounce % 2)

    def gunpos(self):
        pos = self.facing * self.gun_offset + self.rect.centerx
        return pos, self.rect.top

Here, we've defined the Player class. It includes attributes like speed, bounce, and gun offset. The move method handles player movement. The gunpos method returns the position of the player's gun. The Player class inherits from the pg.sprite.Sprite class, which is a base class for visible game objects.

## Define the Alien class
class Alien(pg.sprite.Sprite):
    """An alien space ship. That slowly moves down the screen."""

    speed = 13
    animcycle = 12
    images: List[pg.Surface] = []

    def __init__(self, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect()
        self.facing = random.choice((-1, 1)) * Alien.speed
        self.frame = 0
        if self.facing < 0:
            self.rect.right = SCREENRECT.right

    def update(self):
        self.rect.move_ip(self.facing, 0)
        if not SCREENRECT.contains(self.rect):
            self.facing = -self.facing
            self.rect.top = self.rect.bottom + 1
            self.rect = self.rect.clamp(SCREENRECT)
        self.frame = self.frame + 1
        self.image = self.images[self.frame // self.animcycle % 3]

The Alien class represents the alien invaders. It has attributes for speed and animation. Aliens move left and right, and their images cycle. The update method handles alien movement and animation. The Alien class inherits from the pg.sprite.Sprite class, which is a base class for visible game objects.

âœĻ Check Solution and Practice

Define Additional Game Classes

In this step, we'll define additional game-related classes: Explosion, Shot, Bomb, and Score. These classes handle explosions, player shots, alien bombs, and the game score.

## ... (code from step 2)

## Define the Explosion class
class Explosion(pg.sprite.Sprite):
    """An explosion. Hopefully the Alien and not the player!"""

    defaultlife = 12
    animcycle = 3
    images: List[pg.Surface] = []

    def __init__(self, actor, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect(center=actor.rect.center)
        self.life = self.defaultlife

    def update(self):
        """called every time around the game loop.

        Show the explosion surface for 'defaultlife'.
        Every game tick(update), we decrease the 'life'.

        Also we animate the explosion.
        """
        self.life = self.life - 1
        self.image = self.images[self.life // self.animcycle % 2]
        if self.life <= 0:
            self.kill()

## Define the Shot class
class Shot(pg.sprite.Sprite):
    """a bullet the Player sprite fires."""

    speed = -11
    images: List[pg.Surface] = []

    def __init__(self, pos, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect(midbottom=pos)

    def update(self):
        """called every time around the game loop.

        Every tick we move the shot upwards.
        """
        self.rect.move_ip(0, self.speed)
        if self.rect.top <= 0:
            self.kill()

## Define the Bomb class
class Bomb(pg.sprite.Sprite):
    """A bomb the aliens drop."""

    speed = 9
    images: List[pg.Surface] = []

    def __init__(self, alien, explosion_group, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.image = self.images[0]
        self.rect = self.image.get_rect(midbottom=alien.rect.move(0, 5).midbottom)
        self.explosion_group = explosion_group

    def update(self):
        """called every time around the game loop.

        Every frame we move the sprite 'rect' down.
        When it reaches the bottom we:

        - make an explosion.
        - remove the Bomb.
        """
        self.rect.move_ip(0, self.speed)
        if self.rect.bottom >= 470:
            Explosion(self, self.explosion_group)
            self.kill()

## Define the Score class
class Score(pg.sprite.Sprite):
    """to keep track of the score."""

    def __init__(self, *groups):
        pg.sprite.Sprite.__init__(self, *groups)
        self.font = pg.font.Font(None, 20)
        self.font.set_italic(1)
        self.color = "white"
        self.lastscore = -1
        self.update()
        self.rect = self.image.get_rect().move(10, 450)

    def update(self):
        """We only update the score in update() when it has changed."""
        if SCORE != self.lastscore:
            self.lastscore = SCORE
            msg = f"Score: {SCORE}"
            self.image = self.font.render(msg, 0, self.color)

These classes handle different aspects of the game, such as explosions, shots, bombs, and displaying the player's score.

âœĻ Check Solution and Practice

Initialize Game and Load Resources

Now, we'll continue initializing the game and loading the required resources like images and sounds.

## ... (code from step 3)

def main(winstyle=0):
    ## Initialize pygame
    if pg.get_sdl_version()[0] == 2:
        pg.mixer.pre_init(44100, 32, 2, 1024)
    pg.init()
    if pg.mixer and not pg.mixer.get_init():
        print("Warning, no sound")
        pg.mixer = None

    fullscreen = False
    ## Set the display mode
    winstyle = 0  ## |FULLSCREEN
    bestdepth = pg.display.mode_ok(SCREENRECT.size, winstyle, 32)
    screen = pg.display.set_mode(SCREENRECT.size, winstyle, bestdepth)

    ## Load images, assign to sprite classes
    ## (do this before the classes are used, after screen setup)
    img = load_image("player1.gif")
    Player.images = [img, pg.transform.flip(img, 1, 0)]
    img = load_image("explosion1.gif")
    Explosion.images = [img, pg.transform.flip(img, 1, 1)]
    Alien.images = [load_image(im) for im in ("alien1.gif", "alien2.gif", "alien3.gif")]
    Bomb.images = [load_image("bomb.gif")]
    Shot.images = [load_image("shot.gif")]

Here, we initialize Pygame, set up the game window, and load images for the player, aliens, explosions, bombs, and shots.

âœĻ Check Solution and Practice

Complete Game Setup and Main Loop

Now, we'll complete the game setup and create the main game loop, where all the game logic and interactions will take place.

## ... (code from step 4)

def main(winstyle=0):
    ## ... (previous code)

    ## decorate the game window
    icon = pg.transform.scale(Alien.images[0], (32, 32))
    pg.display.set_icon(icon)
    pg.display.set_caption("Pygame Aliens")
    pg.mouse.set_visible(0)

    ## create the background, tile the bgd image
    bgdtile = load_image("background.gif")
    background = pg.Surface(SCREENRECT.size)
    for x in range(0, SCREENRECT.width, bgdtile.get_width()):
        background.blit(bgdtile, (x, 0))
    screen.blit(background, (0, 0))
    pg.display.flip()

    ## load the sound effects
    boom_sound = load_sound("boom.wav")
    shoot_sound = load_sound("car_door.wav")
    if pg.mixer:
        music = os.path.join(main_dir, "data", "house_lo.wav")
        pg.mixer.music.load(music)
        pg.mixer.music.play(-1)

    ## Initialize Game Groups
    aliens = pg.sprite.Group()
    shots = pg.sprite.Group()
    bombs = pg.sprite.Group()
    all = pg.sprite.RenderUpdates()
    lastalien = pg.sprite.GroupSingle()

    ## Create Some Starting Values
    alienreload = ALIEN_RELOAD
    clock = pg.time.Clock()

    ## initialize our starting sprites
    global SCORE
    player = Player(all)
    Alien(
        aliens, all, lastalien
    )  ## note, this 'lives' because it goes into a sprite group
    if pg.font:
        all.add(Score(all))

    ## Run our main loop whilst the player is alive.
    while player.alive():
        ## get input
        for event in pg.event.get():
            if event.type == pg.QUIT:
                return
            if event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE:
                return
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_f:
                    if not fullscreen:
                        print("Changing to FULLSCREEN")
                        screen_backup = screen.copy()
                        screen = pg.display.set_mode(
                            SCREENRECT.size, winstyle | pg.FULLSCREEN, bestdepth
                        )
                        screen.blit(screen_backup, (0, 0))
                    else:
                        print("Changing to windowed mode")
                        screen_backup = screen.copy()
                        screen = pg.display.set_mode(
                            SCREENRECT.size, winstyle, bestdepth
                        )
                        screen.blit(screen_backup, (0, 0))
                    pg.display.flip()
                    fullscreen = not fullscreen

        keystate = pg.key.get_pressed()

        ## clear/erase the last drawn sprites
        all.clear(screen, background)

        ## update all the sprites
        all.update()

        ## handle player input
        direction = keystate[pg.K_RIGHT] - keystate[pg.K_LEFT]
        player.move(direction)
        firing = keystate[pg.K_SPACE]
        if not player.reloading and firing and len(shots) < MAX_SHOTS:
            Shot(player.gunpos(), shots, all)
            if pg.mixer and shoot_sound is not None:
                shoot_sound.play()
        player.reloading = firing

        ## Create new alien
        if alienreload:
            alienreload = alienreload - 1
        elif not int(random.random() * ALIEN_ODDS):
            Alien(aliens, all, lastalien)
            alienreload = ALIEN_RELOAD

        ## Drop bombs
        if lastalien and not int(random.random() * BOMB_ODDS):
            Bomb(lastalien.sprite, all, bombs, all)

        ## Detect collisions between aliens and players.
        for alien in pg.sprite.spritecollide(player, aliens, 1):
            if pg.mixer and boom_sound is not None:
                boom_sound.play()
            Explosion(alien, all)
            Explosion(player, all)
            SCORE = SCORE + 1
            player.kill()

        ## See if shots hit the aliens.
        for alien in pg.sprite.groupcollide(aliens, shots, 1, 1).keys():
            if pg.mixer and boom_sound is not None:
                boom_sound.play()
            Explosion(alien, all)
            SCORE = SCORE + 1

        ## See if alien bombs hit the player.
        for bomb in pg.sprite.spritecollide(player, bombs, 1):
            if pg.mixer and boom_sound is not None:
                boom_sound.play()
            Explosion(player, all)
            Explosion(bomb, all)
            player.kill()

        ## draw the scene
        dirty = all.draw(screen)
        pg.display.update(dirty)

        ## cap the framerate at 40fps. Also called 40HZ or 40 times per second.
        clock.tick(40)

    if pg.mixer:
        pg.mixer.music.fadeout(1000)
    pg.time.wait(1000)


## call the "main" function if running this script
if __name__ == "__main__":
    main()
    pg.quit()

In the main function, we initialize game groups for various entities, create starting values, and implement the main game loop. The game logic, player input handling, collisions, and drawing of the game scene are performed within this loop.

Running the Game

Now, we can run the game by executing the following command:

cd ~/project
python aliens.py
âœĻ Check Solution and Practice

Running the Game

Now, we can run the game by executing the following command:

cd ~/project
python aliens.py
Alt text

Summary

In this project, we've divided the process of building a simple "Aliens" game using Pygame into multiple steps. We've set up the project files, defined player and alien classes, created additional game-related classes, and initialized the game with resources. Finally, we implemented the main game loop to handle game logic and interactions.

In the upcoming steps, we'll continue to add functionality to the player and alien classes, handle movement and shooting, and update the game's visual elements.

Other Python Tutorials you may like