Sample Code: Box jump and platforms
How do we get objects to move using the keyboard, mouse, or a game controller?
4.1 Introduction
So far, we've shown how to animate items on the screen, but not how tointeract 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.
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:
Function to draw a snowman
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.
Calling draw_snowman
# 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)
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:
Code to draw a stickfigure
# 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)
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.
Function that always draws a stickfigure in the same place
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)
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 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.
Second attempt at stickfigure function
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 the figure and how the stick figure is not drawn at the (0, 0) coordinate passed in.
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?
Find the smallest x value, and the smallest y value as shown above. 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:
Third attempt at stickfigure function
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:
Final stickfigure function
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)
4.2 Mouse
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 below.
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:
Controlling an object via the mouse
# 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 below.
The mouse can be hidden by using the following code right before the main program loop:
Hide the mouse cursor
# Hide the mouse cursor
pygame.mouse.set_visible(False)
4.3 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:
Create an initial x and y for our start position.
Set a “velocity” in pixels per frame when an arrow key is pressed down. (keydown)
Reset the velocity to zero when an arrow key is released. (keyup)
Adjust the x and y each frame depending on the velocity.
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:
Controlling an object via the keyboard
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)
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.
This table shows a full list of key-codes that can be used in Pygame
4.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:
Initializing the game controller for use
# 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.
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.
Controlling an object via a game controller
# 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)
Controllers have a lot of joysticks, buttons, and even “hat” switches. Below is an example screenshot 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.