Thanks to the Pygame library we installed in the previous post, we can draw 2D graphics. In this post, I propose to introduce controls with the keyboard as well as some improvements like window centering and frame rate handling.
This post is part of the Discover Python and Patterns series
Pygame events allow the capture of keyboard presses:
import pygame
pygame.init()
window = pygame.display.set_mode((640,480))
x = 120
y = 120
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
break
elif event.key == pygame.K_RIGHT:
x += 8
elif event.key == pygame.K_LEFT:
x -= 8
elif event.key == pygame.K_DOWN:
y += 8
elif event.key == pygame.K_UP:
y -= 8
window.fill((0,0,0))
pygame.draw.rect(window,(0,0,255),(x,y,400,240))
pygame.display.update()
pygame.quit()
This program draws a blue rectangle at coordinates (x,y) with a with of 400 pixels and a height of 240 pixels. You can move it using the arrows. You can quit the game using the Espace key or closing the window.
In lines 15-26, there is the handling of the keyboard. It manages key pressing: it is when a key goes from unpressed to pressed. It is different from holding or releasing a key.
Once we know that it is a pygame.KEYDOWN
event (line 15), the key
attribute of the event contains the value of the pressed key. These values are always something like pygame.K_XXX
. In Spyder, if you type pygame.K_
, and then hit Ctrl+Space, you will see the list of all key names.
The Espace key quit the game (like the pygame.QUIT
event). The arrow keys change the x and y coordinates of the rectangle.
Line 28 calls the function window.fill()
with a black color (0,0,0)
. It fills all the window client area with a color. You can try with a different color, for instance, red (255,0,0)
, green (0,255,0)
or blue (0,0,255)
.
Exercise: Move with WASD. Update the previous program to move the rectangle with the arrows and the W, A, S, and D keys.
When the previous program starts, the window can appear at different locations on the screen. We can force the centering by adding the following commands at the beginning of the program:
import os
os.environ['SDL_VIDEO_CENTERED'] = '1'
The first line imports the standard package os
. It contains many functions related to the operating system.
The second line sets the environment variable SDL_VIDEO_CENTERED
to "1" (NB: the character "1" not the number). Pygame is based on the SDL library, and this one uses environment variables to define some options like window centering. You can do more hacks like this one, see the SDL document at https://www.libsdl.org.
If you have a look at the CPU usage when the program runs, you can see that it uses 100% of a core. It is because the program renders as many frames as possible, and certainly much more than what a screen can display.
To save computations, we can use a pygame.tick.Clock
to limit the frame rate. First of all, we need to create such an object at the beginning of the program:
clock = pygame.time.Clock()
Note that pygame.tick.Clock()
looks like a function call, and the line above puts the return value of this function into the variable clock
. It is not exactly a function call: it is the creation of an instance of the pygame.time.Clock
class. Class instances are advanced objects; for example, they can have several sub-variables called attributes. I introduce them in the next post.
Then, at the end of the while
loop, add the following line:
clock.tick(60)
It limits the frame rate to 60 per second. More specifically, it releases the CPU such that the frame rate is 60 per second. For instance, if the beginning of the while
took five milliseconds, then it releases the CPU for 11 milliseconds (at 60 frames per second, we have 16 milliseconds per frame).
Considering the function call, it should look strange to you: clock
is a variable, not a package like random
or pygame
. It works because clock
refers to a class instance. These objects can contain functions called methods. So, tick()
is a method of the pygame.time.Clock
class. It has two arguments: clock
and 60. The first argument is the class instance, and all the others are in the parentheses.
To finish with improvements, I propose to set a window caption (or title) and an icon. For the first one, if we want to see "My game" in the window title, we have to call pygame.display.set_caption()
after the creation of the window:
pygame.display.set_caption("My game")
For the window icon, we first need an image. We can load one using the pygame.image.load()
function:
iconImage = pygame.image.load("icon.png")
It assumes that the file "icon.png" is in the folder where our program is running. You can download this one here (it is a tank, we'll program a small tank game in the next posts).
Then, the window icon is set using the pygame.display.set_icon()
function:
pygame.display.set_icon(iconImage)
Note that we can pack both lines into a single one (the use of a variable is not mandatory):
pygame.display.set_icon(pygame.image.load("icon.png"))
import os
import pygame
os.environ['SDL_VIDEO_CENTERED'] = '1'
pygame.init()
window = pygame.display.set_mode((640,480))
pygame.display.set_caption("Discover Python & Patterns - https://www.patternsgameprog.com")
pygame.display.set_icon(pygame.image.load("icon.png"))
clock = pygame.time.Clock()
x = 120
y = 120
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
break
elif event.key == pygame.K_RIGHT:
x += 8
elif event.key == pygame.K_LEFT:
x -= 8
elif event.key == pygame.K_DOWN:
y += 8
elif event.key == pygame.K_UP:
y -= 8
window.fill((0,0,0))
pygame.draw.rect(window,(0,0,255),(x,y,400,240))
pygame.display.update()
clock.tick(60)
pygame.quit()
In the next post, I will introduce classes, and we will use them to implement the Game Loop pattern.
import pygame
pygame.init()
window = pygame.display.set_mode((640,480))
x = 120
y = 120
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
break
elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
x += 8
elif event.key == pygame.K_LEFT or event.key == pygame.K_a:
x -= 8
elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
y += 8
elif event.key == pygame.K_UP or event.key == pygame.K_w:
y -= 8
window.fill((0,0,0))
pygame.draw.rect(window,(0,0,255),(x,y,400,240))
pygame.display.update()
pygame.quit()