Developing a game is very interesting to do. Today we are going to build a space invader game with python’s library called “pygame“. In our game, there will be enemy ships which we need to destroy with our ship which can shoot a bullet. If a specific amount of enemy ship passes our ship we will lose. Any enemy ship hits our ship, we will lose. We run out of ammo, we lose. So, this is what we are gonna build today with Python.
Prerequisites
- Linux (mine is ubuntu 18.04)
- Python3.7
- pip
- venv
- pygame
Environment Setup & Installation
Always remember, whenever you are becoming a crazy horse to build a new python project, just hold that horse for a few seconds. Set up a virtual environment first and thank me later. Go to your favorite directory and follow the steps
python3 -m venv my_env
cd my_env
Now we need to activate the virtual environment. And then we need to install pygame.
. bin/activate pip install pygame
Now create our project repository. We will call our project space_invader.
mkdir space_invader
Game Assets
I gave a GitHub link at the very bottom of this article. Visit there and download the assets we need in our game. We will need an image file for our ship, enemy ship, and bullet. And for some sound effects, we need explosion and firing sound.
Develop The Game
First, we need to import all the things we need.
import os import random import math import pygame
Then we need to init the game.
pygame.mixer.pre_init(frequency=44100) pygame.init() pygame.mixer.init(frequency=44100)
Now, to make the game visible we need a window, right? Let’s create one with size, an icon, and a caption.
if __name__ == "__main__": # window config window_height = 800 window_width = 800 window = pygame.display.set_mode((window_width, window_height)) pygame.display.set_caption('War Ship') game_icon = pygame.image.load(os.path.join("assets", "icon.png")) pygame.display.set_icon(game_icon)
Now, let’s load the assets and fonts we will use later on.
# assets loading and fonts BACKGROUND = pygame.image.load(os.path.join("assets", "background.jpg")) PLAYER_IMAGE = pygame.image.load(os.path.join("assets", "spaceship.png")) RED_ENEMY_IMAGE = pygame.image.load(os.path.join("assets", "red-enemy.png")) BULLET = pygame.image.load(os.path.join("assets", "bullet.png")) explosion_sound = pygame.mixer.Sound(os.path.join("assets", "explosion.WAV")) font = pygame.font.Font("freesansbold.ttf", 20) game_over_font = pygame.font.Font("freesansbold.ttf", 70)
Scoring
Initially, our score will be zero, and our life will be 100. Also, the total score to obtain in case of winning is 1000. We will set a rules for the ammo limit, which is in our case (winning score / 10) + 20. Of course, you can set your own rules here.
# scoring score = 0 life = 100 winning_score = 1000 ammo = winning_score/10 + 20
Game property configuration
Let’s set the initial position and velocity of our fighter ship.
# player player_x = 350 player_y = 730 player_velocity = 5
Also, we have the freedom to decide these values. But make sure the values make sense.
Let’s do the same for an enemy ship, but the enemy ship will be multiple.
# enemy ships enemy_image = [] enemy_x = [] enemy_y = [] enemy_velocity = [] enemy_number = 5 for i in range(0, enemy_number): enemy_image.append(RED_ENEMY_IMAGE) enemy_x.append(random.randint(70, 730)) enemy_y.append(70) enemy_velocity.append(random.uniform(0.1, 0.5))
Now, while we are configuring our bullet properties, we must make sure it’s faster than other ships.
# bullet bullet_x = 0 bullet_y = 750 bullet_velocity = 15 bullet_state = "ready"
Actions
Now, let’s build a method to show the score, life, and ammo. And also the texts for winning and game-over event.
# actions def show_score(): score_text = font.render("Score : " + str(score), True, (255, 255, 255)) ammo_text = font.render("Ammo : " + str(ammo), True, (255, 255, 255)) color = (255, 255, 255) if 100 > life >= 60: color = (14, 214, 0) if 60 > life > 40: color = (177, 184, 0) if 40 > life >= 0: color = (184, 0, 0) life_text = font.render("Life : " + str(life), True, color) window.blit(score_text, (10, 10)) window.blit(ammo_text, (400, 10)) window.blit(life_text, (600, 10))
def game_over_text(): text = font.render("GAME OVER", True, (255, 255, 255)) window.blit(text, (400, 400)) def winner_text(): text = font.render("YOY WIN", True, (184, 0, 0)) window.blit(text, (400, 400))
And also methods to display the player ship, enemy ships, and bullet in the window.
def player(x, y): window.blit(PLAYER_IMAGE, (x, y)) def enemy(x, y, nth_enemy): window.blit(enemy_image[nth_enemy], (x, y)) def trigger_bullet(x, y): global bullet_state bullet_state = "fire" window.blit(BULLET, (x + 10, y + 10))
We also need a method to check collision. But to do that we need a little bit of help from mathematics. To find the distance between two points by using the distance formula, which is an application of the Pythagorean theorem. We can rewrite the Pythagorean theorem as d=√((x_2-x_1)²+(y_2-y_1)²) to find the distance between any two points
Also, we will play a sound when this action occurs.
def collision(ex, ey, bx, by): distance = math.sqrt(math.pow((ex - bx), 2) + math.pow((ey - by), 2)) if distance < 30: explosion_sound.play() return True return False
The game loop
Now lets, build the game loop and check events in it for now. in our game loop we need to load a background image for a better view. And if we quit the window the game will get out from the while loop.
# Game Loop run = True while run: window.fill((0, 0, 0)) window.blit(BACKGROUND, (0, 0)) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False
Now let’s catch the left and right keypress events and change the coordinate of our player ship.
keys = pygame.key.get_pressed() if keys[pygame.K_LEFT] and player_x > player_velocity: player_x -= player_velocity if keys[pygame.K_RIGHT] and player_x < (window_width - 64 - player_velocity): player_x += player_velocity
To fire bullet we will press the space key, so we need to catch the space button press event. We will also play a sound and keep track of our ammo. If we ran out of ammo then we will simple get lost from the game loop by breaking the while loop.
if keys[pygame.K_SPACE]: if bullet_state is "ready" and ammo > 0: print("FIRE IN THE HOLE!") ammo -= 1 bullet_sound = pygame.mixer.Sound( os.path.join("assets", "GUNSHOT.WAV")) bullet_sound.play() bullet_x = player_x trigger_bullet(bullet_x, bullet_y) if ammo == 0: game_over_text() run = False
Now, let’s change and check the bullet state. If the bullet state is “fire” then we will keep increasing the Y-AXIS position of the bullet.
if bullet_y <= 0: bullet_y = 750 bullet_state = "ready" if bullet_state == "fire": trigger_bullet(bullet_x, bullet_y) bullet_y -= bullet_velocity
Now, let’s work on the enemy ship. We need to keep them moving through the Y-Axis. And if enemy ship passes our ship we will lose 10 life out of total 100 life. And a collision between bullet and enemy ship happen, we need to remove the enemy ship from our window. But if any collision between our ship and enemy ship occurs then the game will be over by breaking the loop.
for j in range(0, enemy_number): if enemy_y[j] >= 750: life -= 10 if life == 0: game_over_text() run = False enemy_y[j] = 10 enemy_y[j] += enemy_velocity[j] if collision(enemy_x[j], enemy_y[j], bullet_x, bullet_y): print("ENEMY DESTROYED") score += 10 if score >= winning_score: winner_text() run = False bullet_y = 750 bullet_state = "ready" enemy_x[j] = random.randint(70, 730) enemy_y[j] = 10 if collision(enemy_x[j], enemy_y[j], player_x, player_y): game_over_text() run = False enemy(enemy_x[j], enemy_y[j], j)
Now at the very end display the player and show the score.
player(player_x, player_y) show_score() pygame.display.update()
And outside of the loop, we will just quit the window.
pygame.quit()
Source Code
If you followed all the steps then your code will look like this. And of course, you can improve the code by dividing them in separate files. We will see the OOP version of this game later.
import os import random import math import pygame pygame.mixer.pre_init(frequency=44100) pygame.init() pygame.mixer.init(frequency=44100) if __name__ == "__main__": # window config window_height = 800 window_width = 800 window = pygame.display.set_mode((window_width, window_height)) pygame.display.set_caption('War Ship') game_icon = pygame.image.load(os.path.join("assets", "icon.png")) pygame.display.set_icon(game_icon) # assets loading and fonts BACKGROUND = pygame.image.load(os.path.join("assets", "background.jpg")) PLAYER_IMAGE = pygame.image.load(os.path.join("assets", "spaceship.png")) RED_ENEMY_IMAGE = pygame.image.load(os.path.join("assets", "red-enemy.png")) BULLET = pygame.image.load(os.path.join("assets", "bullet.png")) explosion_sound = pygame.mixer.Sound(os.path.join("assets", "explosion.WAV")) font = pygame.font.Font("freesansbold.ttf", 20) game_over_font = pygame.font.Font("freesansbold.ttf", 70) # scoring score = 0 life = 100 winning_score = 1000 ammo = winning_score/10 + 20 # player player_x = 350 player_y = 730 player_velocity = 5 # enemy ships enemy_image = [] enemy_x = [] enemy_y = [] enemy_velocity = [] enemy_number = 5 for i in range(0, enemy_number): enemy_image.append(RED_ENEMY_IMAGE) enemy_x.append(random.randint(70, 730)) enemy_y.append(70) enemy_velocity.append(random.uniform(0.1, 0.5)) # bullet bullet_x = 0 bullet_y = 750 bullet_velocity = 15 bullet_state = "ready" # actions def show_score(): score_text = font.render("Score : " + str(score), True, (255, 255, 255)) ammo_text = font.render("Ammo : " + str(ammo), True, (255, 255, 255)) color = (255, 255, 255) if 100 > life >= 60: color = (14, 214, 0) if 60 > life > 40: color = (177, 184, 0) if 40 > life >= 0: color = (184, 0, 0) life_text = font.render("Life : " + str(life), True, color) window.blit(score_text, (10, 10)) window.blit(ammo_text, (400, 10)) window.blit(life_text, (600, 10)) def player(x, y): window.blit(PLAYER_IMAGE, (x, y)) def enemy(x, y, nth_enemy): window.blit(enemy_image[nth_enemy], (x, y)) def trigger_bullet(x, y): global bullet_state bullet_state = "fire" window.blit(BULLET, (x + 10, y + 10)) def collision(ex, ey, bx, by): distance = math.sqrt(math.pow((ex - bx), 2) + math.pow((ey - by), 2)) if distance < 30: explosion_sound.play() return True return False def game_over_text(): text = font.render("GAME OVER", True, (255, 255, 255)) window.blit(text, (400, 400)) def winner_text(): text = font.render("YOY WIN", True, (184, 0, 0)) window.blit(text, (400, 400)) # Game Loop run = True while run: window.fill((0, 0, 0)) window.blit(BACKGROUND, (0, 0)) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False keys = pygame.key.get_pressed() if keys[pygame.K_LEFT] and player_x > player_velocity: player_x -= player_velocity if keys[pygame.K_RIGHT] and player_x < (window_width - 64 - player_velocity): player_x += player_velocity if keys[pygame.K_SPACE]: if bullet_state is "ready" and ammo > 0: print("FIRE IN THE HOLE!") ammo -= 1 bullet_sound = pygame.mixer.Sound( os.path.join("assets", "GUNSHOT.WAV")) bullet_sound.play() bullet_x = player_x trigger_bullet(bullet_x, bullet_y) if ammo == 0: game_over_text() run = False if bullet_y <= 0: bullet_y = 750 bullet_state = "ready" if bullet_state == "fire": trigger_bullet(bullet_x, bullet_y) bullet_y -= bullet_velocity for j in range(0, enemy_number): if enemy_y[j] >= 750: life -= 10 if life == 0: game_over_text() run = False enemy_y[j] = 10 enemy_y[j] += enemy_velocity[j] if collision(enemy_x[j], enemy_y[j], bullet_x, bullet_y): print("ENEMY DESTROYED") score += 10 if score >= winning_score: winner_text() run = False bullet_y = 750 bullet_state = "ready" enemy_x[j] = random.randint(70, 730) enemy_y[j] = 10 if collision(enemy_x[j], enemy_y[j], player_x, player_y): game_over_text() run = False enemy(enemy_x[j], enemy_y[j], j) player(player_x, player_y) show_score() pygame.display.update() pygame.quit()
My Github Repository: https://github.com/zim0101/python_space_invader
Feel free to clone the repo and enjoy the game.