In this tutorial, we’ll be taking a look at a simple game called pyBlasteroids, which is a 2D space shooter game. It’s worth noting that this game is a port of an activity from the book “Head First C” which was originally written in C, but in this tutorial, we’ll be going over the Python version of the code. The game requires Python and Pygame installed on your machine to run.

## Setting up the environment

The code starts by importing several modules and libraries, including Pygame, the operating system module, the platform module, the random module, and the sys module. It then sets some global variables, such as the screen size, the size of the ship and asteroids, and various other game settings like the number of asteroids and the rate at which new asteroids are added to the game.

```import pygame, os, platform, random, sys
from pygame.locals import *
from math import *
from pygame import Vector2

if platform.system() == 'Windows':
os.environ['SDL_VIDEODRIVER'] = 'windib'

# Colors: R G B values, how much red, green and blue
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
CARNATION = (255, 166, 201)

# Initialize our game
SCR_SIZE = SCR_W, SCR_H = 640, 480
SHIP_W, SHIP_H = 17, 21
ROCK_W, ROCK_H = 51, 41
NUM_OF_ROCKS = 3
NUM_OF_ROCK_SPLIT = 5
SPEED_INCREMENT = 6
SPEED_DECREMENT = 13
MAX_SPEED = 200.0

# Points that create the shape of our asteroid
ROCK_SHAPE = [(5,0), (0,15), (0,30),
(15,40), (20,30), (30,40),
(45,30), (45,25), (25,20),
(45,10), (36,0), (25,5)]

```

## Defining game functions

The code also defines several functions that are used throughout the game, such as the `exit_game()` function, which quits the game and exits the program, the `press_any_key()` function, which waits for the player to press a key, the `draw_text()` function, which renders text on the screen, the `get_blast()` function, which creates the ship’s blaster, the `new_rock()` function, which creates a new asteroid, the `new_ship()` function, which creates a new spaceship and the `center_rotate()` function, which rotate the image to the center.

```def exit_game():
pygame.quit()
sys.exit()

def press_any_key():
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit_game()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
exit_game()
return

def draw_text(text, font, surface, pos, color=GREEN):
text_surf = font.render(text, 1, color)
surface.blit(text_surf, pos)

def get_blast():
blast = {'speed': MAX_SPEED * 3,
'pos': Vector2(200, 150),
'rot': 0.0,
'surf': pygame.Surface((3, 11))}
pygame.draw.aaline(blast['surf'], WHITE, [1, 0], [1, 10])
return blast

def new_rock():
# Data structure for our asteroid
rock = {'speed': random.randint(20, 80),
'pos': Vector2(random.randint(0, SCR_W - ROCK_W),
random.randint(0, SCR_H - ROCK_H)),
'rot': 0.0,
'rot_speed': random.randint(90, 180) / 1.0,
'rot_direction': random.choice([-1, 1]),
'surf': pygame.Surface((ROCK_W, ROCK_H)),
'rect': pygame.Rect(0, 0, ROCK_W, ROCK_H),
'hits': 0}

# Draw our asteroid using geometric primitives
pygame.draw.polygon(rock['surf'], WHITE, ROCK_SHAPE, 1)
return rock

def new_ship():
# Data structure for our spaceship, Vectorize
ship = {'speed': 0,
'pos': Vector2(200, 150),
'rot': 0.0,
'rot_speed': 360.0,
'surf': pygame.Surface((SHIP_W, SHIP_H)),
'new': True}

# Draws spaceship using geometric primitives
pygame.draw.aaline(ship['surf'], GREEN, [0, 20], [8, 0])
pygame.draw.aaline(ship['surf'], GREEN, [8, 0], [16, 20])
pygame.draw.aaline(ship['surf'], GREEN, [2, 15], [7, 15])
pygame.draw.aaline(ship['surf'], GREEN, [14, 15], [9, 15])
return ship

def center_rotate(image, w, h):
"""Returns the drawing position and where it's heading"""
heading_x = sin(image['rot'] * pi / 180.0) # Convert degrees to rads then calculate x component
heading_y = cos(image['rot'] * pi / 180.0) # Convert degrees to rads then calculate y component
return Vector2(image['pos'].x - w / 2, image['pos'].y - h / 2), Vector2(heading_x, heading_y)
```

## Running the game

The main function initializes Pygame, creates the game window, and sets the caption for the window. It also loads various game assets such as sounds and fonts, creates the ship and the asteroids and displays the title screen. After the player presses any key, the game begins.

