Classical Game Design

Based on the code given in https://realpython.com/pygame-a-primer/

 

Basics

 

Step 1. Open a new python file or project

 

Step 2. Import pygame and other items

We install "pygame" if it is missing.


try:
    import pygame # import the library "pygame"
except: # install "pygame" if it is missing
    import subprocess, sys
    subprocess.check_call([sys.executable, "-m", "pip", "install", "pygame"])
    import pygame

import random # we use randomness for the position and speed of enemies

# This module contains various constants used by pygame.
from pygame.locals import ( # import Arrow Keys and ESC
    K_UP,
    K_DOWN,
    K_LEFT,
    K_RIGHT,
    K_ESCAPE,
)

 

Step 3. Initiate the game screen

Define the screen width and height


SCREEN_WIDTH = 800
SCREEN_HEIGHT = 320

Initiate the pygame and define the screen variable


pygame.init() # initiate pygame
screen = pygame.display.set_mode([SCREEN_WIDTH, SCREEN_HEIGHT]) # define the main screen

 

Step 4. Game loop

FPS (Frame per second): The number of frames shown in one second. E.g., TV shows/movies -> 24fps, live broadcast -> 30 fps, video games -> 30-120 fps.

We prepare each frame and then it is shown on the screen.

We use an infinite loop, but we can adjust FPS for our game.

To exit from the loop, the user can close the window, which is an event that we can check inside the loop.


running = True
while running:
    for event in pygame.event.get():  # check all events one by one since the last frame
        if event.type == pygame.QUIT:  # if the window is closed
            running = False
    screen.fill((0, 0, 0)) # the screen background color is set to black (Red=0,Green=0,Blue=0)
    # update and show the ship and enemies
    pygame.display.flip() # show everything since the last frame
    pygame.time.Clock().tick(30)  # maximum number of frames per second <- set the FPS rate

Note that we can specify color with a tuple (Red,Green,Blue), where each entry is an integer between 0 and 255.

 

Our ship

 

Step 5: Define the ship as an Sprite object


ship = pygame.sprite.Sprite()
ship.surf = pygame.Surface((60, 20)) # create a surface
ship.surf.fill((170, 255, 0)) # color of our photonic ship
ship.rect = ship.surf.get_rect() # create a variable to access the surface as a rectangle

 

Step 6: Show the player on the screen

We use "blit()" method of "screen": Block transfer, "blit(surface, position or rectangle)", the position of rectange is used when a rectange is provided

Place this code in the game-loop, just before "pygame.display.flip()":


    screen.blit(ship.surf, ship.rect) # ship is "transferred as a block" on the screen

 

Step 7: Update the player position with arrows

We can directly access all pressed keys by "pygame.key.get_pressed()".

We define a function "updateShip(presses_keys)" class taking "pressed_keys" as the input.


def updateShip(pressed_keys):
    if pressed_keys[K_UP]:
        ship.rect.move_ip(0, -5)
    if pressed_keys[K_DOWN]:
        ship.rect.move_ip(0, 5)
    if pressed_keys[K_LEFT]:
        ship.rect.move_ip(-5, 0)
    if pressed_keys[K_RIGHT]:
        ship.rect.move_ip(5, 0)
		
    # Keep the ship within the screen
    if ship.rect.left < 0:
        ship.rect.left = 0
    if ship.rect.right > SCREEN_WIDTH:
        ship.rect.right = SCREEN_WIDTH
    if ship.rect.top <= 0:
        ship.rect.top = 0
    if ship.rect.bottom >= SCREEN_HEIGHT:
        ship.rect.bottom = SCREEN_HEIGHT

Now, let's call this function in the game-loop, before "blitting" the ship.


    pressed_keys = pygame.key.get_pressed()
    if pressed_keys[K_ESCAPE]: running = False  # let's exit the game if the ship press "ESC"
    updateShip(pressed_keys)
The ship should move within the screen amd should not be off screen.

 

Enemies

 

Step 8: Creating enemies in certain intervals

We define a certain time interval to create each enemy randomly on the right side of the screen.

For this purpose, we will create a new EVENT not in the system and call it in every 250 miliseconds.


CREATING_ENEMY_TIME_INTERVAL = 250 # milliseconds
ADDENEMY = pygame.USEREVENT + 1 # each event is associated with an integer
pygame.time.set_timer(ADDENEMY, CREATING_ENEMY_TIME_INTERVAL)

Now, we have a new event ADDENEMY, and we catch it within the event loop of the game-loop.

REPLACE

while running:
    for event in pygame.event.get():  # check all events one by one since the last frame
        if event.type == pygame.QUIT:  # if the window is closed
            running = False
WITH

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == ADDENEMY: # we catch the new event here and then we will create a new enemy
            print("The code for creating new enemy will come here") # we will replace this line in Step 11

 

Step 9. Define a function for creating a new enemy

Enemies and our photonic ship are basically the same, but their sizes and moves are defined differently.

First, we create groups for enemies and all sprites.


enemies = pygame.sprite.Group() # keep all enemies - the enemies will be added automatically
all_sprites = pygame.sprite.Group() # keep all enemies and ship(s)
all_sprites.add(ship) # add ship to the group of all sprites
Second, we define the function "createEnemy()".

def createEnemy():
    enemy = pygame.sprite.Sprite() # create a new enemy
    enemy.surf = pygame.Surface((20, 10))  # create a surface
    enemy.surf.fill((255, 0, 0))  # color of enemy
    enemy_X = random.randint(SCREEN_WIDTH + 20, SCREEN_WIDTH + 100) # position of x
    enemy_Y = random.randint(0, SCREEN_HEIGHT) # position of y
    enemy.rect = enemy.surf.get_rect(center=(enemy_X,enemy_Y)) # position of the new enemy
    enemy.speed = random.randint(5, 20) # we assign a random speed - how many pixel to move to the left in each frame
    enemies.add(enemy)  # add the new enemy
    all_sprites.add(enemy)  # add the new enemy

 

Step 10. Define a function to update all enemies


def updateEnemies():
    for enemy in enemies:
        enemy.rect.move_ip(-enemy.speed, 0) # change the horizontal position x
        if enemy.rect.right < 0:  # remove any enemy moving out side of the screen
            enemy.kill()  # a nice method of Sprite

 

Step 11. Creating, updating, and displaying enemies

REPLACE

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == ADDENEMY: # we catch the new event here and then we will create a new enemy
            print("The code for creating new enemy will come here") # we will replace this line in Step 11
WITH

while running:
    for event in pygame.event.get():  # check all events one by one since the last frame
        if event.type == pygame.QUIT:  # if the window is closed
            running = False
        elif event.type == ADDENEMY:  # we catch the new event here and then we will create a new enemy
            createEnemy()

Then, we update all enemies before updating the ship in the game-loop:


    updateEnemies()

After that, we show all sprites (ship and enemies) by using "all_sprites":

REPLACE


    screen.blit(ship.surf, ship.rect) # ship is "transferred as a block" on the screen

WITH


    for entity in all_sprites:
        screen.blit(entity.surf, entity.rect)

 

Game Over

 

Step 12. Collision detection

Thanks to Sprite and Sprite groups. We can detect if any enemy hits our photonic ship by using a single line of code.


    if pygame.sprite.spritecollideany(ship, enemies):  # check if "ship" is hit by any enemy in the "enemies" group
        ship.kill() # ship is killed
        running = False # the game-loop will be terminated

 

Step 13. Displaying "Game Over!"

Let's display "Game Over!" message before quitting the game.

We define a variable "there_is_message" initially set to "False". Whenever there is a message to be displayed, we set it to "True".

Before the game-loop:


there_is_message = False

Prepare the "Game Over!" message:

REPLACE


    if pygame.sprite.spritecollideany(ship, enemies):  # check if "ship" is hit by any enemy in the "enemies" group
        ship.kill() # ship is killed
        running = False # the game-loop will be terminated

WITH


    if pygame.sprite.spritecollideany(ship, enemies):  # check if "ship" is hit by any enemy in the "enemies" group
        ship.kill()
        running = False
        my_font = pygame.font.SysFont('Comic Sans MS', 48)  # create a font object
        # we create a text surface to blit on the screen
        text_surface = my_font.render("Game Over! ", False, (255, 0, 0), (0, 0, 0))  # message / anti-aliasing effect / text color / background color
        screen.blit( text_surface, (SCREEN_WIDTH // 3,SCREEN_HEIGHT // 3) )  # blit the text on the screen with the specified position
        there_is_message = True

This message can stay for example 2 seconds before the exiting the game.

REPLACE


    pygame.display.flip()  # show everything since the last frame
    pygame.time.Clock().tick(30)  # maximum number of frames per second <- set the FPS rate

WITH


    pygame.display.flip()  # show everything since the last frame
    if there_is_message: pygame.time.wait(2000)  # wait for 2000 milliseconds (= 2 seconds)
    pygame.time.Clock().tick(30)  # maximum number of frames per second <- set the FPS rate

 


 

Tasks

 

Use the following tasks to practice and deepen your understanding of game design and Pygame features.

 

Task 1. Adding a Score to the Game

Add a score to the game and display it in the top-right or top-left corner.

A simple method: The player earns 1 point for every frame they survive.

The score can also increase based on elapsed time.

 

Task 2. Adding a Shooting Mechanism

Our spaceship can fire bullets, and enemies hit by the bullets will be removed from the game.

Press the space key to shoot.

 

Task 3. Freezing Enemies Temporarily

When the F key is pressed, all enemies will pause for a certain duration while the player can still move their spaceship./p>

This duration can be 1 or 2 seconds (equivalent to 30 or 60 frames).

 

Task 4. Random Teleportation

Pressing the R key teleports the player's ship to a random location, ensuring it does not land on an enemy.

 

Additional Features

If you have extra time, consider adding more features from the list below or come up with your own creative ideas: