【问题标题】:How do I rotate an image around its center using Pygame?如何使用 Pygame 围绕其中心旋转图像?
【发布时间】:2026-01-15 11:40:01
【问题描述】:

我一直在尝试使用pygame.transform.rotate() 围绕其中心旋转图像,但它不起作用。具体来说,挂起的部分是rot_image = rot_image.subsurface(rot_rect).copy()。我得到了例外:

ValueError: subsurface rectangle outside surface area

这是用于旋转图像的代码:

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

【问题讨论】:

    标签: python rotation pygame


    【解决方案1】:

    简答:

    获取原始图像的矩形并设置位置。获取旋转图像的矩形,并通过原始矩形的中心设置中心位置。返回旋转图像和矩形的元组:

    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
    

    或者写一个函数来旋转和.blit图片:

    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)
    

    长答案:

    图像 (pygame.Surface) 可以旋转 pygame.transform.rotate

    如果在循环中逐步完成,则图像会失真并迅速增加:

    while not done:
    
        # [...]
    
        image = pygame.transform.rotate(image, 1)
        screen.blit(image, pos)
        pygame.display.flip()
    

    这是因为旋转图像的边界矩形总是大于原始图像的边界矩形(某些旋转 90 度的倍数除外)。
    由于多重副本,图像会变形。每次旋转都会产生一个小错误(不准确)。错误的总和在增加,图像在衰减。

    这可以通过保留原始图像并“blit”由原始图像的单个旋转操作生成的图像来解决。

    angle = 0
    while not done:
    
        # [...]
    
        rotated_image = pygame.transform.rotate(image, angle)
        angle += 1
    
        screen.blit(rotated_image, pos)
        pygame.display.flip()
    

    现在图像似乎可以随意改变它的位置,因为图像的大小会随着旋转而改变,并且原点始终是图像边界矩形的左上角。

    这可以通过比较旋转前后图像的axis aligned bounding box来补偿。
    对于以下数学,使用pygame.math.Vector2。请注意屏幕坐标中的 y 指向屏幕下方,但数学上的 y 轴点从底部到顶部形成。这导致在计算过程中必须“翻转”y轴

    用边界框的4个角点建立一个列表:

    w, h = image.get_size()
    box = [pygame.math.Vector2(p) for p in [(0, 0), (w, 0), (w, -h), (0, -h)]]
    

    将向量旋转到pygame.math.Vector2.rotate的角点:

    box_rotate = [p.rotate(angle) for p in box]
    

    获取旋转点的最小值和最大值:

    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])
    

    通过将旋转框的最小值添加到位置来计算图像左上角点的“补偿”原点。对于 y 坐标 max_box[1] 是最小值,因为沿 y 轴“翻转”:

    origin = (pos[0] + min_box[0], pos[1] - max_box[1])
    
    rotated_image = pygame.transform.rotate(image, angle)
    screen.blit(rotated_image, origin)
    

    甚至可以在原始图像上定义一个轴。计算从图像中心到枢轴的偏移向量并旋转向量。向量可以用pygame.math.Vector2 表示,也可以用pygame.math.Vector2.rotate 旋转。请注意,pygame.math.Vector2.rotate 的旋转方向与 pygame.transform.rotate 的旋转方向相反。因此必须反转角度:

    计算从图像中心到枢轴的向量:

    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
    

    旋转矢量

    rotated_offset = offset_center_to_pivot.rotate(-angle)
    

    计算旋转图像的中心:

    rotated_image_center = (pos[0] - rotated_offset.x, pos[1] - rotated_offset.y)
    

    旋转和blit图像:

    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)
    

    在下面的示例程序中,函数blitRotate(surf, image, pos, originPos, angle) 执行上述所有步骤并将旋转的图像“blit”到表面。

    • surf 是目标表面

    • image 是必须旋转的表面,blit

    • pos 是目标 Surface surf 上的枢轴位置(相对于surf 的左上角)

    • originPosimage 表面上的枢轴位置(相对于image 的左上角)

    • angle 是以度为单位的旋转角度

    这意味着,blitRotate 的第二个参数 (pos) 是窗口中枢轴点的位置,第三个参数 (originPos) 是旋转 上的枢轴点位置表面


    最小示例: 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()
    

    另请参阅Rotate surface 以及问题的答案:

    【讨论】:

    • 超出了原始答案的范围,但绝对是我想要的。我喜欢它超出中心并且可以围绕提供点旋转。在做得比要求的更多方面反应很好。
    【解决方案2】:

    您正在删除旋转创建的矩形。您需要保留矩形,因为它在旋转时会改变大小。

    如果您想保留对象位置,请执行以下操作:

    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()
    

    【讨论】:

      【解决方案3】:

      上面的回答有些问题:函数中需要有前一个rect的位置,这样我们才能赋值给新的rect,例如:

      rect = new_image.get_rect(center=rect.center) 
      

      在另一个答案中,位置是通过从原始图像创建一个新的矩形来获得的,但这意味着它将定位在默认的 (0, 0) 坐标处。

      下面的示例应该可以正常工作。新的矩形需要旧矩形的center 位置,所以我们也将它传递给函数。然后旋转图像,调用get_rect 以获取具有正确大小的新矩形并将旧矩形的center 属性作为center 参数传递。最后,将旋转后的图像和新的矩形作为元组返回,并在主循环中解包。

      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()
      

      这是另一个旋转 pygame 精灵的示例。

      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()
      

      【讨论】:

      • 我正在使用你的方法,它似乎也有一些问题。它在屏幕的左上角显示我的图像(它仍然在中心正确旋转,万岁),但我不希望它在那里,并且发生了一些奇怪的事情(位置相同,但目的地不同)。我无法在这里全部解释,我可能会就此提出问题并附上此问题的链接和您的答案。
      • @Ethanol 如果没有看到您的代码,我会情不自禁。上面的例子对我来说是正确的。
      • 实际上,只是一个可能对我有帮助的快速问题:为什么您将(320, 240) 在这个rect = image.get_rect(center=(320, 240)) 中分配给中心?它有什么作用,如果删除它会对代码产生什么影响?
      • 我正在那里设置rect 的初始位置。矩形将具有imagecenter 坐标(320, 240) 的大小。其他坐标如topleft(即实际 blit 位置)将相应调整。当图像被旋转并且你再次调用get_rect时,矩形的大小会有所不同,但是通过将中心坐标设置为之前的中心坐标,我们也会自动更新左上角位置,这样图像就会被blitted在正确的位置。
      • @Ethanol 你明白了吗?如果是,我们可以在这里删除 cmets,我会在答案中添加一些额外的信息。
      【解决方案4】:

      发现问题:示例效果很好,但宽度和高度需要相等的尺寸。修复了图片,它可以工作。

      【讨论】:

        【解决方案5】:

        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)
        

        【讨论】:

          【解决方案6】:

          我必须如下修改 skrx 解决方案,这种方式对我有用。

          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 
          

          【讨论】: