0

After many research I didn't find the answer, when I'm trying to display object in pygame with pytmx, the result is fully broken, because x, y change with rotation. I've tried to use matrix rotation but for this, I need to know the original center. I don't know how to find it, because Tiled sends me, x, y after rotation...

So my goal is simply to display object tile in pygame with pytmx.

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

If I only use 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))

I get this the wrong position x,y for tile object:

image of the error

Nazim Kerimbekov
  • 4,712
  • 8
  • 34
  • 58
laochun
  • 1
  • 4
  • Yes, I have read this, and I don't see what problem has my question ;(. Thanks for your answer. – laochun Jun 29 '19 at 12:11
  • If you have tried to solve the problem by yourself, show your best effort. We may help you make it work. – Valentino Jun 29 '19 at 12:23
  • @Valentino, thx for your answer, i have edited my post. So you can see, i can found the orignal position of tile object with the x, y after rotation and angle, but i also need center_x, center_y and this i don't know how to get ? – laochun Jun 29 '19 at 12:41
  • Can't you get them from the original image before rotation? Please, expain better what you are trying to do (in the question). Seems to me you're fallen in an [xy problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). If you are just trying to rotate an image, why not use [pygame.transform.rotate](https://www.pygame.org/docs/ref/transform.html#pygame.transform.rotate) and let it do all the dirty job? – Valentino Jun 29 '19 at 13:01
  • @Valentino, I've posted a screenshot what happened when i use pygame.transform.rotate, maybe the matrix rotation is not the solution, but I don't know what to do :( – laochun Jun 29 '19 at 13:18
  • I cannot say much since I don't know what `object` is, but the logic when using `transform.rotate` seems fine to me. Not sure why it doesn't rotate the object. Rather than reinventing the wheel, I would try to fix this. You probably have a (maybe silly) error somewhere. Maybe when you load the image with `pytmx`. – Valentino Jun 29 '19 at 14:19
  • @Valentino, I hope this screenshot will help you : https://imgur.com/a/Kz0dVUt – laochun Jun 29 '19 at 14:43

2 Answers2

0

I think you are doing some error when passing the x and y position of the object after rotation. I've never used tile map so I don't know the specifics, but in pygame when you pass the position to blit, you should pass the coordinates of the top-left corner. The top-left corner of the Surface will be at those coordinates.

rdc.blit(assets_surface_rotate, (object.x, object.y))

Here I don't know what coordinates object.x and object.y are exactly, but I bet they are not the top-left corner, or your code should work.

In general to do these kind of jobs you can use a Sprite class or subclass, which can help a lot.

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

And this is how you could rewrite your snippet using the 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)

Here, instead of passing the top-left coordinates, I pass the Rect of the sprite to blit. The blit method will extract the coordinates from the rectangle.

Note that rotation is performed on the centre of the Surface. After rotation, if the angle is not a multiple of 90°, the Surface is enlarged since the square of the Surface must be aligned with the screen. If there is an alpha channel is not a problem, the extra pixel are transparent, but the top-left corner will change.

Valentino
  • 7,291
  • 6
  • 18
  • 34
  • Hey, thanks for your answer. I think i must write `rdc.blit(assets_sprite.image, assets_sprite.rect)` instead `rdc.blit(assets_sprite_rotate.image, assets_sprite_rotate.rect)` ? In fact the coo of object in Tiled Software is Bottom Left and in Pygame is Top Left, so when I blit (x, y - height) but this doesn't work and your solution neither ;( I think the reason why, it's because Tiled give me (x, y) of Bottom Left corner after rotation, so I think this position is wrong ? Right ? – laochun Jun 29 '19 at 16:11
  • You are right. My `TMSprite.rotate` does not return a new sprite. I wrote it without thinking that. I fixed my answer. – Valentino Jun 29 '19 at 16:29
  • the x and y value must be before or after the rotation ? – laochun Jun 29 '19 at 17:07
  • In my answer, before. But it's possible to edit it according to your needs. For example, it's possible to do so that you need to pass the x, y of the center of the surface instead of the topleft. – Valentino Jun 29 '19 at 17:12
  • My problem is I have only, x after rotation, y after rotation, angle used for rotation in degree clockwise, width and height of object. So I don't know how to get the center ? – laochun Jun 29 '19 at 17:18
  • How is that possible? If you rotate the object in pygame, what you get from the tilemap is before the rotation. If your object is aleady rotated, you don't need to rotate it again. – Valentino Jun 29 '19 at 18:08
0

Ok I have found the solution if someone needs :

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))
laochun
  • 1
  • 4