简体   繁体   English

如何为pygame.transform.rotate()设置pivot点(旋转中心)?

[英]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. 要围绕其中心旋转曲面,我们首先旋转图像,然后获得一个新的矩形,我们通过前一个矩形的center坐标使其保持居中。 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. 所以我们必须存储枢轴点(图像或精灵的原始中心) - 在元组,列表,向量或矩形中 - 以及偏移向量(我们移动矩形的量)并将它们传递给rotate功能。 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. 然后我们旋转图像和偏移矢量,得到一个新的矩形,将枢轴+偏移作为center参数传递,最后返回旋转的图像和新的矩形。

在此输入图像描述

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 : 这是一个带有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()

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:围绕图像上的pivot旋转图像,锚定到世界中的一个点( origin ),可以通过以下 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 .矢量可以用pygame.math.Vector2表示,可以用pygame.math.Vector2.rotate旋转。 Notice that pygame.math.Vector2.rotate rotates in the opposite direction than pygame.transform.rotate .请注意pygame.math.Vector2.rotate的旋转方向与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:计算从图像中心到图像上的 pivot 的偏移向量:

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:世界上的pivot点减去旋转后的偏移向量,计算出旋转后图像的新中心点:

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:最后 blit 图像:

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 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 围绕pivot旋转和缩放图像,同时在Pygame中分别缩放宽度和高度
围绕枢轴旋转和缩放图像,同时在 Pygame 中分别缩放宽度和高度围绕枢轴旋转和缩放图像,同时在 Pygame 中分别缩放宽度和高度

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. 我也有这个问题,并找到了一个简单的解决方案:你可以创建一个更大的表面(加倍长度和加倍高度)并将较小的表面blit到较大的表面,以便旋转点是较大的表面的中心。 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. 我同意MegaIng和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. 在运行它之后,我仍然有问题,但后来我发现他们用于加农炮的图像是这个技巧的一部分。

PNG加农炮图像以其枢轴为中心。

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! 希望这可以帮助!

I think you have to make a function of your own for that. 我认为你必须为此做一个自己的功能。

If you make a Vector class it's much easier. 如果你创建一个Vector类,那就容易多了。

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()'. 所以你有它。你唯一需要做的就是Vector类的方法'add()','sub()','get_angle()'和'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. 最后,您将得到一个很好的Vector类,您可以在其他项目中进行扩展和使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM