如何在 Pygame 中围绕偏离中心的支点旋转图像

Rab*_*d76 4 python pygame

我想围绕一个支点,这是不是在中心旋转图像表面pygame的
枢轴是图像中的绿色十字:

在此处输入图片说明

我知道游戏窗口中枢轴的位置。我如何在此时跟踪图像并同时围绕该点旋转它。

image = pygame.image.load("boomerang64.png")
pos = (200, 200)
angle = 0

while True:
    # [...]

    rotate_rect, rotate_image = ???? # rotate around green cross by angle 
    surf.blit(rotated_image, rotate_rect)
    angle += 1

    # [...]
Run Code Online (Sandbox Code Playgroud)

Rab*_*d76 9

首先Surface必须定义枢轴在 上的位置:

image = pygame.image.load("boomerang64.png") # 64x64 surface
pivot = (48, 21)                             # position of the pivot on the image
Run Code Online (Sandbox Code Playgroud)

旋转图像时,其大小会增加。我们必须比较旋转前和旋转后图像的轴对齐边界框
对于以下数学pygame.math.Vector2使用。注意屏幕坐标中的 y 指向屏幕下方,但数学 y 轴点从底部到顶部。这会导致在计算过程中必须“翻转”y 轴

使用边界框的 4 个角点设置一个列表,并将向量旋转到角点pygame.math.Vector2.rotate。最后找到旋转框的最小值。由于 pygame 中的 y 轴指向下方,因此必须通过找到旋转倒置高度的最大值(max(rotate(-h)) ”)来补偿这一点:

w, h = image.get_size()
box = [pygame.math.Vector2(p) for p in [(0, 0), (w, 0), (w, -h), (0, -h)]]
box_rotate = [p.rotate(angle) for p in box]
min_x, min_y = min(box_rotate, key=lambda p: p[0])[0], max(box_rotate, key=lambda p: p[1])[1]
Run Code Online (Sandbox Code Playgroud)

可以通过三角函数直接计算旋转向量的 x 和 y 分量来改进min_x和的计算:min_y

w, h         = image.get_size()
sin_a, cos_a = math.sin(math.radians(angle)), math.cos(math.radians(angle)) 
min_x, min_y = min([0, sin_a*h, cos_a*w, sin_a*h + cos_a*w]), max([0, sin_a*w, -cos_a*h, sin_a*w - cos_a*h])
Run Code Online (Sandbox Code Playgroud)

通过将旋转框的最小值添加到与图像上的枢轴相关的位置,计算图像左上点的“补偿”原点:

origin = (pos[0] - originPos[0] + min_x - pivot_move[0], pos[1] - originPos[1] - min_y + pivot_move[1])
rotated_image = pygame.transform.rotate(image, angle)
screen.blit(rotated_image, origin)
Run Code Online (Sandbox Code Playgroud)

在以下示例程序中,该函数blitRotate(surf, image, pos, originPos, angle)执行上述所有步骤并将blit旋转图像发送到与显示器关联的 Surface:

  • surf 是目标曲面
  • image 是必须旋转的表面和 blit
  • pos是目标 Surface 上的枢轴位置surf(相对于 的左上角surf
  • originPosimage表面上枢轴的位置(相对于 的左上角image
  • angle 是以度为单位的旋转角度

import pygame
import math

def blitRotate(surf, image, pos, originPos, angle):

    # calcaulate the axis aligned bounding box of the rotated image
    w, h         = image.get_size()
    sin_a, cos_a = math.sin(math.radians(angle)), math.cos(math.radians(angle)) 
    min_x, min_y = min([0, sin_a*h, cos_a*w, sin_a*h + cos_a*w]), max([0, sin_a*w, -cos_a*h, sin_a*w - cos_a*h])

    # calculate the translation of the pivot 
    pivot        = pygame.math.Vector2(originPos[0], -originPos[1])
    pivot_rotate = pivot.rotate(angle)
    pivot_move   = pivot_rotate - pivot

    # calculate the upper left origin of the rotated image
    origin = (pos[0] - originPos[0] + min_x - pivot_move[0], pos[1] - originPos[1] - min_y + pivot_move[1])

    # get a rotated image
    rotated_image = pygame.transform.rotate(image, angle)

    # rotate and blit the image
    surf.blit(rotated_image, origin)
  
pygame.init()
size = (400,400)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()

image = pygame.image.load('boomerang64.png')
pivot = (48, 21)

angle, frame = 0, 0
done = False
while not done:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True
    screen.fill(0)

    pos = (200 + math.cos(frame * 0.05)*100, 200 + math.sin(frame * 0.05)*50)
    blitRotate(screen, image, pos, pivot, angle)

    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.display.flip()
    frame += 1
    angle += 10
    
pygame.quit()
Run Code Online (Sandbox Code Playgroud)

同样的算法也可以用于Sprite
在这种情况下,位置 ( self.pos)、枢轴 ( self.pivot) 和角度 ( self.angle) 是类的实例属性。在update方法中计算self.rectself.image属性。

最小的例子: repl.it/@Rabbid76/PyGame-RotateSpriteAroundOffCenterPivot

import pygame
import math

class SpriteRotate(pygame.sprite.Sprite):

    def __init__(self, imageName, pos, pivot):
        super().__init__() 
        self.image = pygame.image.load(imageName)
        self.original_image = self.image
        self.rect = self.image.get_rect(topleft = (pos[0]-pivot[0], pos[1]-pivot[1]))
        self.pos   = pos
        self.pivot = pivot
        self.angle = 0

    def update(self):

        # calcaulate the axis aligned bounding box of the rotated image
        w, h         = self.original_image.get_size()
        sin_a, cos_a = math.sin(math.radians(self.angle)), math.cos(math.radians(self.angle)) 
        min_x, min_y = min([0, sin_a*h, cos_a*w, sin_a*h + cos_a*w]), max([0, sin_a*w, -cos_a*h, sin_a*w - cos_a*h])

        # calculate the translation of the pivot 
        pivot        = pygame.math.Vector2(self.pivot[0], -self.pivot[1])
        pivot_rotate = pivot.rotate(self.angle)
        pivot_move   = pivot_rotate - pivot

        # calculate the upper left origin of the rotated image
        origin = (self.pos[0] - self.pivot[0] + min_x - pivot_move[0], self.pos[1] - self.pivot[1] - min_y + pivot_move[1])

        # get a rotated image
        self.image  = pygame.transform.rotate(self.original_image, self.angle)
        self.rect   = self.image.get_rect(topleft = (round(origin[0]), round(origin[1])))
        self.angle += 10
  
pygame.init()
size = (400,400)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()

boomerang = SpriteRotate('boomerang64.png', (200, 200), (48, 21))
all_sprites = pygame.sprite.Group(boomerang)

frame = 0
done = False
while not done:
    clock.tick(60)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

    pos = (200 + math.cos(frame * 0.05)*100, 200 + math.sin(frame * 0.05)*50)
    boomerang.pos = pos
    all_sprites.update()

    screen.fill(0)
    all_sprites.draw(screen)
    pygame.display.flip()
    frame += 1

pygame.quit()
Run Code Online (Sandbox Code Playgroud)

如何使用 Pygame 围绕其中心旋转图像?
如何在图像的比例变大时围绕其中心旋转图像(在 Pygame 中)