Unit 4 - Intro to Pygame and Graphics, Pt1

4.1 Pygame

To make graphics easier to work with, we'll use “Pygame.” Pygame is a library of code other people have written, and makes it simple to:

  • Draw graphic shapes

  • Display bitmapped images

  • Animate

  • Interact with keyboard, mouse, and gamepad

  • Play sound

  • Detect when objects collide

The first code a Pygame program needs to do is load and initialize the Pygame library. Every program that uses Pygame should start with these lines:

Importing and initializing Pygame

# Import a library of functions called 'pygame'

import pygame

# Initialize the game engine

pygame.init()

Pygame is preinstalled in the PyCharm IDE, which is loaded on all of the desktops. If Pygame is not installed on your computer, you will get an error when trying to run import Pygame.

4.2 Colors

Next, we need to add variables that define our program's colors. Colors are defined in a list of three colors: red, green, and blue. Each element of the RGB triad is a number ranging from 0 to 255. Zero means there is none of the color, and 255 tells the monitor to display as much of the color as possible. The colors combine in an additive way, so if all three colors are specified, the color on the monitor appears white.

Tuples (Python data-type that serves as an immutable, i.e. non-changeable, container for other data-types) in Python are surrounded by parentheses. Individual numbers in the tuple are separated by commas. Below is an example that creates variables and sets them equal to tuples of three numbers. These tuples will be used later to specify colors.

Defining colors

# Define some colors

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

GREEN = (0, 255, 0)

RED = (255, 0, 0)

BLUE = (0, 0, 255)

Why are these variables in upper-case? A variable that doesn't change is called a constant. We don't expect the color of black to change; it is a constant. We signify that variables are constants by naming them with all upper-case letters. If we expect a color to change, like if we have sky_color that changes as the sun sets, then that variable would be in all lower-case letters.

If the five colors above aren't the colors you are looking for, you can define your own. To pick a color, find an on-line “color picker” like the one linked to in the left hand column of this site.

4.3 Creating a Game Window

So far, the programs we have created only printed text out to the screen. Those programs did not open any windows like most modern programs do. The code to open a window is not complex. Below is the required code, which creates a window sized to a width of 700 pixels, and a height of 500:

Opening and setting the window size:

size = (700, 500)

screen = pygame.display.set_mode(size)

Why set_mode? Why not open_window? The reason is that this command can actually do a lot more than open a window. It can also create games that run in a full-screen mode. This removes the start menu, title bars, and gives the game control of everything on the screen. Because this mode is slightly more complex to use, and most people prefer windowed games anyway, we'll skip a detailed discussion on full-screen games. But if you want to find out more about full-screen games, check out the documentation on pygame's display command.

Also, why size = (700, 500) and not size = 700, 500? The same reason why we put parentheses around the color definitions. Python can't normally store two numbers (a height and width) into one variable. The only way it can is if the numbers are stored as a tuple. Also you can actually say size = 700, 500 and it will default to a tuple but I prefer to use parentheses.

To set the title of the window (which is shown in the title bar) use the following line of code:

Setting the window title

pygame.display.set_caption("Some Cool Game")

4.4 User Interaction

With just the code written so far, the program would create a window and immediately hang. The user can't interact with the window, even to close it. All of this needs to be programmed. Code needs to be added so that the program waits in a loop until the user clicks “exit.”

This is the most complex part of the program, and a complete understanding of it isn't needed yet. But it is necessary to have an idea of what it does, so spend some time studying it and asking questions.

Setting up the main program loop

# Loop until the user clicks the close button.

running = True


# Used to manage how fast the screen updates

clock = pygame.time.Clock()


# -------- Main Program Loop -----------

while running:

# --- Main event loop

for event in pygame.event.get(): # User did something

if event.type == pygame.QUIT: # If user clicked close

running = False # Flag that we are done so we exit this loop


# --- Game logic should go here (how bullets are fired, how objects move, etc)


# --- Drawing code should go here


# First, clear the screen to white. Don't put other drawing

# commands above this, or they will be erased with this command.

screen.fill(WHITE)


# --- Go ahead and update the screen with what we've drawn.

pygame.display.flip()


# --- Limit to 60 frames per second

clock.tick(60)

Eventually we will add code to handle the keyboard and mouse clicks. That code will go below the comment for main event loop. Code for determining when bullets are fired and how objects move will go below the comment for game logic. We'll talk about that in later chapters. Code to draw will go in below where the screen is filled with white on line 20.

4.5 The Event Processing Loop

One of the most frustrating problems programmers have is to mess up the event processing loop. This “event processing” code handles all the keystrokes, mouse button clicks, and several other types of events. For example your loop might look like:

Sample event loop - Good

for event in pygame.event.get():

if event.type == pygame.QUIT:

print("User asked to quit.")

elif event.type == pygame.KEYDOWN:

print("User pressed a key.")

elif event.type == pygame.KEYUP:

print("User let go of a key.")

elif event.type == pygame.MOUSEBUTTONDOWN:

print("User pressed a mouse button")

The events (like pressing keys) all go together in a list. The program uses a for loop to loop through each event. Using a chain of if statements the code figures out what type of event occurred, and the code to handle that event goes in the if statement.

All the if statements should go together, in one for loop. A common mistake when doing copy and pasting of code is to not merge loops from two programs, but to have two event loops.

BAD event loop example

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

# Here is one event loop

for event in pygame.event.get():

if event.type == pygame.QUIT:

print("User asked to quit.")

elif event.type == pygame.KEYDOWN:

print("User pressed a key.")

elif event.type == pygame.KEYUP:

print("User let go of a key.")


# Here the programmer has copied another event loop into the program. This # is BAD. The events were already processed.

for event in pygame.event.get():

if event.type == pygame.QUIT:

print("User asked to quit.")

elif event.type == pygame.MOUSEBUTTONDOWN:

print("User pressed a mouse button")

The for loop on line 2 grabbed all of the user events. The for loop on line 13 won't grab any events because they were already processed in the prior loop.

Another typical problem is to start drawing, and then try to finish the event loop:

BAD Sample event loop

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

for event in pygame.event.get():

if event.type == pygame.QUIT:

print("User asked to quit.")

elif event.type == pygame.KEYDOWN:

print("User pressed a key.")


pygame.draw.rect(screen, GREEN, [50,50,100,100])


# This is code that processes events. But it is not in the

# 'for' loop that processes events. It will not act reliably.

if event.type == pygame.KEYUP:

print("User let go of a key.")

elif event.type == pygame.MOUSEBUTTONDOWN:

print("User pressed a mouse button")

This will cause the program to ignore some keyboard and mouse commands. Why? The for loop processes all the events in a list. So if there are two keys that are hit, the for loop will process both. In the example above, the if statements are not in the for loop. If there are multiple events, the if statements will only run for the last event, rather than all events.

4.6 Frame Processing

The basic logic and order for each frame of the game:

While not done:

For each event (keypress, mouse click, etc.):

Use a chain of if statements to run code to handle each event.

Run calculations to determine where objects move, what happens when objects collide, etc.

Clear the screen

Draw everything

It makes the program easier to read and understand if these steps aren't mixed together. Don't do some calculations, some drawing, some more calculations, some more drawing.

The code for drawing the image to the screen happens inside the while loop. With the clock tick set at 10, the contents of the window will be drawn 10 times per second. If it happens too fast the computer is sluggish because all of its time is spent updating the screen. If it isn't in the loop at all, the screen won't redraw properly. If the drawing is outside the loop, the screen may initially show the graphics, but the graphics won't reappear if the window is minimized, or if another window is placed in front.

4.7 Ending the Program

Right now, clicking the “close” button of a window while running this Pygame program in IDLE will still cause the program to crash. This is a hassle because it requires a lot of clicking to close a crashed program.

The problem is, even though the loop has exited, the program hasn't told the computer to close the window. By calling the command below, the program will close any open windows and exit as desired.

Proper shutdown of a Pygame program

pygame.quit()

4.8 Clearing the Screen

The following code clears whatever might be in the window with a white background. Remember that the variable WHITE was defined earlier as a list of 3 RGB values.

Clear the Screen

# Clear the screen and set the screen background

screen.fill(WHITE)

This should be done before any drawing command is issued. Clearing the screen after the program draws graphics results in the user only seeing a blank screen.

When a window is first created it has a black background. It is still important to clear the screen because there are several things that could occur to keep this window from starting out cleared. A program should not assume it has a blank canvas to draw on.

4.9 Flipping the Screen

Very important! You must flip the display after you draw. The computer will not display the graphics as you draw them because it would cause the screen to flicker. This waits to display the screen until the program has finished drawing. The command below “flips” the graphics to the screen.

Failure to include this command will mean the program just shows a blank screen. Any drawing code after this flip will not display.

Flipping the Pygame display

# Go ahead and update the screen with what we've drawn.

pygame.display.flip()

4.10 Open a Blank Window

Let's bring everything we've talked about into one full program. This code can be used as a base template for a Pygame program. It opens up a blank window and waits for the user to press the close button.

"""

Pygame base template for opening a window


"""

import pygame


# Define some colors

BLACK = (0, 0, 0)

WHITE = (255, 255, 255)

GREEN = (0, 255, 0)

RED = (255, 0, 0)


pygame.init()


# Set the width and height of the screen [width, height]

SIZE = (700, 500)

screen = pygame.display.set_mode(SIZE)


pygame.display.set_caption("My Game")


# Loop until the user clicks the close button.

done = False


# Used to manage how fast the screen updates

clock = pygame.time.Clock()


# -------- Main Program Loop -----------

while not done:

# --- Main event loop

for event in pygame.event.get():

if event.type == pygame.QUIT:

done = True


# --- Game logic should go here


# --- Screen-clearing code goes here


# Here, we clear the screen to white. Don't put other drawing commands

# above this, or they will be erased with this command.


# If you want a background image, replace this clear with blit'ing the

# background image.

screen.fill(WHITE)


# --- Drawing code should go here


# --- Go ahead and update the screen with what we've drawn.

pygame.display.flip()


# --- Limit to 60 frames per second

clock.tick(60)


# Close the window and quit.

pygame.quit()