Pygame Game Development

Game Development With Pygame

Game development is exciting because you can visually see the result of the program. The visual feedback helps kids learn to program rather quickly.

We will develop a simple game with animation and sound. The motivation of this simple game is to tickle kids’ imagination and curiosity to develop their games.

Our First Pygame Game Development

The app in this example displays a soccer ball that moves randomly on the display screen. 

It also displays a bat (a paddle) controlled by the player using the keyboard’s arrow keys. 

The goal for the player is to hit the ball with the bat as the ball is randomly moving on the screen. The more times the player can hit the ball, the higher the score.

The program also checks if your bat has gone outside the screen. If the bat goes outside the screen window, the program asks if the player wants to quit (Q) the game or continue (C) the game.

This app does not keep a score about the number of hits. It also does not have a time limit for the game. A later version of the program adds scorekeeping and time limits.

PROGRAM EXAMPLE: OUR FIRST GAME: HIT THE BALL WITH A BAT

Program name: hitBall_NoScore_NoTime.py

Image name: ball.png

# This program moves the ball randomly.
# The bat is moved by the keyboard arrow keys.
# The player moves the bat to hit the ball.
import pygame
import time
import random
#Inialize pygame module 
pygame.init()
#######      SET UP     ######
# Set screen width and height.
screen_width = 800
screen_height = 600
# Define colors tuples
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
GREEN = (0, 255, 0)
MAGENTA = (255, 0, 255)
# Create a display surface width = 800 pixels, height = 600 pixels
screen = pygame.display.set_mode((screen_width, screen_height))
# Give our display window a name 'HitBall_NoScore_NoTime'
pygame.display.set_caption('HitBall_NoScore_NoTime')
# time.clock() function provides the clock for App. Specified
# in frames per second. 
clock = pygame.time.Clock()

###### Ball Image load
img = pygame.image.load('ball.png')
####### 
#######
bat_width = 30     # Set bat width and bat height
bat_height = 50
bat_move = 20  # Set bat movement in pixels each key press
ball_width = 100

###### Define text font to display text on hitting the ball
font = pygame.font.SysFont("arial black", 48)  #1
text = font.render("I hit the ball", True, BLUE) #2
# True = anti-aliasing
# get_rect method returns Rect object, a rectangle of 
# the size of the text
textRect = text.get_rect()		   #3
textRect.center = screen_width//2, screen_height * 0.2

#### Define font for "bat_out_of_screen" message asking player 
# to Continue or Quit
fontCorQ = pygame.font.SysFont(None, 30)    #4

###### Function userInput to ask player to Continue or Quit.
def userInput(cont_quit, color):            #5
    contOrQuit = fontCorQ.render(cont_quit, True, color) #5
    screen.blit(contOrQuit, [screen_width/5, 4*screen_height/5]) 
#                                           #6
######
#####       MAIN LOOP      ######
def mainLoop():
    running = True		            #7
    bat_out_of_screen = False

###### Initialize bat position
    xBat = screen_width / 2		    #8
    yBat = screen_height / 2
##### Initialize bat move step at each key press 
    batXstep = 0  			    #9
    batYstep = 0 
###### Ball initial position coordinates
    x = screen_width / 2		    #10
    y = screen_height * 0.7
######
    while running:
###### Ask player to play again or quit
        while bat_out_of_screen == True:
            screen.fill(WHITE)
            userInput("Bat is out of window! Press Q to Quit or C to Continue", RED) 
#                                           #11
            pygame.display.update()
 
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_q:	#12
                        running = False
                        bat_out_of_screen = False
                    if event.key == pygame.K_c:
                        mainLoop()
###### Keyboard Events for bat movement
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:	#13
                    batXstep = -bat_move
                    batYstep = 0
                elif event.key == pygame.K_RIGHT:
                    batXstep = bat_move
                    batYstep = 0
                elif event.key == pygame.K_UP:
                    batYstep = -bat_move
                    batXstep = 0
                elif event.key == pygame.K_DOWN:
                    batYstep = bat_move
                    batXstep = 0
            if event.type == pygame.KEYUP:   #14
                    batYstep = 0 # if the key is up, do not change bat position
                    batXstep = 0

####### Check if bat is out of display window 
        if xBat >= screen_width - 50 or xBat < 50 or yBat >= screen_height - 50 or yBat < 50:
            bat_out_of_screen = True	     #15
 
        xBat += batXstep
        yBat += batYstep
######
        screen.fill(WHITE)                   #16
###### Draw bat rectangle and update the screen
        pygame.draw.rect(screen, GREEN, [xBat, yBat, bat_width, bat_height]) #16
        pygame.display.update()

###### Generate new ball location randomly for each new frame       
        x = x + random.randint(-30, 30)       #17
        y = y + random.randint(-30, 30)
        
###### Check if ball reaches the screen window
###### Back-off the ball back by 50 pixels if the ball reaches screen boundary
        back_off = 50			      #18
        if  x > 700:
            x = x - back_off
        elif x < 0:
            x = back_off
        if y > 500:
            y = y - back_off
        elif y < 0:
            y = back_off
            
# The blit function draws the ball image on the 
# display at location (x, y)
        screen.blit(img, (x, y))	    #19
        pygame.display.update()

######
# If bat comes within 30 pixels of ball’s left 
# x-coordinate AND the bat comes within 
# ball’s left edge coordinate + ball_width, then
# test if the bat comes within 30 pixels 
# of ball’s top y-coordinate 
# AND the ball’s top edge y-coordinate + ball_width.
        if xBat > x - 30 and xBat < x + ball_width:
            if yBat > y - 30 and yBat < y + ball_width:
                screen.blit(text, textRect) 
# If Bat hits the ball                       #20
                pygame.display.update()  
# print text "I hit the ball"

        clock.tick(60)
        pygame.time.delay(50)  

    pygame.quit()
    quit()
 
mainLoop()				      #21

Detailed Line-by-line Program Explanation:

1) font = pygame.font.SysFont(“arial black”, 48)

This statement creates a new Font object. This Font object is stored in the variable named “font”. 

It selects the “arial black” font type and font size of 48. This font is used to display the message “I hit the ball” later in the program.

For more information on Pygame.font, refer to the link below:

<a href=”https://www.pygame.org/docs/ref/font.html?highlight=pygame%20font%20render“>

Define Text Font to Display Text “I Hit the Ball”

The code block below is reproduced from the main program above.

###### Define text font to display text on hitting the ball.
font = pygame.font.SysFont("arial black", 48)    #1
text = font.render("I hit the ball", True, BLUE) #2
# True = anti-aliasing
# get_rect method returns Rect object, a rectangle of the size of the text
textRect = text.get_rect()			 #3
textRect.center = screen_width//2, screen_height * 0.2

2) text = font.render(“I hit the ball”, True, BLUE) # font color=BLUE

To write the text on the Surface, the Font object’s method render() method is called to create a Surface object. The variable name of the text Surface created in our example is “text”

The parameters in the render()method are:

  • the text string to be written on the Surface (“I hit the ball”)
  • the anti-aliasing flag (True),
  • the color of the text (BLUE)

3) textRect = text.get_rect()

textRect.center = screen_width//2, screen_height*0.2

The get_rect() method returns a Rect object, which is a rectangle that provides the bounds of the text. We name this Rect object as textRect.

The Rect object has an attribute (center) that positions the text at the desired location on the screen. 

Later on, in the program (step 20), we blit Surface object “text” to Rect object (‘textRect’) to write “I hit the ball” on the screen.

Define Font for Continue (C) of Quit (Q) Message, When the Bat Is Out of Screen Boundaries

4) In step 4), we create another Font object to select the font for displaying the message “Continue or Quit”.  We name this font object “fontCorQ” (short for “font Continue or Quit”).

The code block below is reproduced from the main program above.

###### Define font for "bat_out_of_screen" message asking player to Continue or Quit
fontCorQ = pygame.font.SysFont(None, 30)	#4

###### Function to ask player to Continue or Quit
def userInput(cont_quit, color):                #5
    contOrQuit = fontCorQ.render(cont_quit, True, color) # 5
    screen.blit(contOrQuit, [screen_width/5, 4*screen_height/5]) #6	

5) Define a function named “userInput” that is called later in the program in step 11 to prompt the user to enter character “C” to continue playing or character “Q” to quit. 

The render()method is used here again as described earlier in step 2) to create a Surface object, named “contOrQuit”.

Note: We are doing this a little differently than in step 2) to show how this can be done inside a function.  The arguments of the function are “cont_quit” and “color”.  When this function is called in step 11), these arguments will be passed by the program and “cont_quit” will be replaced by the text “Bat is out of window! Press Q-Quit or C-Play Again” and argument “color” is replaced by “RED”

6) screen.blit(contOrQuit, [screen_width/5, 4*screen_height/5])

The call to blit() method copies the contents of the Surface object (“Bat is out of window! Press Q-Quit or C-Play Again”) onto another Surface object destination coordinates [screen_width/5, 4*screen_height/5]. This statement blits the text Surface object created in step 5) (object “Bat is out of window! Press Q-Quit or C-Play Again”) at the position specified by this line.

7) Define a function mainLoop()

def mainLoop():
    running = True				#7
    bat_out_of_screen = False

This is the main loop that is called later in the program on step 21). This function defines the variable “running” and assigns it the value “True”.

The statement running = True means that the game is running. As long as the variable “running” is True, the mainloop() will keep on looping. When the bat goes out of the screen, the program sets the variable “running” = False and the game finishes. 

This step also defines a variable “bat_out_of_screen” and initializes it to False. When the bat goes out of the screen, the program sets the variable “bat_out_of_screen” to True.

Define Variables for Coordinates for the Bat (xBat, yBat) and the Nimber of Pixels the Bat Moves When the Arrow Key is Pressed. Also Initialize the Ball Position

8) In this step, we define two variables, xBat and yBat.  These are the coordinates of the bat’s position. This statement initializes these two variables to the middle of the screen so that when the user starts the program, the bat appears in the middle of the screen.

    xBat = screen_width / 2		# 8
    yBat = screen_height / 2
 
    batXstep = 0  # Initialize  bat move step each key press 
    batYstep = 0                        # 9

9) Define two variables batXstep and batYstep.  batXstep is the number of pixels the bat moves in the x-direction when the left or right arrow key is pressed.  batYstep is for the y-direction movement for the UP-arrow key and the DOWN-arrow key. We initialize both of them to zero.

10) Similar to initializing the bat position, statement 10) initializes the ball position.

Statement 10) defines two variables x and y for the ball’s position coordinates (x, y). It initializes these coordinates to an initial position on the screen at the start of the program.

    x = screen_width / 2                    #10
    y = screen_height * 0.7

11) We have, so far, defined and done the initialization of the various variables that the program will use.

The next step is to define the logic of the game.

The Mainloop (Logic of the Game)

The while loop in step 11) contains the main logic of the app. The while loop checks if the bat is out of the screen. If the variable “bat_out_of_screen” is True, it calls the function “userInput” (see step 5) and prints “Bat is out of window! Press Q to Quit or C to continue” in RED color on the screen, prompting the user to press “C” continue to play or “Q” to quit the game. 

Note that as described in step 5), the program passed the arguments (“Bat is out of window! Press Q to Quit or C to continue” and “RED”) to replace the parameters “cont_quit” and “color”.

Event Checking to Check For Key Presses For Continue (C) or Quit (Q)

12) Step 12) is for the event loop that checks for any events in the event queue such as KEYDOWN. 

If there is a KEYDOWN event, the program logic checks to see if the event is due to the player pressing the ‘q’ key (quit).  In this case, we set the variable ‘running’ = False indicating the player does not want to play the game anymore and exits the while loop. If, however, the event is due to the player pressing the ‘c’ key (continue), the program determines that the player would like to continue to play the game and loops back to mainloop() at the start of the while loop and starts the loop again.

The code block below is reproduced from the main program above.

    while running:                             #11
###### Ask player to play again or quit
        while bat_out_of_screen == True:
            screen.fill(WHITE)
            userInput("Bat is out of window! Press Q to Quit or C to Continue", RED) #11
            pygame.display.update()
 
            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_q:	#12
                        running = False
                        bat_out_of_screen = False
                    if event.key == pygame.K_c:
                        mainLoop()

Checking For Key Press Events For Left, Right, Up, and Down Arrow Keys

13) In this step, we have another for event loop that first checks if a pygame.QUIT event has happened, in which case the variable “running” is set to False and the game ends gracefully.

Check For Left Arrow Key Pressed

If the pygame.QUIT event was not detected, the event loop checks for the event type = KEYDOWN. The KEYDOWN event type checks if the player has pressed a keyboard key.

If the event type = ‘KEYDOWN’ is True, the program logic checks to see if the left arrow key was pressed (event.key == pygame.K_LEFT). If the left arrow key pressed is detected, the program moves the bat in the negative x-direction by an amount equal to bat_move, which was assigned a value = 20 at the top of the program. This is done by assigning batXstep = -bat_move.

Check for Right Arrow Key Pressed

If the left arrow key-press was not detected, the program then checks if the right arrow was pressed (event.key == pygame.K_RIGHT). If this test evaluates to True, the program moves the bat in the positive x-direction by an amount equal to bat_move.  This is done by assigning batXstep = +bat_move. 

Check For UP Arrow Key Pressed

The program then checks for the up-arrow press event (event.key == pygame.K_UP) and moves the bat UP (batYstep = -bat_move) if the event evaluates to True.

Check For Down Arrow Key Pressed

Lastly, the program checks if the down-arrow key was pressed (event.type == pygame.KEYDOWN) and moves the bat down (batYstep = bat_move) if the test evaluates to True.

In summary, depending on whether the key pressed is left-arrow, right-arrow, up-arrow, or down-arrow key, the program moves the bat left, right, up, or down by the number of pixels specified in the variable “bat_move” by assigning “bat_move” value to variables “batXstep” and “batYstep”.  The program does this by assigning (- bat_move) to batXstep for the left-arrow key; assigning +bat_move for the right-arrow key, -bat_move for the up-arrow key, and +bat_move for the down-arrow key.

Note that bat_move was assigned a value of 20 early in the program, meaning the bat will move 20 pixels each time the arrow key is pressed. You can change the value of bat_move to see how the program behaves.

The code block for step 13) and 14) below is reproduced from the main program above.

###### Keyboard Events for bat movement
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:	#13
                    batXstep = -bat_move
                    batYstep = 0
                elif event.key == pygame.K_RIGHT:
                    batXstep = bat_move
                    batYstep = 0
                elif event.key == pygame.K_UP:
                    batYstep = -bat_move
                    batXstep = 0
                elif event.key == pygame.K_DOWN:
                    batYstep = bat_move
                    batXstep = 0
            if event.type == pygame.KEYUP:	#14
                    batYstep = 0 # if the key is up, do not change bat position
                    batXstep = 0

Check If the Player Has Released the Key

14) if event.type == pygame.KEYUP:

The program then checks if the player has un-pressed the arrow key. It does so by checking for the event pygame.KEYUP.

If the event type is KEYUP, that means that the player has un-pressed the key). The program sets the variables batXstep and batYstep to zero.  Therefore, the bat does not move when the player un-presses the key.

Check If the Bat Is Outside the Display Window

15) Step 15) checks if the bat’s position is close to the boundary of the screen.

If so, it sets the variable “bat_out_of_screen” = True.

####### Check if bat is out of display window 
        if xBat >= screen_width - 50 or xBat < 50 or yBat >= screen_height - 50 or yBat < 50:
            bat_out_of_screen = True	# 15

Paint the Screen White in Each Iteration of the Loop

16) Step 16) paints the screen WHITE at the beginning of every loop iteration.

This step is necessary, otherwise, the screen will display a trace of all positions of the bat and the ball from each preceding loop.

How to Draw the Bat

16a) Our bat has the shape of a rectangle. To draw the bat shape, we use the pygame module called pygame.draw.

The Pygame.draw module provides several functions for drawing various shapes onto a Surface object.  One of the drawing functions is rect, which can draw a rectangle.  In this step, we use pygame.draw.rect() function to draw a rectangle object with its top-left corner at coordinates (xBat, yBat) and width = bat_width and height = bat_height.  These variables were defined and initialized early in the program.

As the player moves the bat by pressing the arrow keys, the program updates the variables xBat and ybat. The new bat position will be drawn on the screen on the next iteration of the loop. 

Note: The first parameter in the parenthesis is the “screen” object.  The bat rectangle is drawn on the “screen” object

The second parameter is the color of the bat.  This example program picked the GREEN color. The colors were defined in the earlier part of the program. 

Also note that [xBat, yBat, bat_width, bat_height] is a tuple of four integers.

The syntax for drawing rectangles was discussed in Pygame Draw and Animation chapter, which is repeated below.

Pygame.draw.rect(surface, color, (left-top corner coordinates, right-bottom corner coordinates), width)

The code block for steps 16) and 17) below is reproduced from the main program above.

        screen.fill(WHITE)                      #16
###### Draw bat rectangle and update the screen #16a
        pygame.draw.rect(screen, GREEN, [xBat, yBat, bat_width, bat_height]) #16
        pygame.display.update()

###### Generate new ball location randomly for each new frame       
        x = x + random.randint(-30, 30) 	#17
        y = y + random.randint(-30, 30)

17) Let’s now turn our attention to the ball’s location.

Coordinates (x, y) represent the location of the ball. Step 17) uses the python module random() to move the ball randomly across the screen.

The random() module has a function randint() that can generate a random number between two parameters specified within the parenthesis. We take the current position of the ball, which is (x, y), and add a random integer to the x and y-coordinates using the expressions below:

x = x + random.randint(-30, 30) and

y = y + random.randint(-30, 30)

The random numbers generated by the randint(-30, 30) function will be between -30 and 30. Therefore, in each successive iteration of the while loop, the ball’s position will change randomly in the x and y-direction by a number between -30 and 30 pixels from its current position. Thus, the ball appears at a randomly different position on the screen at each iteration of the main loop.

Check If The Ball Has Reached the Screen Boundaries

18) Step 18) checks whether the ball has reached the boundaries of the screen. The check is done for all screen boundaries (left, right, top, or bottom).

If the ball has reached any of the boundaries, this part of the program backs off the ball by 50 pixels from the edge of the screen boundary.

        back_off = 50			# 18
        if  x > 700:
            x = x - back_off
        elif x < 0:
            x = back_off
        if y > 500:
            y = y - back_off
        elif y < 0:
            y = back_off

Blit the Ball Image to the Screen Surface

19) This code blits the ball image on the screen Surface object.  The display.update() function makes the ball visible.

Early in this chapter, we discussed the blit() method that draws the contents of one Surface object onto another Surface object by copying the contents (graphics) of memory from one memory location to another memory location.  

The arguments for the blit method are:

  • the source Surface object variable name to copy from
  • and the destination location (x, y) where to put the image.

The blit of the ball image on the screen Surface object is done by passing the positions as (x, y) coordinates, where (x, y) is a tuple.

The blit function screen.blit(img, (x, y)) copies the ball image and places it at position (x, y) on the screen Surface object.

Note that the image is not visible on the screen until the display.update() function is called.

# The blit function draws the ball image on the display at location (x, y)
        screen.blit(img, (x, y))		#19
        pygame.display.update()
######
# If bat comes within 30 pixels of ball’s left x-coordinate
# AND the bat comes within ball’s left edge coordinate + ball_width, then
# test if the bat comes within 30 pixels of ball’s top y-coordinate
# AND the ball’s top edge y-coordinate + ball_width.
        if xBat > x - 30 and xBat < x + ball_width:
            if yBat > y - 30 and yBat < y + ball_width:
                screen.blit(text, textRect) # If Bat hits the ball, #20
                pygame.display.update()         #20
# print text "I hit the ball"

        clock.tick(60)
        pygame.time.delay(50)

Collision Detection Between the Bat and Ball

20) The last step in the logic in the while loop is to check for collision between the ball and bat (that is, the bat hitting the ball).

# If bat comes within 30 pixels of ball’s left x-coordinate # AND the bat comes within ball’s left edge coordinate + ball_width, then # test if the bat comes within 30 pixels of ball’s top y-coordinate # AND the ball’s top edge y-coordinate + ball_width.

The detection of collision is done by checking the following two conditions:

The bat coming from the left side reaches within 30 pixels of the ball’s left x-coordinate is True.

AND

The bat coming from the right side reaches the ball’s right x-coordinate is True.

The logic operator AND is used to logically AND these two conditions.

If the result of the above logical AND operation is True, step 20) does a similar operation for the bat reaching the ball from the UP and DOWN direction (y-axis).

If BOTH the x-direction test AND the y-direction test evaluate True, it implies that the bat hit the ball and there was a collision between the bat and the ball.

Notice we are using ball_width to determine the ball’s right x-coordinate. 

When a collision is detected, the program prints the message “I hit the ball” using the blit(text, textRect) function. 

Recall Step 3) created the bounds of the text string “I hit the ball” using the text.rect method.

Step 20) uses the text object (which is “I hit the ball”) and textRect (which gives the bounds of the text string) to blit the text string (“I hit the ball”) to the Rect object.

Finally, it makes the text string (“I hit the ball”) visible using the display.update() function.

21) The last statement in the program to call the mainLoop().

Verified by MonsterInsights