如何使用 Pygame 围绕其中心旋转图像?

新手上路,请多包涵

我一直在尝试使用 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

原文由 Miha 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
1 个回答

简短回答:

当您使用 pygame.transform.rotate 时,与原始图像的大小相比,新旋转图像的大小增加了。您必须确保放置旋转图像,使其中心保持在未旋转图像的中心。为此,获取原始图像的矩形并设置位置。获取旋转图像的矩形,通过原矩形的中心设置中心位置。

从函数 red_center 返回一个元组,带有旋转图像和旋转图像的边界矩形:

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

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

这可以通过比较旋转前后图像的 轴对齐边界框 来补偿。

对于以下数学 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 的左上角)

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

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

这意味着, --- 的第二个参数 ( pos blitRotate 是窗口中轴心点的位置,第三个参数 ( 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()


另见 旋转曲面 和问题的答案:

原文由 Rabbid76 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题