Python Project: Pang Arcade
Python's pygame is a free library for game development.
We will learn the functions of pygame by creating a simple version of Pang which is a retro arcade game. Pang is a game which a gamer moves a character from side to side, avoiding balls, and firing weapons to eliminate balls.
Pygame Installation
First, open a terminal window in VS Code and run pip install pygame
. If we can see a statement “Successfully installed pygame,” it means that the installation has been done well. It is very simple and easy.
To be more clearly, let’s create a test file to make sure that the installation is completed well. Import pygame, and press the triangle button in the upper right to run the file. If it runs well without any errors, we can go to the next step.
For more information about pygame, please refer to the two links below.
https://github.com/pygame/pygame
Basic Frame
To use pygame’s modules, initialization and shutdown are essential. They are pygame.init()
and pygame.quit()
, and all codes for the game will be written between these two.
To set the size of the game window, create variables for the width and height, and then put them in set_mode
method of display module. Declare the object created to a variable so that it can continue to be used. Also, we can set the title of the game using the set_caption
method.
Create a time object with time module and clock()
method to set FPS (frames per second), the frame rate, and then set the value using the tick
method. The higher the value, the faster the game running and feels more natural.
We will prepare a while statement for game running and for statement for event handling. update()
method of display module is required within the while statement because the screen must be drawn continuously during the game running.
import pygame
# initialization
pygame.init()
# screen setting
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Simple Pang Arcade")
# create time object
clock = pygame.time.Clock()
# game running
running = True
while running:
# set FPS
clock.tick(30)
# event handling
for event in pygame.event.get():
# close the window by clicking the X button of the game window
if event.type == pygame.QUIT:
# stop the game
running = False
# update screen continuously
pygame.display.update()
# shut down
pygame.quit()
We have completed the basic frame for game development, and we will be able to reuse it for other games as well.
Game Setup
Before writing code, let’s set the conditions for the game.
- A character is located at the bottom of the screen and can only be moved from side to side
- Press the space key to fire a weapon
- A big ball appears and bounces
- When the weapon touches the ball, the ball is divided into two smaller sizes and the smallest size ball disappears
- Game over when all balls are eliminated (mission complete)
- Game over when the ball hits the character (mission failed)
- If the time limit of 99 seconds is exceeded, the game ends (mission failed)
A game requires images for visible operation. At this stage, we will just use simple shapes to develop the game.
- Background: 640 * 480
- Stage: 640 * 50
- Character: 33 * 60
- Weapon: 20 * 430
- Balls: 160 * 160, 80 * 80, 40 * 40, 20 * 20
Game Development
After placing the images in the images folder, import os module to find a path to use them. This module is a part of the standard library, so we can just get it using import
keyword. Use dirname
method, which is to obtain the directory name, using the special variable __file__
to get the current path. And then, use join
method to get the path of the folder containing the images.
import os
# get the path of the current file
current_path = os.path.dirname(__file__)
# get the path of the images folder
image_path = os.path.join(current_path, "images")
Create the background, stage, and character on the screen using the path we got. To place the character on the stage, we will use get_rect()
method so that we can obtain the height of the stage. get_rect()
method returns a Rect object from an image and allows us to take its width and height and use them. size attribute of the Rect object is Tuple, and the first element stores the width and the second element stores the height. It is also used to place the character in the middle of the width of the screen.
# create background
background = pygame.image.load(os.path.join(image_path, "background.png"))
# create stage
stage = pygame.image.load(os.path.join(image_path, "stage.png"))
stage_size = stage.get_rect().size
stage_height = stage_size[1]
# create character
character = pygame.image.load(os.path.join(image_path, "character.png"))
character_size = character.get_rect().size
character_width = character_size[0]
character_height = character_size[1]
character_x_pos = (screen_width / 2) - (character_width / 2)
character_y_pos = screen_height - character_height - stage_height
Use blit
method to display the created background, stage, and character on the screen. These codes will be placed above update()
method in the while statement. Set the background to (0, 0) which is the upper left position because it is the same size as the screen. Set the stage at the bottom of the screen and the character at the center just above the stage.
# display images
screen.blit(background, (0, 0))
screen.blit(stage, (0, screen_height - stage_height))
screen.blit(character, (character_x_pos, character_y_pos))
Let’s make the character move. The character will move on the x-axis which means from side to side and have the speed appropriately. The character’s movement will take place in the events of the left and right keys. When the left key is pressed, it will move from the position on the x-axis to the left by the speed set, so use subtraction to adjust the position. For the right key, addition will be used to reposition the character to the right. Yes, the speed can actually be thought of as the unit of movement.
We also need to consider and handle the situations when the event that was pressing the key is released and when the character reaches both left and right ends.
# direction of moving
character_to_x = 0
# speed of character
character_speed = 5
# event handling
for event in pygame.event.get():
# close the window by clicking the X button of the game window
if event.type == pygame.QUIT:
# stop the game
running = False
# pressing key event
if event.type == pygame.KEYDOWN:
# move character to the left using the left key
if event.key == pygame.K_LEFT:
character_to_x -= character_speed
# move character to the left using the left key
elif event.key == pygame.K_RIGHT:
character_to_x += character_speed
# releasing key event
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
character_to_x = 0
# reposition character
character_x_pos += character_to_x
# when character reaches left end
if character_x_pos < 0:
character_x_pos = 0
# when character reaches right end
elif character_x_pos > screen_width - character_width:
character_x_pos = screen_width - character_width
We will create a weapon and write a code to handle the event of the weapon. A weapon is fired when the space key is pressed, and the x-axis and y-axis position of the weapon are stored in List so that a number of fired weapons can be managed separately. Weapons automatically move up and then disappear when they reach the end. When displaying the weapons to the screen, the code of the weapons should be written before the stage’s one because the layers of images follow the order of codes.
# create weapon
weapon = pygame.image.load(os.path.join(image_path, "weapon.png"))
weapon_size = weapon.get_rect().size
weapon_width = weapon_size[0]
# weapon list
weapons = []
# speed of weapon
weapon_speed = 10
# pressing key event
if event.type == pygame.KEYDOWN:
# move character to the left using the left key
if event.key == pygame.K_LEFT:
character_to_x -= character_speed
# move character to the left using the left key
elif event.key == pygame.K_RIGHT:
character_to_x += character_speed
# fire weapon from the center of character
elif event.key == pygame.K_SPACE:
weapon_x_pos = character_x_pos + (character_width / 2) - (weapon_width / 2)
weapon_y_pos = character_y_pos
weapons.append([weapon_x_pos, weapon_y_pos])
# move up weapons adjusting y position
weapons = [ [w[0], w[1] - weapon_speed] for w in weapons]
# save only weapons whose y position is greater than 0
# weapon that reaches the top (y <= 0) is removed
weapons = [ [w[0], w[1]] for w in weapons if w[1] > 0]
# display images
screen.blit(background, (0, 0))
for weapon_x_pos, weapon_y_pos in weapons:
screen.blit(weapon, (weapon_x_pos, weapon_y_pos))
screen.blit(stage, (0, screen_height - stage_height))
screen.blit(character, (character_x_pos, character_y_pos))
Now, let’s create the balls and make them move. Put four images of the balls in List, and put the speeds that will be applied differently depending on the size in another List. In the last List to manage the balls that will appear on the screen, we will put the biggest ball initially. To adjust the position of the balls, enumerate
function is used that allows the index and value to be tracked together. The balls will bounce around on the screen and change directions when they reach the ends. Place the display code of the balls directly below the weapon’s one.
# create balls
ball_images = [
pygame.image.load(os.path.join(image_path, "ball01.png")),
pygame.image.load(os.path.join(image_path, "ball02.png")),
pygame.image.load(os.path.join(image_path, "ball03.png")),
pygame.image.load(os.path.join(image_path, "ball04.png"))]
# speeds of balls
ball_speed_y = [-18, -15, -12, -9]
# balls on the screen
balls = []
# the initial biggest ball
balls.append({
"pos_x" : 50,
"pos_y" : 50,
"img_idx" : 0,
"to_x" : 3,
"to_y" : -6,
"init_spd_y" : ball_speed_y[0]})
# position of balls
for ball_idx, ball_val in enumerate(balls):
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
ball_size = ball_images[ball_img_idx].get_rect().size
ball_width = ball_size[0]
ball_height = ball_size[1]
# turn around when reaching the left and right ends
if ball_pos_x < 0 or ball_pos_x > screen_width - ball_width:
ball_val["to_x"] = ball_val["to_x"] * -1
# turn up when reaching the stage
if ball_pos_y >= screen_height - stage_height - ball_height:
ball_val["to_y"] = ball_val["init_spd_y"]
# increase speed and move down ball
else:
ball_val["to_y"] += 0.5
ball_val["pos_x"] += ball_val["to_x"]
ball_val["pos_y"] += ball_val["to_y"]
for inx, val in enumerate(balls):
ball_pos_x = val["pos_x"]
ball_pos_y = val["pos_y"]
ball_img_idx = val["img_idx"]
screen.blit(ball_images[ball_img_idx], (ball_pos_x, ball_pos_y))
We are going to the step that we handle events when the character and the ball touch and the ball and the weapon touch. First, update the position of the character and balls, and make the game end when they touch. We also update the position of the weapons and make the ball be divided when the weapon touches it. The smallest size ball will be removed, otherwise the two divided balls will bounce off in different directions. The weapon that touches the ball will be removed. The determination of touching is made using colliderect
method.
# variables for weapons and balls to be removed
weapon_to_remove = -1
ball_to_remove = -1
# update character position
character_rect = character.get_rect()
character_rect.left = character_x_pos
character_rect.top = character_y_pos
for ball_idx, ball_val in enumerate(balls):
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
# update ball position
ball_rect = ball_images[ball_img_idx].get_rect()
ball_rect.left = ball_pos_x
ball_rect.top = ball_pos_y
# when character and ball touch
if character_rect.colliderect(ball_rect):
running = False
break
for weapon_idx, weapon_val in enumerate(weapons):
weapon_pos_x = weapon_val[0]
weapon_pos_y = weapon_val[1]
# update weapon position
weapon_rect = weapon.get_rect()
weapon_rect.left = weapon_pos_x
weapon_rect.top = weapon_pos_y
# when weapon and ball touch
if weapon_rect.colliderect(ball_rect):
weapon_to_remove = weapon_idx
ball_to_remove = ball_idx
# not the smallest ball
if ball_img_idx < 3:
ball_width = ball_rect.size[0]
ball_height = ball_rect.size[1]
small_ball_rect = ball_images[ball_img_idx + 1].get_rect()
small_ball_width = small_ball_rect.size[0]
small_ball_height = small_ball_rect.size[1]
# new ball bouncing to the left
balls.append({
"pos_x" : ball_pos_x + (ball_width / 2) - (small_ball_width / 2),
"pos_y" : ball_pos_y + (ball_height / 2) - (small_ball_height / 2),
"img_idx" : ball_img_idx + 1,
"to_x" : -3,
"to_y" : -6,
"init_spd_y" : ball_speed_y[ball_img_idx + 1]})
# new ball bouncing to the right
balls.append({
"pos_x" : ball_pos_x + (ball_width / 2) - (small_ball_width / 2),
"pos_y" : ball_pos_y + (ball_height / 2) - (small_ball_height / 2),
"img_idx" : ball_img_idx + 1,
"to_x" : 3,
"to_y" : -6,
"init_spd_y" : ball_speed_y[ball_img_idx + 1]})
break
else:
continue
break
# remove touched ball
if ball_to_remove > -1:
del balls[ball_to_remove]
ball_to_remove = -1
# remove touched weapon
if weapon_to_remove > -1:
del weapons[weapon_to_remove]
weapon_to_remove = -1
# when all balls removed
if len(balls) == 0:
running = False
We are almost there. We will set up and insert texts indicating the time and success or failure of the game. Calculate the time for that, and also give a delay of 2 seconds before closing the game window for a natural end.
Now, our code has been completed well.
import os
import pygame
# initialization
pygame.init()
# screen setting
screen_width = 640
screen_height = 480
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Simple Pang Arcade")
# create time object
clock = pygame.time.Clock()
# get the path of the current file
current_path = os.path.dirname(__file__)
# get the path of the images folder
image_path = os.path.join(current_path, "images")
# create background
background = pygame.image.load(os.path.join(image_path, "background.png"))
# create stage
stage = pygame.image.load(os.path.join(image_path, "stage.png"))
stage_size = stage.get_rect().size
stage_height = stage_size[1]
# create character
character = pygame.image.load(os.path.join(image_path, "character.png"))
character_size = character.get_rect().size
character_width = character_size[0]
character_height = character_size[1]
character_x_pos = (screen_width / 2) - (character_width / 2)
character_y_pos = screen_height - character_height - stage_height
# direction of moving
character_to_x = 0
# speed of character
character_speed = 5
# create weapon
weapon = pygame.image.load(os.path.join(image_path, "weapon.png"))
weapon_size = weapon.get_rect().size
weapon_width = weapon_size[0]
# weapon list
weapons = []
# speed of weapon
weapon_speed = 10
# create balls
ball_images = [
pygame.image.load(os.path.join(image_path, "ball01.png")),
pygame.image.load(os.path.join(image_path, "ball02.png")),
pygame.image.load(os.path.join(image_path, "ball03.png")),
pygame.image.load(os.path.join(image_path, "ball04.png"))]
# speeds of balls
ball_speed_y = [-18, -15, -12, -9]
# balls on the screen
balls = []
# the initial biggest ball
balls.append({
"pos_x" : 50,
"pos_y" : 50,
"img_idx" : 0,
"to_x" : 3,
"to_y" : -6,
"init_spd_y" : ball_speed_y[0]})
# variables for weapons and balls to be removed
weapon_to_remove = -1
ball_to_remove = -1
# declare the font
game_font = pygame.font.Font(None, 40)
# set times
total_time = 100
start_ticks = pygame.time.get_ticks()
# set message
game_result = "Game Over"
# game running
running = True
while running:
# set FPS
clock.tick(30)
# event handling
for event in pygame.event.get():
# close the window by clicking the X button of the game window
if event.type == pygame.QUIT:
# stop the game
running = False
# pressing key event
if event.type == pygame.KEYDOWN:
# move character to the left using the left key
if event.key == pygame.K_LEFT:
character_to_x -= character_speed
# move character to the left using the left key
elif event.key == pygame.K_RIGHT:
character_to_x += character_speed
# fire weapon from the center of character
elif event.key == pygame.K_SPACE:
weapon_x_pos = character_x_pos + (character_width / 2) - (weapon_width / 2)
weapon_y_pos = character_y_pos
weapons.append([weapon_x_pos, weapon_y_pos])
# releasing key event
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
character_to_x = 0
# reposition character
character_x_pos += character_to_x
# when character reaches left end
if character_x_pos < 0:
character_x_pos = 0
# when character reaches right end
elif character_x_pos > screen_width - character_width:
character_x_pos = screen_width - character_width
# move up weapons adjusting y position
weapons = [ [w[0], w[1] - weapon_speed] for w in weapons]
# save only weapons whose y position is greater than 0
# weapon that reaches the top (y <= 0) is removed
weapons = [ [w[0], w[1]] for w in weapons if w[1] > 0]
# position of balls
for ball_idx, ball_val in enumerate(balls):
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
ball_size = ball_images[ball_img_idx].get_rect().size
ball_width = ball_size[0]
ball_height = ball_size[1]
# turn around when reaching the left and right ends
if ball_pos_x < 0 or ball_pos_x > screen_width - ball_width:
ball_val["to_x"] = ball_val["to_x"] * -1
# turn up when reaching the stage
if ball_pos_y >= screen_height - stage_height - ball_height:
ball_val["to_y"] = ball_val["init_spd_y"]
# increase speed and move down ball
else:
ball_val["to_y"] += 0.5
ball_val["pos_x"] += ball_val["to_x"]
ball_val["pos_y"] += ball_val["to_y"]
# update character position
character_rect = character.get_rect()
character_rect.left = character_x_pos
character_rect.top = character_y_pos
for ball_idx, ball_val in enumerate(balls):
ball_pos_x = ball_val["pos_x"]
ball_pos_y = ball_val["pos_y"]
ball_img_idx = ball_val["img_idx"]
# update ball position
ball_rect = ball_images[ball_img_idx].get_rect()
ball_rect.left = ball_pos_x
ball_rect.top = ball_pos_y
# when character and ball touch
if character_rect.colliderect(ball_rect):
running = False
break
for weapon_idx, weapon_val in enumerate(weapons):
weapon_pos_x = weapon_val[0]
weapon_pos_y = weapon_val[1]
# update weapon position
weapon_rect = weapon.get_rect()
weapon_rect.left = weapon_pos_x
weapon_rect.top = weapon_pos_y
# when weapon and ball touch
if weapon_rect.colliderect(ball_rect):
weapon_to_remove = weapon_idx
ball_to_remove = ball_idx
# not the smallest ball
if ball_img_idx < 3:
ball_width = ball_rect.size[0]
ball_height = ball_rect.size[1]
small_ball_rect = ball_images[ball_img_idx + 1].get_rect()
small_ball_width = small_ball_rect.size[0]
small_ball_height = small_ball_rect.size[1]
# new ball bouncing to the left
balls.append({
"pos_x" : ball_pos_x + (ball_width / 2) - (small_ball_width / 2),
"pos_y" : ball_pos_y + (ball_height / 2) - (small_ball_height / 2),
"img_idx" : ball_img_idx + 1,
"to_x" : -3,
"to_y" : -6,
"init_spd_y" : ball_speed_y[ball_img_idx + 1]})
# new ball bouncing to the right
balls.append({
"pos_x" : ball_pos_x + (ball_width / 2) - (small_ball_width / 2),
"pos_y" : ball_pos_y + (ball_height / 2) - (small_ball_height / 2),
"img_idx" : ball_img_idx + 1,
"to_x" : 3,
"to_y" : -6,
"init_spd_y" : ball_speed_y[ball_img_idx + 1]})
break
else:
continue
break
# remove touched ball
if ball_to_remove > -1:
del balls[ball_to_remove]
ball_to_remove = -1
# remove touched weapon
if weapon_to_remove > -1:
del weapons[weapon_to_remove]
weapon_to_remove = -1
# when all balls removed
if len(balls) == 0:
game_result = "Mission Complete"
running = False
# display images
screen.blit(background, (0, 0))
for weapon_x_pos, weapon_y_pos in weapons:
screen.blit(weapon, (weapon_x_pos, weapon_y_pos))
for inx, val in enumerate(balls):
ball_pos_x = val["pos_x"]
ball_pos_y = val["pos_y"]
ball_img_idx = val["img_idx"]
screen.blit(ball_images[ball_img_idx], (ball_pos_x, ball_pos_y))
screen.blit(stage, (0, screen_height - stage_height))
screen.blit(character, (character_x_pos, character_y_pos))
# caculate and display elapsed time
elapsed_time = (pygame.time.get_ticks() - start_ticks) / 1000
timer = game_font.render("Time : {}".format(int(total_time - elapsed_time)), True, (255, 255, 255))
screen.blit(timer, (10, 10))
# when time over
if total_time - elapsed_time <= 0:
game_result = "Time Over"
running = False
# update screen continuously
pygame.display.update()
# display game result message
msg = game_font.render(game_result, True, (255, 255, 0))
mag_rect = msg.get_rect(center = (int(screen_width / 2), int(screen_height / 2)))
screen.blit(msg, mag_rect)
pygame.display.update()
# delay 2 seconds before closing the window
pygame.time.delay(2000)
# shut down
pygame.quit()
We can make the game look better by applying advanced images.
The actual demo of the game can be found in the video below. If the game code doesn’t work, please leave a comment.