Pygame Shmup Part 6: Sprite Animation
This is part 6 of our “Shmup” project. If you haven’t already read through the previous parts, please start with Part 1. In this lesson we’ll make our meteors more interesting by adding a little bit of sprite animation.
About this series
In this series of lessons we’ll build a complete game using Python and Pygame. It’s intended for beginning programmers who already understand the basics of Python and are looking to deepen their Python understanding and learn the fundamentals of programming games.
You can watch a video version of this lesson here:
Animated meteors
All of our meteors look exactly the same, which is not very exciting to look at:
How can we add a little more variety and visual appeal to the meteors? One way would be to add a bit of rotation, so that they look more like rocks tumbling through space. Rotation is relatively easy to do — just as we used the pygame.transform.scale() function to change the size of our Player sprite, we can use pygame.transform.rotate() to perform a rotation. However, there are a few things we need to learn along the way in order to make it work correctly.
First, let’s add some new properties to the Mob sprite:
The first property, rot (short for ‘rotation’), will measure how many degrees the sprite should be rotated. It starts out at 0 and will change over time. rot_speed measures how many degrees the sprite should rotate each time — bigger numbers will mean faster rotation. We’re picking a random value, with negative being counterclockwise and positive being clockwise.
The last property is an important one for controlling animation speed. We don’t really want to change the sprite’s image every frame, or it will appear much too fast. Whenever you’re animating a sprite’s image, you have to figure out the timing — how often the image should be changed.
We have a pygame.time.Clock() object, named clock , which is helping us control the FPS. By calling pygame.time.get_ticks() we can find out how many milliseconds have elapsed since the clock was started. This way, we can tell if enough time has gone by for us to make another change to the sprite’s image.
Rotating the image
We’re going to need a few lines of code to perform this operation, so we’ll make a new method for it called self.rotate() , which we can add to the update() method:
This way we can keep our update method from getting too crowded, and you can also comment out that line if you want to turn rotation off. Here’s the start of our rotate method:
First, we check what time it is currently, then we subtract the time of the last update. If more than 50 milliseconds have gone by, then we’re going to update the image. We put the value of now into last_update and we can perform the rotation. Now, you might think that this is as simple as just applying the rotation to the sprite like this:
However, if you try this, you will have a problem:
Rotation is destructive!
This happens because images are made up of a grid of pixels. When you try to rotate those pixels into a new position, some of them won’t line up anymore, so some information will be lost. That’s fine if you’re only rotating once, but repeatedly rotating the image will result in a scrambled image.
The solution is to use our rot variable to keep track of the total rotation amount (adding rot_speed each update) and to rotate the original image by that amount. This way we’re always starting with a clean image and rotating it only once.
First let’s keep a copy of the original image:
Then in the rotate method we can update the value of rot and apply that rotation to the original image:
Note that we used the remainder operator — % — to prevent rot from having values greater than 360.
We’re almost there — the images look fine — but we still have a small problem:
The meteors look like they’re bouncing instead of rotating smoothly.
Updating the rect
After rotating an image, the size of the rect may no longer be correct. Let’s look at an example where we want to rotate a picture of a spaceship:
Here we can see that while we’re rotating the image, the rect is remaining the same. We need to calculate a new rect each time the image changes:
It’s easy to see how the size of the rect can change quite a bit depending on how the image is rotated. Now, to fix the “bouncing” effect, we need to make sure we keep the new rect centered at the same location as the old one, instead of being anchored at the top left corner:
Bringing this to back to our rotate code, we just record the location of the rect’s center, calculate the new rect, and set its center to that saved one:
Random meteor images
The last thing we can do to make the meteors more interesting is to randomize the images, using different sizes and looks for each meteor.
First, we’ll load all the meteor images and put them into a list:
Then all we have to do is choose a random image when our meteor is spawned:
Wrapping up
Animated sprites add a lot of visual appeal to a game, whether it’s spinning rocks or a running/jumping/crouching hero. However, the more animations you have, the more images you have to keep track of. The trick is to keep them organized and take advantage of tools like the pygame.transform commands — as long as you’re careful about their limitations.
In the next part we’ll start keeping score and dive into how to draw text on the screen.
Pygame and rotation of a sprite [duplicate]
I’ve been playing around for a while now with the function pygame.transform.rotate() , _get_rect() , _get_rect().center and _get_size() .
I have an image of an arrow that I want to rotate and I have trouble to understand what’s going on.
My question is from which point (x,y) is the rotation done?
In my example I have an image of size 28×182 and I put at 200,100 with screen.blit() .
![]()
3 Answers 3
You can use the examples here to rotate while keeping the image’s centre:
I can’t seem to find out what the centre of rotation pygame uses actually is, bar a reference here:
it doesn’t rotate about the origin, or any particular point for that matter: the fixed point of rotation depends on the surface dimensions and the rotation angle
![]()
The original documentation doesn’t seem to specify the answer to your question.
Unfiltered counterclockwise rotation. The angle argument represents degrees and can be any floating point value. Negative angle amounts will rotate clockwise.
Unless rotating by 90 degree increments, the image will be padded larger to hold the new size. If the image has pixel alphas, the padded area will be transparent. Otherwise pygame will pick a color that matches the Surface colorkey or the topleft pixel value.
Как повернуть спрайт в pygame

pygame documentation
A Surface transform is an operation that moves or resizes the pixels. All these functions take a Surface to operate on and return a new Surface with the results.
Some of the transforms are considered destructive. These means every time they are performed they lose pixel data. Common examples of this are resizing and rotating. For this reason, it is better to re-transform the original surface than to keep transforming an image multiple times. (For example, suppose you are animating a bouncing spring which expands and contracts. If you applied the size changes incrementally to the previous images, you would lose detail. Instead, always begin with the original image and scale to the desired size.)
Changed in pygame 2.0.2: transform functions now support keyword arguments.
This can flip a Surface either vertically, horizontally, or both. The arguments flip_x and flip_y are booleans that control whether to flip each axis. Flipping a Surface is non-destructive and returns a new Surface with the same dimensions.
Resizes the Surface to a new size, given as (width, height). This is a fast scale operation that does not sample the results.
An optional destination surface can be used, rather than have it create a new one. This is quicker if you want to repeatedly scale something. However the destination must be the same size as the size (width, height) passed in. Also the destination surface must be the same format.
Experimental: feature still in development available for testing and feedback. It may change. Please leave scale_by feedback with authors
Same as scale() , but scales by some factor, rather than taking the new size explicitly. For example, transform.scale_by(surf, 3) will triple the size of the surface in both dimensions. Optionally, the scale factor can be a sequence of two numbers, controlling x and y scaling separately. For example, transform.scale_by(surf, (2, 1)) doubles the image width but keeps the height the same.
New in pygame 2.1.3.
Unfiltered counterclockwise rotation. The angle argument represents degrees and can be any floating point value. Negative angle amounts will rotate clockwise.
Unless rotating by 90 degree increments, the image will be padded larger to hold the new size. If the image has pixel alphas, the padded area will be transparent. Otherwise pygame will pick a color that matches the Surface colorkey or the topleft pixel value.
This is a combined scale and rotation transform. The resulting Surface will be a filtered 32-bit Surface. The scale argument is a floating point value that will be multiplied by the current resolution. The angle argument is a floating point value that represents the counterclockwise degrees to rotate. A negative rotation angle will rotate clockwise.
This will return a new image that is double the size of the original. It uses the AdvanceMAME Scale2X algorithm which does a ‘jaggie-less’ scale of bitmap graphics.
This really only has an effect on simple images with solid colors. On photographic and antialiased images it will look like a regular unfiltered scale.
An optional destination surface can be used, rather than have it create a new one. This is quicker if you want to repeatedly scale something. However the destination must be twice the size of the source surface passed in. Also the destination surface must be the same format.
Uses one of two different algorithms for scaling each dimension of the input surface as required. For shrinkage, the output pixels are area averages of the colors they cover. For expansion, a bilinear filter is used. For the x86-64 and i686 architectures, optimized MMX routines are included and will run much faster than other machine types. The size is a 2 number sequence for (width, height). This function only works for 24-bit or 32-bit surfaces. An exception will be thrown if the input surface bit depth is less than 24.
New in pygame 1.8.
Experimental: feature still in development available for testing and feedback. It may change. Please leave smoothscale_by feedback with authors
Same as smoothscale() , but scales by some factor, rather than taking the new size explicitly. For example, transform.smoothscale_by(surf, 3) will triple the size of the surface in both dimensions. Optionally, the scale factor can be a sequence of two numbers, controlling x and y scaling separately. For example, transform.smoothscale_by(surf, (2, 1)) doubles the image width but keeps the height the same.
New in pygame 2.1.3.
Shows whether or not smoothscale is using MMX or SSE acceleration. If no acceleration is available then "GENERIC" is returned. For a x86 processor the level of acceleration to use is determined at runtime.
This function is provided for pygame testing and debugging.
Sets smoothscale acceleration. Takes a string argument. A value of ‘GENERIC’ turns off acceleration. ‘MMX’ uses MMX instructions only. ‘SSE’ allows SSE extensions as well. A value error is raised if type is not recognized or not supported by the current processor.
This function is provided for pygame testing and debugging. If smoothscale causes an invalid instruction error then it is a pygame/SDL bug that should be reported. Use this function as a temporary fix only.
Extracts a portion of an image. All vertical and horizontal pixels surrounding the given rectangle area are removed. The corner areas (diagonal to the rect) are then brought together. (The original image is not altered by this operation.)
NOTE : If you want a "crop" that returns the part of an image within a rect, you can blit with a rect to a new surface or copy a subsurface.
Finds the edges in a surface using the laplacian algorithm.
New in pygame 1.8.
Takes a sequence of surfaces and returns a surface with average colors from each of the surfaces.
palette_colors — if true we average the colors in palette, otherwise we average the pixel values. This is useful if the surface is actually greyscale colors, and not palette colors.
Note, this function currently does not handle palette using surfaces correctly.
New in pygame 1.8.
New in pygame 1.9: palette_colors argument
Finds the average color of a Surface or a region of a surface specified by a Rect, and returns it as a Color. If consider_alpha is set to True, then alpha is taken into account (removing the black artifacts).
New in pygame 2.1.2: consider_alpha argument
This versatile function can be used for find colors in a ‘surf’ close to a ‘search_color’ or close to colors in a separate ‘search_surf’.
It can also be used to transfer pixels into a ‘dest_surf’ that match or don’t match.
By default it sets pixels in the ‘dest_surf’ where all of the pixels NOT within the threshold are changed to set_color. If inverse_set is optionally set to True, the pixels that ARE within the threshold are changed to set_color.
If the optional ‘search_surf’ surface is given, it is used to threshold against rather than the specified ‘set_color’. That is, it will find each pixel in the ‘surf’ that is within the ‘threshold’ of the pixel at the same coordinates of the ‘search_surf’.
dest_surf (pygame.Surface pygame object for representing images or None) — Surface we are changing. See ‘set_behavior’. Should be None if counting (set_behavior is 0).
threshold (pygame.Color pygame object for color representations ) — Within this distance from search_color (or search_surf). You can use a threshold of (r,g,b,a) where the r,g,b can have different thresholds. So you could use an r threshold of 40 and a blue threshold of 2 if you like.
set_behavior=1 (default). Pixels in dest_surface will be changed to ‘set_color’.
set_behavior=0 we do not change ‘dest_surf’, just count. Make dest_surf=None.
set_behavior=2 pixels set in ‘dest_surf’ will be from ‘surf’.
search_surf=None (default). Search against ‘search_color’ instead.
search_surf=Surface. Look at the color in ‘search_surf’ rather than using ‘search_color’.
False, default. Pixels outside of threshold are changed.
True, Pixels within threshold are changed.
The number of pixels that are within the ‘threshold’ in ‘surf’ compared to either ‘search_color’ or search_surf .
New in pygame 1.8.
Changed in pygame 1.9.4: Fixed a lot of bugs and added keyword arguments. Test your code.
Name already in use
PyGameExamplesAndAnswers / documentation / pygame / pygame_surface_rotate.md
- Go to file T
- Go to line L
- Copy path
- Copy permalink
1 contributor
Users who have contributed to this file
- Open with Desktop
- View raw
- Copy raw contents Copy raw contents
Copy raw contents
Copy raw contents
«It is not the language that makes programs appear simple. It is the programmer that make the language appear simple!»
«Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship
Related Stack Overflow questions:
- How do I rotate an image around its center using PyGame?
When you use pygame.transform.rotate the size of the new rotated image is increased compared to the size of the original image. You must make sure that the rotated image is placed so that its center remains in the center of the non-rotated image. To do this, get the rectangle of the original image and set the position. Get the rectangle of the rotated image and set the center position through the center of the original rectangle.
Returns a tuple from the function red_center , with the rotated image and the bounding rectangle of the rotated image:
Or write a function which rotates and .blit the image:
For the following examples and explanation I’ll use a simple image generated by a rendered text:
If that is done progressively in a loop, then the image gets distorted and rapidly increases:
This is cause, because the bounding rectangle of a rotated image is always greater than the bounding rectangle of the original image (except some rotations by multiples of 90 degrees).
The image gets distort because of the multiply copies. Each rotation generates a small error (inaccuracy). The sum of the errors is growing and the images decays.
That can be fixed by keeping the original image and «blit» an image which was generated by a single rotation operation form the original image.
Now the image seems to arbitrary change its position, because the size of the image changes by the rotation and origin is always the top left of the bounding rectangle of the image.
This can be compensated by comparing the axis aligned bounding box of the image before the rotation and after the rotation.
For the following math pygame.math.Vector2 is used. Note in screen coordinates the y-axis points down the screen, but the mathematical y axis points form the bottom to the top. This causes that the y axis has to be «flipped» during calculations
Set up a list with the 4 corner points of the bounding box:
Rotate the vectors to the corner points by pygame.math.Vector2.rotate :
Get the minimum and the maximum of the rotated points:
Calculate the «compensated» origin of the upper left point of the image by adding the minimum of the rotated box to the position. For the y coordinate max_box[1] is the minimum, because of the «flipping» along the y axis:
It is even possible to define a pivot on the original image. Compute the offset vector from the center of the image to the pivot and rotate the vector. A vector can be represented by pygame.math.Vector2 and can be rotated with pygame.math.Vector2.rotate . Notice that pygame.math.Vector2.rotate rotates in the opposite direction than pygame.transform.rotate . Therefore the angle has to be inverted:
Compute the offset vector from the center of the image to the pivot on the image:
Rotate the offset vector the same angle you want to rotate the image:
Calculate the new center point of the rotated image by subtracting the rotated offset vector from the pivot point in the world:
Rotate the image and set the center point of the rectangle enclosing the rotated image. Finally blit the image :
In the following example program, the function blitRotate(surf, image, pos, originPos, angle) does all the above steps and «blit» a rotated image to a surface.
surf is the target Surface
image is the Surface which has to be rotated and blit
pos is the position of the pivot on the target Surface surf (relative to the top left of surf )
originPos is position of the pivot on the image Surface (relative to the top left of image )
angle is the angle of rotation in degrees
This means, the 2nd argument ( pos ) of blitRotate is the position of the pivot point in the window and the 3rd argument ( originPos ) is the position of the pivot point on the rotating Surface:
repl.it/@Rabbid76/PyGame-RotateAroundPivot
repl.it/@Rabbid76/PyGame-RotateAroundPivotAndZoom
First the position of the pivot on the Surface has to be defined:
When an image is rotated, then its size increase. We have to compare the axis aligned bounding box of the image before the rotation and after the rotation.
For the following math pygame.math.Vector2 is used. Note in screen coordinates the y points down the screen, but the mathematical y axis points form the bottom to the top. This causes that the y axis has to be «flipped» during calculations
Set up a list with the 4 corner points of the bounding box and rotate the vectors to the corner points by pygame.math.Vector2.rotate . Finally find the minimum of the rotated box. Since the y axis in pygame points downwards, this has to be compensated by finding the maximum of the rotated inverted height («max(rotate(-h))«):
The computation of min_x and min_y can be improved by directly computing the x and y component of the rotated vectors by trigonometric functions:
Calculate the «compensated» origin of the upper left point of the image by adding the minimum of the rotated box to the position in relation to the pivot on the image:
In the following example program, the function blitRotate(surf, image, pos, originPos, angle) does all the above steps and blit a rotated image to the Surface which is associated to the display:
- surf is the target Surface
- image is the Surface which has to be rotated and blit
- origin is the position of the pivot on the target Surface surf (relative to the top left of surf )
- pivot is position of the pivot on the image Surface (relative to the top left of image )
- angle is the angle of rotation in degrees
The same algorithm can be used for a Sprite, too.
In that case the position ( self.pos ), pivot ( self.pivot ) and angle ( self.angle ) are instance attributes of the class. In the update method the self.rect and self.image attributes are computed. e.g:
Minimal example — Rotate Sprite around off center pivot (boomerang)
repl.it/@Rabbid76/PyGame-RotateSpriteAroundOffCenterPivot
Minimal example — Rotate Sprite around off center pivot (cannon)
repl.it/@Rabbid76/PyGame-RotateSpriteAroundOffCenterPivotCannon
To rotate an image around a pivot point and zoom the image, all you have to do is scale the vector from the center of the image to the pivot point on the image by the zoom factor:
offset_center_to_pivot = pygame.math.Vector2(origin) — image_rect.center
The final function that rotates an image around a pivot point, zooms and blit s the image might look like this:
The scaling factor can also be specified separately for the x and y axis:
Minimal example — Rotate Sprite around pivot and zoom
repl.it/@Rabbid76/PyGame-RotateZoomPivot
Minimal example — Rotate Sprite around pivot and zoom — cannon
repl.it/@Rabbid76/PyGame-RotateZoomPivot-Example