如何使用 Pygame 围绕图像中心旋转图像?
- 2024-11-27 10:43:00
- admin 原创
- 187
问题描述:
我一直试图在使用中围绕图像中心旋转图像,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
解决方案 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()
另请参阅旋转曲面和问题的答案:
如何在 Pygame 中围绕偏离中心的枢轴旋转图像
如何让图像围绕其中心旋转并且其比例越来越大(在 Pygame 中)
如何将图像(播放器)旋转至鼠标方向?
如何设置 pygame.transform.rotate() 的枢轴点(旋转中心)?
如何在 pygame 中将枪管指向鼠标?
解决方案 2:
最佳答案存在一些问题:函数中需要提供前一个矩形的位置,以便我们可以将其分配给新的矩形,例如:
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()
解决方案 3:
您正在删除旋转创建的矩形。您需要保留矩形,因为它在旋转时会改变大小。
如果您想保留对象的位置,请执行以下操作:
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()
解决方案 4:
绘制图像所需的一切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)
解决方案 5:
发现问题:示例运行良好,但需要宽度和高度相等的尺寸。修复图片后即可运行。
解决方案 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
解决方案 7:
旋转图像时的另一个重要点是图像必须具有 alpha 通道。许多人使用矩形、均匀填充的表面来测试他们的代码,而忘记了 alpha 通道。这会导致看起来像是随机拉伸的矩形。如何创建具有 alpha 通道的表面在问题的答案中进行了解释:pygame 不旋转表面。在示例中,我pygame.Surface
使用pygame.SRCALPHA
标志创建了以获取具有 alpha 通道的图像:
surface = pygame.Surface((100, 50), pygame.SRCAPLHA)
第二件重要的事情是,你绝不能pygame.Surface
反复旋转一个物体,因为这会导致物体不断增加pygame.Surface
和扭曲,但你必须保存原始表面并始终从中创建旋转表面pygame.Surface
。请参阅以下我使用pygame.sprite
模块的示例:
import pygame
class Player(pygame.sprite.Sprite):
def __init__(self, image, x, y):
pygame.sprite.Sprite.__init__(self)
self.original_image = image
self.image = self.original_image
self.rect = self.image.get_rect(center = (x, y))
self.angle = 0
def update(self):
self.image = pygame.transform.rotate(self.original_image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
self.angle = (self.angle + 1) % 360
pygame.init()
window = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
surface = pygame.Surface((100, 50), pygame.SRCALPHA)
surface.fill("black")
player = Player(surface, *window.get_rect().center)
all_sprites = pygame.sprite.Group(player)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
all_sprites.update()
window.fill((255, 255, 255))
all_sprites.draw(window)
pygame.display.flip()
pygame.quit()
exit()