'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.
Solution 1:[1]
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()
Solution 2:[2]
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.)
Solution 3:[3]
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!
Solution 4:[4]
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
Solution 5:[5]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | MegaIng |
Solution 3 | Bedir Yilmaz |
Solution 4 | |
Solution 5 | alexpinho98 |