```## Main loop. Initializes PyGame and our game ##
def main():
pygame.init()
screen = pygame.display.set_mode(SCR_SIZE)
pygame.display.set_caption("pyBlasteroids")

title_font = pygame.font.Font('assets/SFPixelate.ttf', 72)
text_font = pygame.font.Font('assets/SFPixelate.ttf', 36)
score_font = pygame.font.Font(None, 72)

welcome_sound = pygame.mixer.Sound('assets/sfx-01.wav')
blaster_sound = pygame.mixer.Sound('assets/blast.wav')
asteroid_hit_sound = pygame.mixer.Sound('assets/hit.wav')
player_collision_sound = pygame.mixer.Sound('assets/explode.wav')

screen_rect = screen.get_rect()
ship = new_ship()
blasts = []
score = 0
num_of_lives = 3
total_time_passed_secs = 0
running = True

# Displays game title screen and waits for user
title_rect = title_font.render('pyBlasteroids', 1, GREEN).get_rect()
start_rect = text_font.render('Press any key to start', 1, GREEN).get_rect()
draw_text('pyBlasteroids', title_font, screen,
(screen_rect.centerx - title_rect.width / 2,
screen_rect.centery - (title_rect.height + start_rect.height + 10) / 2))
draw_text('Press any key to start', text_font, screen,
(screen_rect.centerx - start_rect.width / 2,
screen_rect.centery + 10))
global top_score
if top_score > 0:
draw_text('Top score: %s' % str(top_score), text_font, screen, (20, 10), CARNATION)
pygame.display.update()
welcome_sound.play()
press_any_key()
clock = pygame.time.Clock()

rocks = []
for i in range(NUM_OF_ROCKS):
rock = new_rock()
rocks.append(rock)
```

The game loop is the heart of the game and it continually updates the game state, handles user input, and renders the game objects on the screen. The loop begins by handling any events, such as user input or the player closing the window. The player’s ship can be controlled by the arrow keys, with the left and right arrow keys controlling the rotation of the ship and the up and down arrow keys controlling the speed of the ship. The player can also shoot using the spacebar, which creates a blaster and adds it to the list of active blasts.

```## Game loop ##
while running:
for event in pygame.event.get():
if event.type == QUIT:
exit_game()

pressed_keys = pygame.key.get_pressed()
rot_direction = 0.0
mov_direction = -1

if pressed_keys[K_ESCAPE]:
exit_game()
if pressed_keys[K_LEFT]:
rot_direction = +1.0
elif pressed_keys[K_RIGHT]:
rot_direction = -1.0
if pressed_keys[K_UP]:
ship['speed'] += SPEED_INCREMENT
if ship['speed'] > MAX_SPEED: ship['speed'] = MAX_SPEED
elif pressed_keys[K_DOWN]:
ship['speed'] -= SPEED_DECREMENT
if ship['speed'] < 0: ship['speed'] = 0
if pressed_keys[K_SPACE]:
new_blast = get_blast()
new_blast['pos'] = Vector2(ship['pos'].x, ship['pos'].y)
new_blast['rot'] = ship['rot']
blasts.append(new_blast)
blaster_sound.play()

screen.fill(BLACK)
time_passed = clock.tick(30)
time_passed_secs = time_passed / 1000.0
total_time_passed_secs += time_passed_secs

rock = new_rock()
rock['pos'] = Vector2(0 - ROCK_W, random.randint(0, SCR_H - ROCK_H))
rocks.append(rock)

for blast in blasts[:]:
# The first time around the blast is not yet rotated
rotated_blast_surf = pygame.transform.rotate(blast['surf'], blast['rot'])

# The rotated surface may not return the same dimensions as the original
bw, bh = rotated_blast_surf.get_size()

# We adjust our x and y so that the center of the blast is in the original
# x and y position
blast_draw_pos, b_heading = center_rotate(blast, bw, bh)

# New position time based
blast['pos'] += b_heading * time_passed_secs * blast['speed']

# Removes blasts that has left the edges of the screen from list.
if blast['pos'].y < 0 and blast in blasts:
blasts.remove(blast)
if blast['pos'].y + bh > SCR_H and blast in blasts:
blasts.remove(blast)
if blast['pos'].x < 0 and blast in blasts:
blasts.remove(blast)
if blast['pos'].x + bw > SCR_W and blast in blasts:
blasts.remove(blast)

# Checks if enemy was hit by blast and split it in two
# Remove from list if it has already been hit a couple of times
blast_rect = pygame.Rect(blast_draw_pos.x, blast_draw_pos.y, bw, bh)
for rock in rocks[:]:
if blast_rect.colliderect(rock['rect']) and blast in blasts:

rotated_rock_surf = pygame.transform.rotate(rock['surf'], rock['rot'])
rw, rh = rotated_rock_surf.get_size()

rock_half = new_rock()
rock_half['pos'] = Vector2(rock['pos'].x, rock['pos'].y)

rock['pos'].y -= rh + (rh / 2)
rock_half['pos'].y += rh + (rh / 2)

rock['surf'] = pygame.transform.scale(rotated_rock_surf, (rw - (rw / 4), rh - (rh / 4)))
rock_half['surf'] = rock['surf']

rock['hits'] += 1

if rock['hits'] >= NUM_OF_ROCK_SPLIT:
rocks.remove(rock)
else:
rock_half['hits'] = rock['hits']
rocks.append(rock_half)

blasts.remove(blast)
score += 100
asteroid_hit_sound.play()

screen.blit(rotated_blast_surf, blast_draw_pos)
```

The game loop also updates the position and rotation of the ship and the asteroids, detects collisions between the ship, the asteroids, and the blasts, and increments the score. The game also keeps track of the player’s lives, and if the player loses all their lives, the game is over.

```## Updates the asteroids, the same thing as the blasts ##
for rock in rocks[:]:
rotated_rock_surf = pygame.transform.rotate(rock['surf'], rock['rot'])
rw, rh = rotated_rock_surf.get_size()
rock['rot'] += rock['rot_direction'] * rock['rot_speed'] * time_passed_secs
rock_draw_pos, r_heading = center_rotate(rock, rw, rh)
rock['pos'].x += 1.0 * time_passed_secs * rock['speed']
rock['rect'] = pygame.Rect(rock_draw_pos.x, rock_draw_pos.y, rw, rh)

if rock['pos'].y < 0:
rocks.remove(rock)
if rock['pos'].y + rh > SCR_H:
rocks.remove(rock)
if rock['pos'].x > SCR_W + ROCK_W:
rock['pos'].x = -ROCK_W

screen.blit(rotated_rock_surf, rock_draw_pos)

## Updates the player, the same thing as the blasts ##
if total_time_passed_secs >= 5:
ship['new'] = False
total_time_passed_secs = 0

rotated_ship_surf = pygame.transform.rotate(ship['surf'], ship['rot'])
sw, sh = rotated_ship_surf.get_size()

# This is for the spaceship's rotation--time based
ship['rot'] += rot_direction * ship['rot_speed'] * time_passed_secs

ship_draw_pos, s_heading = center_rotate(ship, sw, sh)
ship['pos'] += s_heading * time_passed_secs * ship['speed']

# Stops player from leaving the edges of the screen
if ship['pos'].y < sh:
ship['pos'].y = sh
if ship['pos'].y + sh > SCR_H:
ship['pos'].y = SCR_H - sh
if ship['pos'].x < sw:
ship['pos'].x = sw
if ship['pos'].x + sw > SCR_W:
ship['pos'].x = SCR_W - sw

# Checks if player has collided with an enemy; it doesn't check for the first 5 seconds
ship_rect = pygame.Rect(ship_draw_pos.x, ship_draw_pos.y, sw, sh)
for rock in rocks[:]:
if ship_rect.colliderect(rock['rect']) and not ship['new']:
total_time_passed_secs = 0
num_of_lives -= 1
ship = new_ship()
player_collision_sound.play()

# Blinks player to indicate time allowance
if ship['new']:
if total_time_passed_secs > 0.5 and total_time_passed_secs < 1:
rotated_ship_surf.fill(BLACK)
if total_time_passed_secs > 1.5 and total_time_passed_secs < 2:
rotated_ship_surf.fill(BLACK)
if total_time_passed_secs > 2.5 and total_time_passed_secs < 3:
rotated_ship_surf.fill(BLACK)
if total_time_passed_secs > 3.5 and total_time_passed_secs < 4:
rotated_ship_surf.fill(BLACK)
if total_time_passed_secs > 4.5 and total_time_passed_secs < 5:
rotated_ship_surf.fill(BLACK)

screen.blit(rotated_ship_surf, ship_draw_pos)

## Displays the score on the left side of the screen ##
draw_text(str(score), score_font, screen, (20, 5), CARNATION)

## Displays the number of lives left ##
x = 28
for i in range(num_of_lives):
screen.blit(ship['surf'], (x, 60))
x += SHIP_W + 10

```

The game also includes sound effects for various actions such as shooting and hitting an asteroid, and it also keeps track of the player’s high score.

```# Displays "Game Over!" if no more lives left
if num_of_lives <= 0:
running = False
screen_copy = screen.copy()
screen_rect = screen.get_rect()

if score > top_score:
top_score = score
top_score_rect = text_font.render('New Top Score: %s!' % str(top_score), 1, GREEN).get_rect()
draw_text('New Top Score: %s!' % str(top_score), text_font, screen,
(screen_rect.centerx - top_score_rect.width / 2, 100))
pygame.display.update()
pygame.time.wait(4000)

game_over_rect = title_font.render('Game Over!', 1, GREEN).get_rect()
draw_text('Game Over!', title_font, screen_copy,
(screen_rect.centerx - game_over_rect.width / 2,
screen_rect.centery - game_over_rect.height / 2))
screen.blit(screen_copy, (0, 0))
pygame.display.update()
pygame.time.wait(4000)

pygame.display.update()

main()

top_score = 0
if __name__ == '__main__':
main()
```

## Conclusion

Overall, Blasteroids is a simple and fun game that demonstrates the basic concepts of game development using Python and Pygame. It is a great starting point for anyone who wants to learn game development or who wants to learn how to port a game from C to Python.

Source Code:

Python:

Pygame:

References:

Sound Effects Generator: