Surfaces can not only moved around, but also rotated and -unlike beer mats- zoomed. The next source code examples introduce a new method of keyboard control.
Instead of checking a queued event with pygame.event.get() the function pygame.key.get_pressed() delivers the actual state of the complete keyboard. This state is represented by a tuple of 0/1 values for each key. A value of 0 signals an unpressed, a value of 1 signals a pressed State of a key. You can access the value for a specific key with the K_<A_SPECIFIC_KEY> values defined in pygame.constants. For example
pressedkeys = pygame.key.get_pressed() if pressedkeys[pygame.K_x]: do_something()
checks if the key “x” is pressed. This method is ideal for constant movement, where a sprite moves or rotate as long as a specific key is pressed.
In the source code example bleow, the cursor keys <key>←</key> <key>↑</key> <key>→</key> <key>↓</key> are used to move the snake surface, while the keys <key>w</key> and <key>s</key> zoom/shrink the snake and the keys <key>a</key> and <key>d</key> rotate the snake.
It is possible to press several keys together, like left and right cursor, and the program will move the correctly (not at all in this case):
dx, dy = 0, 0 # no cursor key, no movement if pressedkeys[pygame.K_LEFT]: dx -= speed if pressedkeys[pygame.K_RIGHT]: dx += speed
Note that this method of keyboard control is less precise than the
if pygame.event.type… - method because there is no guarantee that a fast key-pressing will be noticed by pygame. Depending on how fast the computer can calculate each cycle of the main-loop, there could be a chance that you press and release a key just between 2 main loop cycles and pygame would not notice. However, if your program runs around 30 frames per second, you would need lightning fast fingers to become not noticed by pygame.
Also note that this keyboard control method is not ideal for pre-defined movement like stones on a board game. While you can control with the time-based movement the speed of a surface, it lay in the skill of the user and his dexterity in pressing and releasing a key to control how long a surface moves (and where its movements end exactly).
Also note that the cleaning of the old surface (using the
subsurface method) is done inside a try…except block. If the subsurface is no longer inside the surface, pygame would raise an error. This can happen when you zoom or rotate the snake outside the screen.
# only blit the part of the background where the snake was (cleanrect) try: #if the subsurface is outside the screen pygame would raise an error #this can happen when using rotozoom, therfore check inside try..except #Surface.subsurface(Rect): return Surface dirtyrect = background.subsurface((round(snakex,0), round(snakey,0), snake.get_width(), snake.get_height())) screen.blit(dirtyrect, (round(snakex,0), round(snakey,0))) except: screen.blit(background,(0,0)) # blit the whole background (slow but secure)
Also note that in this code example there is no checking if the snake is inside the screen whatsoever. You can try to move the snake outside of the right , move it down, left and up and reappear from the left.
the pygame.transform.rotozoom command would rotate around the position (0,0) of a Surface - the topleft corner.
To create a more pleasing rotation aroundthe center effect, we need some tricks: First, the original snake surface is copied into snake_original right after creation, to have always a not-manipulated image:
snake_original = snake.copy() # store a unmodified copy of the snake surface
snake_original = snake
would not work because both snake and snake_original would be just pointers to the same python object (the manipulated snake). If you need a copy of an object, use the .copy() method</note>
Always the original_snake surface is zoomed and rotated with the current zoom and angle values by the pygame.transform.rotozoom command.
But how to avoid a rotation around the topleft corner?
For that, before
pygame.transform.rotozoom does its work, the current rectangle of the snake surface is stored into the variable oldrect by the
surface.get_rect() command. Pygame rects have several useful properties, like pre-defined constants for center, centerx, centery, width, height etc.
After rotating and now having a usually resized snake surface (pygame always calculate a rectangle around the visible surface, thus a rotated snake fits in a bigger rectangle than a non-rotated snake) the rectangle of the new surface is stored into the variable newrect. This is also done with the
Now the code example simply blits the new surface so that its new center lays on the same spot as the center of the old rectangle - rotated around the center.
if turnfactor != 0 or zoomfactor !=1.0: angle += turnfactor * turnspeed * seconds # time-based turning zoom *= zoomfactor # the surface shrinks and zooms and moves by rotating oldrect = snake.get_rect() # store current surface rect snake = pygame.transform.rotozoom(snake_original, angle, zoom) newrect = snake.get_rect() # store new surface rect # put new surface rect center on same spot as old surface rect center snakex += oldrect.centerx - newrect.width / 2 snakey += oldrect.centery - newrect.height / 2
To run this example you need:
|011_rotozoom.py|| || Download the whole Archive with all files from Github:
| background640x480_a.jpg ||
| snake.gif ||
View/Edit/Download the file directly in Github: https://github.com/horstjens/ThePythonGameBook/blob/master/pygame/011_rotozoom.py
click reload in your browser if you see no code here: