Pygame Image rotation very stuttery [duplicate] - python
I had been trying to rotate an image around its center in using pygame.transform.rotate() but it's not working. Specifically the part that hangs is rot_image = rot_image.subsurface(rot_rect).copy(). I get the exception:
ValueError: subsurface rectangle outside surface area
Here is the code used to rotate an image:
def rot_center(image, angle):
"""rotate an image while keeping its center and size"""
orig_rect = image.get_rect()
rot_image = pygame.transform.rotate(image, angle)
rot_rect = orig_rect.copy()
rot_rect.center = rot_image.get_rect().center
rot_image = rot_image.subsurface(rot_rect).copy()
return rot_image
Short answer:
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:
def rot_center(image, angle, x, y):
rotated_image = pygame.transform.rotate(image, angle)
new_rect = rotated_image.get_rect(center = image.get_rect(center = (x, y)).center)
return rotated_image, new_rect
Or write a function which rotates and .blit the image:
def blitRotateCenter(surf, image, topleft, angle):
rotated_image = pygame.transform.rotate(image, angle)
new_rect = rotated_image.get_rect(center = image.get_rect(topleft = topleft).center)
surf.blit(rotated_image, new_rect)
Long answer:
An image (pygame.Surface) can be rotated by pygame.transform.rotate.
If that is done progressively in a loop, then the image gets distorted and rapidly increases:
while not done:
# [...]
image = pygame.transform.rotate(image, 1)
screen.blit(image, pos)
pygame.display.flip()
This is 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.
angle = 0
while not done:
# [...]
rotated_image = pygame.transform.rotate(image, angle)
angle += 1
screen.blit(rotated_image, pos)
pygame.display.flip()
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 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:
w, h = image.get_size()
box = [pygame.math.Vector2(p) for p in [(0, 0), (w, 0), (w, -h), (0, -h)]]
Rotate the vectors to the corner points by pygame.math.Vector2.rotate:
box_rotate = [p.rotate(angle) for p in box]
Get the minimum and the maximum of the rotated points:
min_box = (min(box_rotate, key=lambda p: p[0])[0], min(box_rotate, key=lambda p: p[1])[1])
max_box = (max(box_rotate, key=lambda p: p[0])[0], max(box_rotate, key=lambda p: p[1])[1])
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:
origin = (pos[0] + min_box[0], pos[1] - max_box[1])
rotated_image = pygame.transform.rotate(image, angle)
screen.blit(rotated_image, origin)
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 vector from the center of the image to the pivot:
image_rect = image.get_rect(topleft = (pos[0] - originPos[0], pos[1]-originPos[1]))
offset_center_to_pivot = pygame.math.Vector2(pos) - image_rect.center
Rotate the vector
rotated_offset = offset_center_to_pivot.rotate(-angle)
Calculate the center of the rotated image:
rotated_image_center = (pos[0] - rotated_offset.x, pos[1] - rotated_offset.y)
Rotate and blit the image:
rotated_image = pygame.transform.rotate(image, angle)
rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)
screen.blit(rotated_image, rotated_image_rect)
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:
Minimal example: repl.it/#Rabbid76/PyGame-RotateAroundPivot
import pygame
pygame.init()
screen = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
def blitRotate(surf, image, pos, originPos, angle):
# offset from pivot to center
image_rect = image.get_rect(topleft = (pos[0] - originPos[0], pos[1]-originPos[1]))
offset_center_to_pivot = pygame.math.Vector2(pos) - image_rect.center
# roatated offset from pivot to center
rotated_offset = offset_center_to_pivot.rotate(-angle)
# roatetd image center
rotated_image_center = (pos[0] - rotated_offset.x, pos[1] - rotated_offset.y)
# get a rotated image
rotated_image = pygame.transform.rotate(image, angle)
rotated_image_rect = rotated_image.get_rect(center = rotated_image_center)
# rotate and blit the image
surf.blit(rotated_image, rotated_image_rect)
# draw rectangle around the image
pygame.draw.rect(surf, (255, 0, 0), (*rotated_image_rect.topleft, *rotated_image.get_size()),2)
def blitRotate2(surf, image, topleft, angle):
rotated_image = pygame.transform.rotate(image, angle)
new_rect = rotated_image.get_rect(center = image.get_rect(topleft = topleft).center)
surf.blit(rotated_image, new_rect.topleft)
pygame.draw.rect(surf, (255, 0, 0), new_rect, 2)
try:
image = pygame.image.load('AirPlaneFront.png')
except:
text = pygame.font.SysFont('Times New Roman', 50).render('image', False, (255, 255, 0))
image = pygame.Surface((text.get_width()+1, text.get_height()+1))
pygame.draw.rect(image, (0, 0, 255), (1, 1, *text.get_size()))
image.blit(text, (1, 1))
w, h = image.get_size()
angle = 0
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pos = (screen.get_width()/2, screen.get_height()/2)
screen.fill(0)
blitRotate(screen, image, pos, (w/2, h/2), angle)
#blitRotate2(screen, image, pos, angle)
angle += 1
pygame.draw.line(screen, (0, 255, 0), (pos[0]-20, pos[1]), (pos[0]+20, pos[1]), 3)
pygame.draw.line(screen, (0, 255, 0), (pos[0], pos[1]-20), (pos[0], pos[1]+20), 3)
pygame.draw.circle(screen, (0, 255, 0), pos, 7, 0)
pygame.display.flip()
pygame.quit()
exit()
See also Rotate surface and the answers to the questions:
How can you rotate an image around an off center pivot in Pygame
How to rotate an image around its center while its scale is getting larger(in Pygame)
How to rotate an image(player) to the mouse direction?
How to set the pivot point (center of rotation) for pygame.transform.rotate()?
How do you point the barrel towards mouse in pygame?
There are some problems with the top answer: The position of the previous rect needs to be available in the function, so that we can assign it to the new rect, e.g.:
rect = new_image.get_rect(center=rect.center)
In the other answer the location is obtained by creating a new rect from the original image, but that means it will be positioned at the default (0, 0) coordinates.
The example below should work correctly. The new rect needs the center position of the old rect, so we pass it as well to the function. Then rotate the image, call get_rect to get a new rect with the correct size and pass the center attribute of the old rect as the center argument. Finally, return both the rotated image and the new rect as a tuple and unpack it in the main loop.
import pygame as pg
def rotate(image, rect, angle):
"""Rotate the image while keeping its center."""
# Rotate the original image without modifying it.
new_image = pg.transform.rotate(image, angle)
# Get a new rect with the center of the old rect.
rect = new_image.get_rect(center=rect.center)
return new_image, rect
def main():
clock = pg.time.Clock()
screen = pg.display.set_mode((640, 480))
gray = pg.Color('gray15')
blue = pg.Color('dodgerblue2')
image = pg.Surface((320, 200), pg.SRCALPHA)
pg.draw.polygon(image, blue, ((0, 0), (320, 100), (0, 200)))
# Keep a reference to the original to preserve the image quality.
orig_image = image
rect = image.get_rect(center=(320, 240))
angle = 0
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
angle += 2
image, rect = rotate(orig_image, rect, angle)
screen.fill(gray)
screen.blit(image, rect)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
Here's another example with a rotating pygame sprite.
import pygame as pg
class Entity(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pg.Surface((122, 70), pg.SRCALPHA)
pg.draw.polygon(self.image, pg.Color('dodgerblue1'),
((1, 0), (120, 35), (1, 70)))
# A reference to the original image to preserve the quality.
self.orig_image = self.image
self.rect = self.image.get_rect(center=pos)
self.angle = 0
def update(self):
self.angle += 2
self.rotate()
def rotate(self):
"""Rotate the image of the sprite around its center."""
# `rotozoom` usually looks nicer than `rotate`. Pygame's rotation
# functions return new images and don't modify the originals.
self.image = pg.transform.rotozoom(self.orig_image, self.angle, 1)
# Create a new rect with the center of the old rect.
self.rect = self.image.get_rect(center=self.rect.center)
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group(Entity((320, 240)))
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
return
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
You are deleting the rect that rotate creates. You need to preserve rect, since it changes size when rotated.
If you want to preserve the objects location, do:
def rot_center(image, angle):
"""rotate a Surface, maintaining position."""
loc = image.get_rect().center #rot_image is not defined
rot_sprite = pygame.transform.rotate(image, angle)
rot_sprite.get_rect().center = loc
return rot_sprite
# or return tuple: (Surface, Rect)
# return rot_sprite, rot_sprite.get_rect()
Found the problem: Example works good, but needs equal dimensions for width and height. Fixed pictures and it works.
Everything you need for drawing an image in pygame
game_display = pygame.display.set_mode((800, 600))
x = 0
y = 0
angle = 0
img = pygame.image.load("resources/image.png")
img = pygame.transform.scale(img, (50, 50)) # image size
def draw_img(self, image, x, y, angle):
rotated_image = pygame.transform.rotate(image, angle)
game_display.blit(rotated_image, rotated_image.get_rect(center=image.get_rect(topleft=(x, y)).center).topleft)
# run this method with your loop
def tick():
draw_img(img, x, y, angle)
I had to modify skrx solution as below, this way works for me.
angle=0
roll = true
while roll:
# clean surface with your background color
gameDisplay.fill(color)
self.image = yourImage
rotate_image = pygame.transform.rotate(self.image, angle)
rect = rotate_image.get_rect()
pos = (((your_surface_width - rect.width)/2),((your_surface_height - rect.height)/2))
gameDisplay.blit(rotate_image,pos)
pygame.display.flip()
angle+=2
if angle == 360:
roll=False
Related
How to inject Margin class into triangle class? [duplicate]
I have created an image's rectangle of a sprite using the command, pygame.image.get_rect(). When locating coordinates of key points on the rectangle such as bottomleft by drawing dots, I see that the rectangle's dimensions are completely different to the image's dimensions. In this case, the image is just the arrow head. The top right coordinate of the rectangle is correct, however the bottom left coordinate isn't. Test to see rectangle code to draw dots: pygame.draw.circle(simulation_screen, red, velocity_arrow.rect.topright, 2) pygame.draw.circle(simulation_screen,red,velocity_arrow.rect.bottomleft,2) How can I resize the rectangle so that it fits the image?
pygame.Surface.get_rect.get_rect() returns a rectangle with the size of the Surface object. This function does not consider the drawing area in the image. You can find the bounding rectangle of the painted area in the surface, with pygame.Surface.get_bounding_rect: bounding_rect = image.get_bounding_rect() bounding_rect.move_ip(target_rect.topleft) See the example. The black rectangle is the Surface rectangle and the red rectangle is the union of the mask component rectangles: repl.it/#Rabbid76/ImageHitbox import pygame pygame.init() window = pygame.display.set_mode((400, 400)) clock = pygame.time.Clock() try: my_image = pygame.image.load('icon/Bomb-256.png') except: my_image = pygame.Surface((200, 200), pygame.SRCALPHA) pygame.draw.circle(my_image, (0, 128, 0), (60, 60), 40) pygame.draw.circle(my_image, (0, 0, 128), (100, 150), 40) run = True while run: clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False pos = window.get_rect().center my_image_rect = my_image.get_rect(center = pos) bounding_rect = my_image.get_bounding_rect() bounding_rect.move_ip(my_image_rect.topleft) window.fill((255, 255, 255)) window.blit(my_image, my_image_rect) pygame.draw.rect(window, (0, 0, 0), my_image_rect, 3) pygame.draw.rect(window, (255, 0, 0), bounding_rect, 3) pygame.display.flip() pygame.quit() exit()
I have the same problem, and I think because of this line: image = pygame.transform.scale(image, (x, y)) After scaling the image in gimp, everything is working as intended.
image and rect of a image are not on top of each other, they are shifted. what is wrong
When I draw the image and the rect Wof the images, then the upper left corner of the rectangle is exactly in the center of the image. def blitRotateCenter(image, left, top, angle): rotated_image = pygame.transform.rotate(image, angle) new_rect = rotated_image.get_rect(center = image.get_rect(center = (left, top)).center) screen.blit(rotated_image, new_rect) self.image = pygame.image.load("Bilder/car.png") self.rect = self.image.get_rect() blitRotateCenter(auto.image, auto.rect.x, auto.rect.y, auto.wagen_winkel) draw.rect(screen,red,auto.rect)
Just return new_rect from blitRotateCenter and use it to draw the rectangle: def blitRotateCenter(image, left, top, angle): rotated_image = pygame.transform.rotate(image, angle) new_rect = rotated_image.get_rect(center = image.get_rect(center = (left, top)).center) screen.blit(rotated_image, new_rect) return new_rect new_auto_rect = blitRotateCenter(auto.image, auto.rect.x, auto.rect.y, auto.wagen_winkel) draw.rect(screen,red, new_auto_rect) However, if you want to draw a rotated rectangle, see Getting rotated rect of rotated image in Pygame.
How to use the pygame.Surface.scroll() for a particular blit() image [duplicate]
I want to move the image of a Rect object, is this possible? examples: 1 - make a waterfall with the water animated (make the water image scroll) 2 - adjust location of the image not the rect note: these are just examples not the code I am working on
You can shift the surface image in place with pygame.Surface.scroll. For instance, call water_surf.scroll(0, 1) However, this will not satisfy you. See pygame.Surface.scroll: Move the image by dx pixels right and dy pixels down. dx and dy may be negative for left and up scrolls respectively. Areas of the surface that are not overwritten retain their original pixel values. You may want to write a function that overwrites the areas wich are not overwritten, with the pixel that is scrolled out of the surface: def scroll_y(surf, dy): scroll_surf = surf.copy() scroll_surf.scroll(0, dy) if dy > 0: scroll_surf.blit(surf, (0, dy-surf.get_height())) else: scroll_surf.blit(surf, (0, surf.get_height()+dy)) return scroll_surf once per frame to create a water flow effect like a waterfall. To center an image in a rectangular area, you need to get the bounding rectangle of the image and set the center of the rectnagle through the center of the area. Use the rectangle to blit the image: area_rect = pygame.Rect(x, y, w, h) image_rect = surf.get_rect() image_rect.center = area_rect.center screen.blit(surf, image_rect) The same in one line: screen.blit(surf, surf.get_rect(center = area_rect.center)) Minimal example: repl.it/#Rabbid76/PyGame-SCroll import pygame def scroll_y(surf, dy): scroll_surf = surf.copy() scroll_surf.scroll(0, dy) if dy > 0: scroll_surf.blit(surf, (0, dy-surf.get_height())) else: scroll_surf.blit(surf, (0, surf.get_height()+dy)) return scroll_surf pygame.init() window = pygame.display.set_mode((400, 400)) clock = pygame.time.Clock() rain_surf = pygame.image.load('rain.png') dy = 0 run = True while run: clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: run = False window_center = window.get_rect().center scroll_surf = scroll_y(rain_surf, dy) dy = (dy + 1) % rain_surf.get_height() window.fill(0) window.blit(scroll_surf, scroll_surf.get_rect(center = window_center)) pygame.display.flip() pygame.quit() exit()
Pygame: How to rotate a rectangle clockwise/counterclockwise depending on input [duplicate]
This question already has answers here: How do I rotate an image around its center using Pygame? (6 answers) Closed 2 years ago. I have made one rectangle so far that follows the user's mouse and rotates counterclockwise with the a key and clockwise with the d key. Currently the rectangle follows the mouse but once the user starts to rotate the rectangle, the rectangle becomes very laggy and disfigured. I need help with keeping the rectangle the same and a constant FPS. Thank you for your help! Here is the code: import pygame as py # define constants WIDTH = 500 HEIGHT = 500 FPS = 200 # define colors BLACK = (0 , 0 , 0) GREEN = (0 , 255 , 0) # initialize pygame and create screen py.init() screen = py.display.set_mode((WIDTH , HEIGHT)) # for setting FPS clock = py.time.Clock() rot = 0 rot_speed = 2 # define a surface (RECTANGLE) image_orig = py.Surface((1 , 100)) # for making transparent background while rotating an image image_orig.set_colorkey(BLACK) # fill the rectangle / surface with green color image_orig.fill(GREEN) # creating a copy of orignal image for smooth rotation image = image_orig.copy() image.set_colorkey(BLACK) # define rect for placing the rectangle at the desired position rect = image.get_rect() x, y = py.mouse.get_pos() rect.center = (x, y) # keep rotating the rectangle until running is set to False running = True while running: x, y = py.mouse.get_pos() # set FPS clock.tick(FPS) # clear the screen every time before drawing new objects screen.fill(BLACK) # check for the exit for event in py.event.get(): if event.type == py.QUIT: running = False # making a copy of the old center of the rectangle old_center =(x, y) # defining angle of the rotation rot = (rot + rot_speed) % 360 # rotating the orignal image keys = py.key.get_pressed() rot_speed = .2 image_orig = py.transform.rotate(image_orig , 0) rect = image_orig.get_rect() # set the rotated rectangle to the old center rect.center = (x, y) # drawing the rotated rectangle to the screen screen.blit(image_orig , rect) # flipping the display after drawing everything py.display.flip() if(keys[py.K_a]): rot_speed = .2 image_orig = py.transform.rotate(image_orig , rot) rect = image_orig.get_rect() # set the rotated rectangle to the old center rect.center = (x, y) # drawing the rotated rectangle to the screen screen.blit(image_orig , rect) # flipping the display after drawing everything py.display.flip() if(keys[py.K_d]): rot_speed = -.2 image_orig = py.transform.rotate(image_orig , rot) rect = image_orig.get_rect() # set the rotated rectangle to the old center rect.center = (x, y) # drawing the rotated rectangle to the screen screen.blit(image_orig , rect) # flipping the display after drawing everything py.display.flip() rect.center = (x, y) py.quit()
The mayor issue is that you continuously rotate and manipulate the original image. That causes that the image gets distorted. See How do I rotate an image around its center using Pygame? Compute the new angle of the image: rot = (rot + rot_speed) % 360 Create a new image which is rotated around its center: image = py.transform.rotate(image_orig, rot) rect = image.get_rect(center = (x, y)) And blit the rotated image: screen.blit(image, rect) When A or D is pressed then the new direction is set by rot_speed = .2 respectively rot_speed = -.2. Full example code: import pygame as py # define constants WIDTH = 500 HEIGHT = 500 FPS = 200 # define colors BLACK = (0 , 0 , 0) GREEN = (0 , 255 , 0) # initialize pygame and create screen py.init() screen = py.display.set_mode((WIDTH , HEIGHT)) # for setting FPS clock = py.time.Clock() rot = 0 rot_speed = .2 # define a surface (RECTANGLE) image_orig = py.Surface((1 , 100)) # for making transparent background while rotating an image image_orig.set_colorkey(BLACK) # fill the rectangle / surface with green color image_orig.fill(GREEN) # creating a copy of orignal image for smooth rotation image = image_orig.copy() image.set_colorkey(BLACK) # define rect for placing the rectangle at the desired position rect = image.get_rect() x, y = py.mouse.get_pos() rect.center = (x, y) # keep rotating the rectangle until running is set to False running = True while running: x, y = py.mouse.get_pos() # set FPS clock.tick(FPS) # clear the screen every time before drawing new objects screen.fill(BLACK) # check for the exit for event in py.event.get(): if event.type == py.QUIT: running = False # rotating the orignal image keys = py.key.get_pressed() if keys[py.K_a]: rot_speed = .2 if keys[py.K_d]: rot_speed = -.2 # defining angle of the rotation rot = (rot + rot_speed) % 360 # rotating the orignal image image = py.transform.rotate(image_orig, rot) rect = image.get_rect(center = (x, y)) # drawing the rotated rectangle to the screen screen.blit(image, rect) # flipping the display after drawing everything py.display.flip() py.quit()
How to set the pivot point (center of rotation) for pygame.transform.rotate()?
I want to rotate a rectangle about a point other than the center. My code so far is: import pygame pygame.init() w = 640 h = 480 degree = 45 screen = pygame.display.set_mode((w, h)) surf = pygame.Surface((25, 100)) surf.fill((255, 255, 255)) surf.set_colorkey((255, 0, 0)) bigger = pygame.Rect(0, 0, 25, 100) pygame.draw.rect(surf, (100, 0, 0), bigger) rotatedSurf = pygame.transform.rotate(surf, degree) screen.blit(rotatedSurf, (400, 300)) running = True while running: event = pygame.event.poll() if event.type == pygame.QUIT: running = False pygame.display.flip() I can change the degree to get different rotation but the rotation is about the center. I want to set a point other than the center of the rectangle as the rotation point.
To rotate a surface around its center, we first rotate the image and then get a new rect to which we pass the center coordinates of the previous rect to keep it centered. To rotate around an arbitrary point, we can do pretty much the same, but we also have to add an offset vector to the center position (the pivot point) to shift the rect. This vector needs to be rotated each time we rotate the image. So we have to store the pivot point (the original center of the image or sprite) - in a tuple, list, vector or a rect - and the offset vector (the amount by which we shift the rect) and pass them to the rotate function. Then we rotate the image and offset vector, get a new rect, pass the pivot + offset as the center argument and finally return the rotated image and the new rect. import pygame as pg def rotate(surface, angle, pivot, offset): """Rotate the surface around the pivot point. Args: surface (pygame.Surface): The surface that is to be rotated. angle (float): Rotate by this angle. pivot (tuple, list, pygame.math.Vector2): The pivot point. offset (pygame.math.Vector2): This vector is added to the pivot. """ rotated_image = pg.transform.rotozoom(surface, -angle, 1) # Rotate the image. rotated_offset = offset.rotate(angle) # Rotate the offset vector. # Add the offset vector to the center/pivot point to shift the rect. rect = rotated_image.get_rect(center=pivot+rotated_offset) return rotated_image, rect # Return the rotated image and shifted rect. pg.init() screen = pg.display.set_mode((640, 480)) clock = pg.time.Clock() BG_COLOR = pg.Color('gray12') # The original image will never be modified. IMAGE = pg.Surface((140, 60), pg.SRCALPHA) pg.draw.polygon(IMAGE, pg.Color('dodgerblue3'), ((0, 0), (140, 30), (0, 60))) # Store the original center position of the surface. pivot = [200, 250] # This offset vector will be added to the pivot point, so the # resulting rect will be blitted at `rect.topleft + offset`. offset = pg.math.Vector2(50, 0) angle = 0 running = True while running: for event in pg.event.get(): if event.type == pg.QUIT: running = False keys = pg.key.get_pressed() if keys[pg.K_d] or keys[pg.K_RIGHT]: angle += 1 elif keys[pg.K_a] or keys[pg.K_LEFT]: angle -= 1 if keys[pg.K_f]: pivot[0] += 2 # Rotated version of the image and the shifted rect. rotated_image, rect = rotate(IMAGE, angle, pivot, offset) # Drawing. screen.fill(BG_COLOR) screen.blit(rotated_image, rect) # Blit the rotated image. pg.draw.circle(screen, (30, 250, 70), pivot, 3) # Pivot point. pg.draw.rect(screen, (30, 250, 70), rect, 1) # The rect. pg.display.set_caption('Angle: {}'.format(angle)) pg.display.flip() clock.tick(30) pg.quit() Here's a version with a pygame.sprite.Sprite: import pygame as pg from pygame.math import Vector2 class Entity(pg.sprite.Sprite): def __init__(self, pos): super().__init__() self.image = pg.Surface((122, 70), pg.SRCALPHA) pg.draw.polygon(self.image, pg.Color('dodgerblue1'), ((1, 0), (120, 35), (1, 70))) # A reference to the original image to preserve the quality. self.orig_image = self.image self.rect = self.image.get_rect(center=pos) self.pos = Vector2(pos) # The original center position/pivot point. self.offset = Vector2(50, 0) # We shift the sprite 50 px to the right. self.angle = 0 def update(self): self.angle += 2 self.rotate() def rotate(self): """Rotate the image of the sprite around a pivot point.""" # Rotate the image. self.image = pg.transform.rotozoom(self.orig_image, -self.angle, 1) # Rotate the offset vector. offset_rotated = self.offset.rotate(self.angle) # Create a new rect with the center of the sprite + the offset. self.rect = self.image.get_rect(center=self.pos+offset_rotated) def main(): screen = pg.display.set_mode((640, 480)) clock = pg.time.Clock() entity = Entity((320, 240)) all_sprites = pg.sprite.Group(entity) while True: for event in pg.event.get(): if event.type == pg.QUIT: return keys = pg.key.get_pressed() if keys[pg.K_d]: entity.pos.x += 5 elif keys[pg.K_a]: entity.pos.x -= 5 all_sprites.update() screen.fill((30, 30, 30)) all_sprites.draw(screen) pg.draw.circle(screen, (255, 128, 0), [int(i) for i in entity.pos], 3) pg.draw.rect(screen, (255, 128, 0), entity.rect, 2) pg.draw.line(screen, (100, 200, 255), (0, 240), (640, 240), 1) pg.display.flip() clock.tick(30) if __name__ == '__main__': pg.init() main() pg.quit()
I also had this problem and found an easy solution: You can just create a bigger surface (doubled length and doubled height) and blit the smaller surface into the bigger so that the rotation point of is the center of the bigger one. Now you can just rotate the bigger one around the center. def rotate(img, pos, angle): w, h = img.get_size() img2 = pygame.Surface((w*2, h*2), pygame.SRCALPHA) img2.blit(img, (w-pos[0], h-pos[1])) return pygame.transform.rotate(img2, angle) (If you would make sketch, it would make much more sense, but trust me: It works and is in my opinion easy to use and to understand than the other solutions.)
I agree with MegaIng and skrx. But I also have to admit that I could not truly grasp the concept after reading their answers. Then I found this game-tutorial regarding a rotating canon over its edge. After running it, I still had questions in mind, but then I found out that the image that they have used for cannon was a part of the trick. The image was not centered around its cannon's center, it was centered around the pivot point and the other half of the image was transparent. After that epiphany I applied the same to the legs of my bugs and they all work just fine now. Here is my rotation code: def rotatePivoted(im, angle, pivot): # rotate the leg image around the pivot image = pygame.transform.rotate(im, angle) rect = image.get_rect() rect.center = pivot return image, rect Hope this helps!
Rotating an image around a pivot on the image, which is anchored to a point in the world (origin), can be achieved with the following function: def blitRotate(surf, image, origin, pivot, angle): image_rect = image.get_rect(topleft = (origin[0] - pivot[0], origin[1]-pivot[1])) offset_center_to_pivot = pygame.math.Vector2(origin) - image_rect.center rotated_offset = offset_center_to_pivot.rotate(-angle) rotated_image_center = (origin[0] - rotated_offset.x, origin[1] - rotated_offset.y) rotated_image = pygame.transform.rotate(image, angle) rotated_image_rect = rotated_image.get_rect(center = rotated_image_center) surf.blit(rotated_image, rotated_image_rect) Explanation: 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: image_rect = image.get_rect(topleft = (origin[0] - pivot[0], origin[1]-pivot[1])) offset_center_to_pivot = pygame.math.Vector2(origin) - image_rect.center Rotate the offset vector the same angle you want to rotate the image: rotated_offset = offset_center_to_pivot.rotate(-angle) Calculate the new center point of the rotated image by subtracting the rotated offset vector from the pivot point in the world: rotated_image_center = (origin[0] - rotated_offset.x, origin[1] - rotated_offset.y) Rotate the image and set the center point of the rectangle enclosing the rotated image. Finally blit the image : rotated_image = pygame.transform.rotate(image, angle) rotated_image_rect = rotated_image.get_rect(center = rotated_image_center) surf.blit(rotated_image, rotated_image_rect) See also Rotate surface Minimal example: repl.it/#Rabbid76/PyGame-RotateSpriteAroundOffCenterPivotCannon import pygame class SpriteRotate(pygame.sprite.Sprite): def __init__(self, imageName, origin, pivot): super().__init__() self.image = pygame.image.load(imageName) self.original_image = self.image self.rect = self.image.get_rect(topleft = (origin[0]-pivot[0], origin[1]-pivot[1])) self.origin = origin self.pivot = pivot self.angle = 0 def update(self): image_rect = self.original_image.get_rect(topleft = (self.origin[0] - self.pivot[0], self.origin[1]-self.pivot[1])) offset_center_to_pivot = pygame.math.Vector2(self.origin) - image_rect.center rotated_offset = offset_center_to_pivot.rotate(-self.angle) rotated_image_center = (self.origin[0] - rotated_offset.x, self.origin[1] - rotated_offset.y) self.image = pygame.transform.rotate(self.original_image, self.angle) self.rect = self.image.get_rect(center = rotated_image_center) pygame.init() size = (400,400) screen = pygame.display.set_mode(size) clock = pygame.time.Clock() cannon = SpriteRotate('cannon.png', (200, 200), (33.5, 120)) cannon_mount = SpriteRotate('cannon_mount.png', (200, 200), (43, 16)) all_sprites = pygame.sprite.Group([cannon, cannon_mount]) angle_range = [-90, 0] angle_step = -1 frame = 0 done = False while not done: clock.tick(60) for event in pygame.event.get(): if event.type == pygame.QUIT: done = True all_sprites.update() screen.fill((64, 128, 255)) pygame.draw.rect(screen, (127, 127, 127), (0, 250, 400, 150)) all_sprites.draw(screen) pygame.display.flip() frame += 1 cannon.angle += angle_step if not angle_range[0] < cannon.angle < angle_range[1]: angle_step *= -1 pygame.quit() exit() See alos Rotating and scaling an image around a pivot, while scaling width and height separately in Pygame
I think you have to make a function of your own for that. If you make a Vector class it's much easier. Maybe something like: def rotate(surf, angle, pos): pygame.transform.rotate(surf, angle) rel_pos = surf.blit_pos.sub(pos) new_rel_pos = rel_pos.set_angle(rel_pos.get_angle() + angle) surf.blit_pos = pos.add(new_rel_pos) So there you have it.The only thing you have to do is the Vector class with the methods 'add()', 'sub()', 'get_angle()' and 'set_angle()'. If you are strugling just google for help. In the end you'll end up with a nice Vector class that you can expand and use in other projects.