如何使用 pytmx 正确显示旋转对象?
How to correctly display rotation object with pytmx?
经过多次研究我没有找到答案,当我试图用 pytmx 在 pygame 中显示对象时,结果完全损坏,因为 x,y 随旋转而变化。我曾尝试使用矩阵旋转,但为此,我需要知道原始中心。不知道怎么找,因为Tiled在旋转后发给我,x,y...
所以我的目标只是用 pytmx 在 pygame 中显示对象图块。
import numpy
import math
angle = math.radians(-117.57) #rotation get with tiled, set - for cancel rotation
center_x = 148 #how to get this ?
center_y = 747 #and this
x = 126.82 #get with tiled
y = 679.54 #get with tiled
id_rotation = [ [math.cos(angle), -math.sin(angle)],
[math.sin(angle), math.cos(angle)] ]
R = numpy.matrix(id_rotation)
id_position = [ [x - center_x],
[y - center_y] ]
B = numpy.matrix(id_position)
id_center = [ [center_x],
[center_y] ]
C = numpy.matrix(id_center)
print(numpy.dot(R, B) + C) #return original position before rotation
如果我只用pygame.transform.rotate:
if isinstance(layer, pytmx.TiledObjectGroup):
for object in layer:
if (object.image):
assets_surface = pygame.Surface((object.width, object.height), pygame.SRCALPHA)
assets_surface.blit(object.image, (0, 0))
assets_surface_rotate = pygame.transform.rotate(assets_surface, -object.rotation)
rdc.blit(assets_surface_rotate, (object.x, object.y))
我得到的图块对象的 x,y 位置错误:
我认为您在旋转后传递对象的 x 和 y 位置时犯了一些错误。我没用过tile map所以不知道具体情况,但是在pygame中把位置传给blit
的时候应该传top-left角的坐标。 Surface
的 top-left 角将位于这些坐标处。
rdc.blit(assets_surface_rotate, (object.x, object.y))
这里我不知道坐标 object.x
和 object.y
是什么,但我敢打赌它们不是 top-left 角,或者你的代码应该可以工作。
一般来说,做这些工作你可以使用 Sprite class 或 subclass,这会有很大帮助。
class TMSprite(pygame.sprite.Sprite):
# Constructor. Create a Surface from a TileMap and set its position
def __init__(self, tmo, x, y, width, height):
# Call the parent class (Sprite) constructor
super(TMSprite, self).__init__()
# Create the image of the block
self.image = pygame.Surface((width, height), pygame.SRCALPHA)
self.image.blit(tmo.image, (0, 0))
# Fetch the rectangle object that has the dimensions of the image
# Set its position with the move method
self.rect = self.image.get_rect().move(x, y)
def rotate(self, angle):
# TMSprite rotation on its center of a given angle
rot_center = self.rect.center
self.image = pygame.transform.rotate(self.image, angle)
self.rect = self.image.get_rect()
self.rect.center = rot_center
这就是使用 TMSprite
class.
重写代码片段的方法
if isinstance(layer, pytmx.TiledObjectGroup):
for tmob in layer:
if (tmob.image):
x = tmob.x #x should be that of the top-left corner. Adjust the formula if tmob.x is not the top-left
y = tmob.y #y should be that of the top-left corner. Adjust the formula if tmob.y is not the top-left
assets_sprite = TMSprite(tmob, x, y, tmob.width, tmob.height)
assets_sprite.rotate(-object.rotation)
rdc.blit(assets_sprite.image, assets_sprite.rect)
在这里,我没有传递 top-left 坐标,而是将精灵的 Rect
传递给 blit
。 blit
方法将从矩形中提取坐标。
请注意,旋转是在 Surface 的 centre 上执行的。旋转后,如果角度不是 90° 的倍数,则 Surface 会放大,因为 Surface 的正方形必须与屏幕对齐。如果有alpha通道没有问题,多余的像素是透明的,但是top-left角会变。
好的,如果有人需要,我已经找到了解决方案:
elif isinstance(layer, pytmx.TiledObjectGroup):
for Object in layer:
if (Object.image):
if Object.rotation != 0:
angle = math.radians(-Object.rotation)
center_x = Object.centerX
center_y = Object.centerY
Object_y = Object.y + Object.height
id_rotation = [ [math.cos(angle), -math.sin(angle)],
[math.sin(angle), math.cos(angle)] ]
R = numpy.matrix(id_rotation)
id_position = [ [Object.x - center_x],
[Object_y - center_y] ]
P = numpy.matrix(id_position)
id_center = [ [center_x],
[center_y] ]
C = numpy.matrix(id_center)
position_without_rotation = numpy.dot(R, P) + C
no_rotation_x = position_without_rotation[0]
no_rotation_y = position_without_rotation[1] - Object.height #Repere Tiled pas le meme que Pygame
Object_surface = pygame.Surface((Object.image.get_rect()[2], Object.image.get_rect()[3]), pygame.SRCALPHA)
Object_surface.blit(Object.image, (0, 0))
Object_surface_scale = pygame.transform.scale(Object_surface, (round(Object.width), round(Object.height)))
Object_surface_rotate = pygame.transform.rotate(Object_surface_scale, -Object.rotation) #Pygame va en anti horaire
extra_x = (Object_surface_rotate.get_rect()[2] - Object.width) / 2
extra_y = (Object_surface_rotate.get_rect()[3] - Object.height) / 2
rdc.blit(Object_surface_rotate, (no_rotation_x - extra_x, no_rotation_y - extra_y))
else:
Object_surface = pygame.Surface((Object.image.get_rect()[2], Object.image.get_rect()[3]), pygame.SRCALPHA)
Object_surface.blit(Object.image, (0, 0))
Object_surface_scale = pygame.transform.scale(Object_surface, (round(Object.width), round(Object.height)))
rdc.blit(Object_surface_scale, (Object.x, Object.y))
经过多次研究我没有找到答案,当我试图用 pytmx 在 pygame 中显示对象时,结果完全损坏,因为 x,y 随旋转而变化。我曾尝试使用矩阵旋转,但为此,我需要知道原始中心。不知道怎么找,因为Tiled在旋转后发给我,x,y...
所以我的目标只是用 pytmx 在 pygame 中显示对象图块。
import numpy
import math
angle = math.radians(-117.57) #rotation get with tiled, set - for cancel rotation
center_x = 148 #how to get this ?
center_y = 747 #and this
x = 126.82 #get with tiled
y = 679.54 #get with tiled
id_rotation = [ [math.cos(angle), -math.sin(angle)],
[math.sin(angle), math.cos(angle)] ]
R = numpy.matrix(id_rotation)
id_position = [ [x - center_x],
[y - center_y] ]
B = numpy.matrix(id_position)
id_center = [ [center_x],
[center_y] ]
C = numpy.matrix(id_center)
print(numpy.dot(R, B) + C) #return original position before rotation
如果我只用pygame.transform.rotate:
if isinstance(layer, pytmx.TiledObjectGroup):
for object in layer:
if (object.image):
assets_surface = pygame.Surface((object.width, object.height), pygame.SRCALPHA)
assets_surface.blit(object.image, (0, 0))
assets_surface_rotate = pygame.transform.rotate(assets_surface, -object.rotation)
rdc.blit(assets_surface_rotate, (object.x, object.y))
我得到的图块对象的 x,y 位置错误:
我认为您在旋转后传递对象的 x 和 y 位置时犯了一些错误。我没用过tile map所以不知道具体情况,但是在pygame中把位置传给blit
的时候应该传top-left角的坐标。 Surface
的 top-left 角将位于这些坐标处。
rdc.blit(assets_surface_rotate, (object.x, object.y))
这里我不知道坐标 object.x
和 object.y
是什么,但我敢打赌它们不是 top-left 角,或者你的代码应该可以工作。
一般来说,做这些工作你可以使用 Sprite class 或 subclass,这会有很大帮助。
class TMSprite(pygame.sprite.Sprite):
# Constructor. Create a Surface from a TileMap and set its position
def __init__(self, tmo, x, y, width, height):
# Call the parent class (Sprite) constructor
super(TMSprite, self).__init__()
# Create the image of the block
self.image = pygame.Surface((width, height), pygame.SRCALPHA)
self.image.blit(tmo.image, (0, 0))
# Fetch the rectangle object that has the dimensions of the image
# Set its position with the move method
self.rect = self.image.get_rect().move(x, y)
def rotate(self, angle):
# TMSprite rotation on its center of a given angle
rot_center = self.rect.center
self.image = pygame.transform.rotate(self.image, angle)
self.rect = self.image.get_rect()
self.rect.center = rot_center
这就是使用 TMSprite
class.
if isinstance(layer, pytmx.TiledObjectGroup):
for tmob in layer:
if (tmob.image):
x = tmob.x #x should be that of the top-left corner. Adjust the formula if tmob.x is not the top-left
y = tmob.y #y should be that of the top-left corner. Adjust the formula if tmob.y is not the top-left
assets_sprite = TMSprite(tmob, x, y, tmob.width, tmob.height)
assets_sprite.rotate(-object.rotation)
rdc.blit(assets_sprite.image, assets_sprite.rect)
在这里,我没有传递 top-left 坐标,而是将精灵的 Rect
传递给 blit
。 blit
方法将从矩形中提取坐标。
请注意,旋转是在 Surface 的 centre 上执行的。旋转后,如果角度不是 90° 的倍数,则 Surface 会放大,因为 Surface 的正方形必须与屏幕对齐。如果有alpha通道没有问题,多余的像素是透明的,但是top-left角会变。
好的,如果有人需要,我已经找到了解决方案:
elif isinstance(layer, pytmx.TiledObjectGroup):
for Object in layer:
if (Object.image):
if Object.rotation != 0:
angle = math.radians(-Object.rotation)
center_x = Object.centerX
center_y = Object.centerY
Object_y = Object.y + Object.height
id_rotation = [ [math.cos(angle), -math.sin(angle)],
[math.sin(angle), math.cos(angle)] ]
R = numpy.matrix(id_rotation)
id_position = [ [Object.x - center_x],
[Object_y - center_y] ]
P = numpy.matrix(id_position)
id_center = [ [center_x],
[center_y] ]
C = numpy.matrix(id_center)
position_without_rotation = numpy.dot(R, P) + C
no_rotation_x = position_without_rotation[0]
no_rotation_y = position_without_rotation[1] - Object.height #Repere Tiled pas le meme que Pygame
Object_surface = pygame.Surface((Object.image.get_rect()[2], Object.image.get_rect()[3]), pygame.SRCALPHA)
Object_surface.blit(Object.image, (0, 0))
Object_surface_scale = pygame.transform.scale(Object_surface, (round(Object.width), round(Object.height)))
Object_surface_rotate = pygame.transform.rotate(Object_surface_scale, -Object.rotation) #Pygame va en anti horaire
extra_x = (Object_surface_rotate.get_rect()[2] - Object.width) / 2
extra_y = (Object_surface_rotate.get_rect()[3] - Object.height) / 2
rdc.blit(Object_surface_rotate, (no_rotation_x - extra_x, no_rotation_y - extra_y))
else:
Object_surface = pygame.Surface((Object.image.get_rect()[2], Object.image.get_rect()[3]), pygame.SRCALPHA)
Object_surface.blit(Object.image, (0, 0))
Object_surface_scale = pygame.transform.scale(Object_surface, (round(Object.width), round(Object.height)))
rdc.blit(Object_surface_scale, (Object.x, Object.y))