Program Arcade Games
With Python And Pygame

Chapter 10: Controllers and Graphics

How do we get objects to move using the keyboard, mouse, or a game controller?

10.1 Introduction

fig.controller

So far, we've shown how to animate items on the screen, but not how to interact with them. How do we use a mouse, keyboard, or game controller to control the action on-screen? Thankfully this is pretty easy.

To begin with, it is necessary to have an object that can be moved around the screen. The best way to do this is to have a function that takes in an x and y coordinate, then draws an object at that location. So back to Chapter 9! Let's take a look at how to write a function to draw an object.

Video: Draw with a function

All the Pygame draw functions require a screen parameter to let Pygame know which window to draw on. We will need to pass this in to any function we create to draw an object on the screen.

The function also needs to know where to draw the object on the screen. The function needs an x and y. We pass the location to the function as a parameter. Here is example code that defines a function that will draw a snowman when called:

def draw_snowman(screen, x, y):
    # Draw a circle for the head
    pygame.draw.ellipse(screen, WHITE, [35 + x, y, 25, 25])
    # Draw the middle snowman circle
    pygame.draw.ellipse(screen, WHITE, [23 + x, 20 + y, 50, 50])
    # Draw the bottom snowman circle
    pygame.draw.ellipse(screen, WHITE, [x, 65 + y, 100, 100])

Then, in the main program loop, multiple snowmen can be drawn, as seen in Figure 10.1.

fig.snowmen
Figure 10.1: Snowmen drawn by a function
# Snowman in upper left
draw_snowman(screen, 10, 10)

# Snowman in upper right
draw_snowman(screen, 300, 10)

# Snowman in lower left
draw_snowman(screen, 10, 300)
Video: Convert existing code to a function

A full working example is available on-line at:
ProgramArcadeGames.com/python_examples/f.php?file=functions_and_graphics.py

Chances are, from a prior lab you already have a code that draws something cool. But how do you get that into a function? Let's take an example of code that draws a stick figure:

# Head
pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)

# Legs
pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)
pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)

# Body
pygame.draw.line(screen, RED, [100,100], [100,90], 2)

# Arms
pygame.draw.line(screen, RED, [100,90], [104,100], 2)
pygame.draw.line(screen, RED, [100,90], [96,100], 2)
fig.stick_figure
Figure 10.2: Stick Figure

This code can easily be put in a function by adding a function def and indenting the code under it. We'll need to bring in all the data that the function needs to draw the stick figure. We need the screen variable to tell the function what window to draw on, and an x and y coordinate for where to draw the stick figure.

But we can't define the function in the middle of our program loop! The code should be removed from the main part of the program. Function declarations should go at the start of the program. We need to move that code to the top. See Figure 10.3 to help visualize.

def draw_stick_figure(screen,x,y):
    # Head
    pygame.draw.ellipse(screen, BLACK, [96,83,10,10], 0)

    # Legs
    pygame.draw.line(screen, BLACK, [100,100], [105,110], 2)
    pygame.draw.line(screen, BLACK, [100,100], [95,110], 2)

    # Body
    pygame.draw.line(screen, RED, [100,100], [100,90], 2)

    # Arms
    pygame.draw.line(screen, RED, [100,90], [104,100], 2)
    pygame.draw.line(screen, RED, [100,90], [96,100], 2)
fig.making_a_function
Figure 10.3: Making a Function and Putting it in the Right Place

Right now, this code takes in an x and y coordinate. Unfortunately it doesn't actually do anything with them. You can specify any coordinate you want, the stick figure always draws in in the same exact spot. Not very useful. The next code example literally adds in the x and y coordinate to the code we had before.

def draw_stick_figure(screen, x, y):
    # Head
    pygame.draw.ellipse(screen, BLACK,[96+x,83+y,10,10],0)

    # Legs
    pygame.draw.line(screen, BLACK, [100+x,100+y], [105+x,110+y], 2)
    pygame.draw.line(screen, BLACK, [100+x,100+y], [95+x,110+y], 2)

    # Body
    pygame.draw.line(screen, RED, [100+x,100+y], [100+x,90+y], 2)

    # Arms
    pygame.draw.line(screen, RED, [100+x,90+y], [104+x,100+y], 2)
    pygame.draw.line(screen, RED, [100+x,90+y], [96+x,100+y], 2)

But the problem is that the figure is already drawn a certain distance from the origin. It assumes an origin of (0, 0) and draws the stick figure down and over about 100 pixels. See Figure 10.4 and how the stick figure is not drawn at the (0, 0) coordinate passed in.

fig.stick_figure_2
Figure 10.4: Stick Figure

By adding x and y in the function, we shift the origin of the stick figure by that amount. For example, if we call:

draw_stick_figure(screen, 50, 50)

The code does not put a stick figure at (50, 50). It shifts the origin down and over 50 pixels. Since our stick figure was already being drawn at about (100, 100), with the origin shift the figure is about (150, 150). How do we fix this so that the figure is actually drawn where the function call requests?

fig.code_example
Figure 10.5: Finding the Smallest X and Y Values

Find the smallest x value, and the smallest y value as shown in Figure 10.5. Then subtract that value from each x and y in the function. Don't mess with the height and width values. Here's an example where we subtracted the smallest x and y values:

def draw_stick_figure(screen, x, y):
    # Head
    pygame.draw.ellipse(screen, BLACK,[96-95+x,83-83+y,10,10],0)

    # Legs
    pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [105-95+x,110-83+y], 2)
    pygame.draw.line(screen, BLACK, [100-95+x,100-83+y], [95-95+x,110-83+y], 2)

    # Body
    pygame.draw.line(screen, RED, [100-95+x,100-83+y], [100-95+x,90-83+y], 2)

    # Arms
    pygame.draw.line(screen, RED, [100-95+x,90-83+y], [104-95+x,100-83+y], 2)
    pygame.draw.line(screen, RED, [100-95+x,90-83+y], [96-95+x,100-83+y], 2)

Or, to make a program simpler, do the subtraction yourself:

def draw_stick_figure(screen, x, y):
    # Head
    pygame.draw.ellipse(screen, BLACK, [1+x,y,10,10], 0)

    # Legs
    pygame.draw.line(screen, BLACK ,[5+x,17+y], [10+x,27+y], 2)
    pygame.draw.line(screen, BLACK, [5+x,17+y], [x,27+y], 2)

    # Body
    pygame.draw.line(screen, RED, [5+x,17+y], [5+x,7+y], 2)

    # Arms
    pygame.draw.line(screen, RED, [5+x,7+y], [9+x,17+y], 2)
    pygame.draw.line(screen, RED, [5+x,7+y], [1+x,17+y], 2)

10.2 Mouse

Video: Move with the mouse

Great, now we know how to write a function to draw an object at specific coordinates. How do we get those coordinates? The easiest to work with is the mouse. It takes one line of code to get the coordinates:

pos = pygame.mouse.get_pos()

The trick is that coordinates are returned as a list, or more specifically a non-modifiable tuple. Both the x and y values are stored in the same variable. So if we do a print(pos) we get what is shown in Figure 10.6.

fig.coordinates
Figure 10.6: Coordinates

The variable pos is a tuple of two numbers. The x coordinate is in position 0 of array and the y coordinate is in the position 1. These can easily be fetched out and passed to the function that draws the item:

# Game logic
pos = pygame.mouse.get_pos()
x = pos[0]
y = pos[1]

# Drawing section
draw_stick_figure(screen, x, y)

Getting the mouse should go in the “game logic” part of the main program loop. The function call should go in the “drawing” part of the main program loop.

The only problem with this is that the mouse pointer draws right on top of the stick figure, making it hard to see, as shown in Figure 10.7.

fig.mouse_and_figure
Figure 10.7: Stick Figure With Mouse Cursor On Top

The mouse can be hidden by using the following code right before the main program loop:

# Hide the mouse cursor
pygame.mouse.set_visible(False)

A full working example can be found here:
ProgramArcadeGames.com/python_examples/f.php?file=move_mouse.py

10.3 Keyboard

Video: Move with the keyboard

Controlling with the keyboard is a bit more complex. We can't just grab the x and y from the mouse. The keyboard doesn't give us an x and y. We need to:

It seems complex, but this is just like the bouncing rectangle we did before, with the exception that the speed is controlled by the keyboard.

To start with, set the location and speed before the main loop starts:

# Speed in pixels per frame
x_speed = 0
y_speed = 0

# Current position
x_coord = 10
y_coord = 10

Inside the main while loop of the program, we need to add some items to our event processing loop. In addition to looking for a pygame.QUIT event, the program needs to look for keyboard events. An event is generated each time the user presses a key.

A pygame.KEYDOWN event is generated when a key is pressed down. A pygame.KEYUP event is generated when the user lets up on a key. When the user presses a key, the speed vector is set to 3 or -3 pixels per frame. When the user lets up on a key the speed vector is reset back to zero. Finally, the coordinates of the object are adjusted by the vector, and then the object is drawn. See the code example below:

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        done = True

    # User pressed down on a key
    elif event.type == pygame.KEYDOWN:
        # Figure out if it was an arrow key. If so
        # adjust speed.
        if event.key == pygame.K_LEFT:
            x_speed = -3
        elif event.key == pygame.K_RIGHT:
            x_speed = 3
        elif event.key == pygame.K_UP:
            y_speed = -3
        elif event.key == pygame.K_DOWN:
            y_speed = 3

    # User let up on a key
    elif event.type == pygame.KEYUP:
        # If it is an arrow key, reset vector back to zero
        if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
            x_speed = 0
        elif event.key == pygame.K_UP or event.key == pygame.K_DOWN:
            y_speed = 0

# Move the object according to the speed vector.
x_coord += x_speed
y_coord += y_speed

# Draw the stick figure
draw_stick_figure(screen, x_coord, y_coord)

For a full example see:
ProgramArcadeGames.com/python_examples/f.php?file=move_keyboard.py

Note that this example does not prevent the character from moving off the edge of the screen. To do this, in the game logic section, a set of if statements would be needed to check the x_coord and y_coord values. If they are outside the boundaries of the screen, then reset the coordinates to the edge. The exact code for this is left as an exercise for the reader.

The table below shows a full list of key-codes that can be used in Pygame:

Pygame CodeASCIICommon Name
K_BACKSPACE\bbackspace
K_RETURN\rreturn
K_TAB\ttab
K_ESCAPE^[escape
K_SPACE space
K_COMMA,comma sign
K_MINUS-minus
K_PERIOD.period slash
K_SLASH/forward
K_000
K_111
K_222
K_333
K_444
K_555
K_666
K_777
K_888
K_999
K_SEMICOLON;semicolon sign
K_EQUALS=equals sign
K_LEFTBRACKET[left
K_RIGHTBRACKET]right
K_BACKSLASH\backslash bracket
K_BACKQUOTE`grave
K_aaa
K_bbb
K_ccc
K_ddd
K_eee
K_fff
K_ggg
K_hhh
K_iii
K_jjj
K_kkk
K_lll
K_mmm
K_nnn
K_ooo
K_ppp
K_qqq
K_rrr
K_sss
K_ttt
K_uuu
K_vvv
K_www
K_xxx
K_yyy
K_zzz
K_DELETEdelete
K_KP0keypad0
K_KP1keypad1
K_KP2keypad2
K_KP3keypad3
K_KP4keypad4
K_KP5keypad5
K_KP6keypad6
K_KP7keypad7
K_KP8keypad8
K_KP9keypad9 period
K_KP_PERIOD.keypad divide
K_KP_DIVIDE/keypad multiply
K_KP_MULTIPLY*keypad minus
K_KP_MINUS-keypad plus
K_KP_PLUS+keypad enter
K_KP_ENTER\rkeypad equals
K_KP_EQUALS=keypad
K_UPuparrow
K_DOWNdownarrow
K_RIGHTrightarrow
K_LEFTleftarrow
K_INSERTinsert
K_HOMEhome
K_ENDend
K_PAGEUPpageup
K_PAGEDOWNpagedown
K_F1F1
K_F2F2
K_F3F3
K_F4F4
K_F5F5
K_F6F6
K_F7F7
K_F8F8
K_F9F9
K_F10F10
K_F11F11
K_F12F12
K_NUMLOCKnumlock
K_CAPSLOCKcapslock
K_RSHIFTrightshift
K_LSHIFTleftshift
K_RCTRLrightctrl
K_LCTRLleftctrl
K_RALTrightalt
K_LALTleftalt

10.4 Game Controller

Game controllers require a different set of code, but the idea is still simple.

To begin, check to see if the computer has a joystick, and initialize it before use. This should only be done once. Do it ahead of the main program loop:

# Current position
x_coord = 10
y_coord = 10

# Count the joysticks the computer has
joystick_count = pygame.joystick.get_count()
if joystick_count == 0:
    # No joysticks!
    print("Error, I didn't find any joysticks.")
else:
    # Use joystick #0 and initialize it
    my_joystick = pygame.joystick.Joystick(0)
    my_joystick.init()

A joystick will return two floating point values. If the joystick is perfectly centered it will return (0, 0). If the joystick is fully up and to the left it will return (-1, -1). If the joystick is down and to the right it will return (1, 1). If the joystick is somewhere in between, values are scaled accordingly. See the controller images starting at Figure 10.8 to get an idea how it works.

fig.c
Figure 10.8: Center (0,0)
fig.ul
Figure 10.9: Up Left (-1,-1)
fig.u
Figure 10.10: Up (0,-1)
fig.ur
Figure 10.11: Up Right (1,-1)
fig.r
Figure 10.12: Right (1,0)
fig.dr
Figure 10.13: Down Right (1,1)
fig.d
Figure 10.14: Down (0,1)
fig.dl
Figure 10.15: Down Left (-1,1)
fig.controller_left
Figure 10.16: Left (-1,0)

Inside the main program loop, the values of the joystick returns may be multiplied according to how far an object should move. In the case of the code below, moving the joystick fully in a direction will move it 10 pixels per frame because the joystick values are multiplied by 10.

# This goes in the main program loop!

# As long as there is a joystick
if joystick_count != 0:

    # This gets the position of the axis on the game controller
    # It returns a number between -1.0 and +1.0
    horiz_axis_pos = my_joystick.get_axis(0)
    vert_axis_pos = my_joystick.get_axis(1)

    # Move x according to the axis. We multiply by 10 to speed up the movement.
    # Convert to an integer because we can't draw at pixel 3.5, just 3 or 4.
    x_coord = x_coord + int(horiz_axis_pos * 10)
    y_coord = y_coord + int(vert_axis_pos * 10)

# Clear the screen
screen.fill(WHITE)

# Draw the item at the proper coordinates
draw_stick_figure(screen, x_coord, y_coord)

For a full example, see
ProgramArcadeGames.com/python_examples/f.php?file=move_game_controller.py.

Controllers have a lot of joysticks, buttons, and even “hat” switches. Below is an example program and screenshot that prints everything to the screen showing what each game controller is doing. Take heed that game controllers must be plugged in before this program starts, or the program can't detect them.

fig.joystick_calls
Figure 10.17: Joystick Calls Program
"""
Sample Python/Pygame Programs
Simpson College Computer Science
http://programarcadegames.com/
http://simpson.edu/computer-science/

Show everything we can pull off the joystick
"""
import pygame

# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)


class TextPrint(object):
    """
    This is a simple class that will help us print to the screen
    It has nothing to do with the joysticks, just outputting the
    information.
    """
    def __init__(self):
        """ Constructor """
        self.reset()
        self.x_pos = 10
        self.y_pos = 10
        self.font = pygame.font.Font(None, 20)

    def print(self, my_screen, text_string):
        """ Draw text onto the screen. """
        text_bitmap = self.font.render(text_string, True, BLACK)
        my_screen.blit(text_bitmap, [self.x_pos, self.y_pos])
        self.y_pos += self.line_height

    def reset(self):
        """ Reset text to the top of the screen. """
        self.x_pos = 10
        self.y_pos = 10
        self.line_height = 15

    def indent(self):
        """ Indent the next line of text """
        self.x_pos += 10

    def unindent(self):
        """ Unindent the next line of text """
        self.x_pos -= 10


pygame.init()

# Set the width and height of the screen [width,height]
size = [500, 700]
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()

# Initialize the joysticks
pygame.joystick.init()

# Get ready to print
textPrint = TextPrint()

# -------- Main Program Loop -----------
while not done:
    # EVENT PROCESSING STEP
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

        # Possible joystick actions: JOYAXISMOTION JOYBALLMOTION JOYBUTTONDOWN
        # JOYBUTTONUP JOYHATMOTION
        if event.type == pygame.JOYBUTTONDOWN:
            print("Joystick button pressed.")
        if event.type == pygame.JOYBUTTONUP:
            print("Joystick button released.")

    # DRAWING STEP
    # 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)
    textPrint.reset()

    # Get count of joysticks
    joystick_count = pygame.joystick.get_count()

    textPrint.print(screen, "Number of joysticks: {}".format(joystick_count))
    textPrint.indent()

    # For each joystick:
    for i in range(joystick_count):
        joystick = pygame.joystick.Joystick(i)
        joystick.init()

        textPrint.print(screen, "Joystick {}".format(i))
        textPrint.indent()

        # Get the name from the OS for the controller/joystick
        name = joystick.get_name()
        textPrint.print(screen, "Joystick name: {}".format(name))

        # Usually axis run in pairs, up/down for one, and left/right for
        # the other.
        axes = joystick.get_numaxes()
        textPrint.print(screen, "Number of axes: {}".format(axes))
        textPrint.indent()

        for i in range(axes):
            axis = joystick.get_axis(i)
            textPrint.print(screen, "Axis {} value: {:>6.3f}".format(i, axis))
        textPrint.unindent()

        buttons = joystick.get_numbuttons()
        textPrint.print(screen, "Number of buttons: {}".format(buttons))
        textPrint.indent()

        for i in range(buttons):
            button = joystick.get_button(i)
            textPrint.print(screen, "Button {:>2} value: {}".format(i, button))
        textPrint.unindent()

        # Hat switch. All or nothing for direction, not like joysticks.
        # Value comes back in an array.
        hats = joystick.get_numhats()
        textPrint.print(screen, "Number of hats: {}".format(hats))
        textPrint.indent()

        for i in range(hats):
            hat = joystick.get_hat(i)
            textPrint.print(screen, "Hat {} value: {}".format(i, str(hat)))
        textPrint.unindent()

        textPrint.unindent()

    # ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT

    # 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.
# If you forget this line, the program will 'hang'
# on exit if running from IDLE.
pygame.quit()

10.5 Review

10.5.1 Multiple Choice Quiz

Click here for a multiple-choice quiz.

10.5.2 Short Answer Worksheet

Click here for the chapter worksheet.

10.5.3 Lab

Click here for the chapter lab.


You are not logged in. Log in here and track your progress.