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

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.
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.
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.
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.
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
Running the Game
Now, we can run the game by executing the following command:
cd ~/project
python aliens.py

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.



