Python Forum
How to display isometric maps with pytmx?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to display isometric maps with pytmx?
#11
Could you share the Tiled map file?
It looks like they're being rendered in the right order, there's just a missing offset, and the number of tiles per row is off. I'm not sure if that info is available in the file itself, or if it needs to be calculated, and I'd like to play around with it.
Reply
#12
(Feb-10-2020, 07:23 PM)nilamo Wrote: Could you share the Tiled map file?
It looks like they're being rendered in the right order, there's just a missing offset, and the number of tiles per row is off. I'm not sure if that info is available in the file itself, or if it needs to be calculated, and I'd like to play around with it.

Although it is already in my first post in this thread, here you go, in it's full version. (Not meant mean! Smile )
NOTE: Some of the information in there is not necessary for the map to load (I think):

import pygame as pg
import pytmx
from settings import *

def collide_hit_rect(one, two):
    return one.hit_rect.colliderect(two.rect)

class TiledMap:
    def __init__(self, filename):
        tm = pytmx.load_pygame(filename, pixelalpha=True)
        self.width = tm.width * tm.tilewidth
        self.height = tm.height * tm.tileheight
        self.tmxdata = tm

    def render(self, surface):
        ti = self.tmxdata.get_tile_image_by_gid
        for layer in self.tmxdata.visible_layers:
            if isinstance(layer, pytmx.TiledTileLayer):
                for x, y, gid, in layer:
                    tile = ti(gid)
                    if tile:
                        surface.blit(tile, (x * self.tmxdata.tilewidth,
                                            y * self.tmxdata.tileheight))

    def make_map(self):
        temp_surface = pg.Surface((self.width, self.height))
        self.render(temp_surface)
        return temp_surface

class Camera:
    def __init__(self, width, height):
        self.camera = pg.Rect(0, 0, width, height)
        self.width = width
        self.height = height

    def apply(self, entity):
        return entity.rect.move(self.camera.topleft)

    def apply_rect(self, rect):
        return rect.move(self.camera.topleft)

    def update(self, target):
        x = -target.rect.centerx + int(WIDTH / 2)
        y = -target.rect.centery + int(HEIGHT / 2)

        # limit scrolling to map size
        x = min(0, x)  # left
        y = min(0, y)  # top
        x = max(-(self.width - WIDTH), x)  # right
        y = max(-(self.height - HEIGHT), y)  # bottom
        self.camera = pg.Rect(x, y, self.width, self.height)
Hope you can figure out my problem now. Thanks for your help! Smile
Reply
#13
(Feb-11-2020, 06:44 PM)Piethon Wrote: Hope you can figure out my problem now. Thanks for your help!
I think he's asking for the .tmx file to find out how the tile locations are expressed to figure out hoe to code the offset.
Reply
#14
(Feb-11-2020, 08:47 PM)michael1789 Wrote:
(Feb-11-2020, 06:44 PM)Piethon Wrote: Hope you can figure out my problem now. Thanks for your help!
I think he's asking for the .tmx file to find out how the tile locations are expressed to figure out hoe to code the offset.

If I try to upload it here, it says, I can't.
So you can download it here:

https://ufile.io/e4ikmqms

Hope that is working for you!
Reply
#15
Hello.

I just thought, that in my code is a grid.
In my settings file, there are the following things:

WIDTH = 1024   
HEIGHT = 768
TILESIZE = 64
GRIDWIDTH = WIDTH / TILESIZE
GRIDHEIGHT = HEIGHT / TILESIZE
Probably I could change these numbers, to make an isometric grid. But I don't know how to change them. Do you think that could solve my problem?

Thanks for your help.
Piethon
Reply
#16
(Feb-17-2020, 11:58 AM)Piethon Wrote: Probably I could change these numbers, to make an isometric grid. But I don't know how to change them. Do you think that could solve my problem?

I don't think so. Looking at you pics, while the individual tiles are at the proper 45degree orientation, they are blit to the screen in a straight pattern across the screen. If we look at your isometric picture and take the top as the first tile, then the next is (x =+ TILESIZE / 2, y =+ TILESIZE / 2). But your program blits at x += TILESIZE. It's just writing them across instead of at the right angle.

nilamo, might be better help with deciphering the .tmx, but I can't read it.

I think the problem needs to be fixed here:
for x, y, gid, in layer:
                    tile = ti(gid)
                    if tile:
                        surface.blit(tile, (x * self.tmxdata.tilewidth,
                                            y * self.tmxdata.tileheight))
I really think there needs to be an offset figured out to put here
x * self.tmxdata.tilewidth + OFFESET
What that is would be the problem. It might be simple. Just to test the theory, try this and see what it looks like.

surface.blit(tile, (x * self.tmxdata.tilewidth / 2,
                    y * (self.tmxdata.tileheight * 1.5) ))
It think it will show a mess, but it might let us know we are on the right track. I suspect that in the end that the OFFSET will have to be a complex variable that tracks the rows and offsets the starting position, but who knows... that might fix it.
Reply
#17
The offset is definitely the way to go. I opened the map, and re-saved it using csv instead of base64, so you can see how it's organizing the tiles. The file itself has no direction whatsoever of how to format the tiles, and pytmx doesn't help there, either.

I think what you want to do, is calculate how many tiles are in each row, which you can use to determine the offset (offset is based on the width of the tiles*number of tiles), and try to format it that way. I can't test it right now, but I'll try to try it out tonight.

The file:
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.2" tiledversion="1.3.2" orientation="isometric" renderorder="left-down" compressionlevel="0" width="16" height="16" tilewidth="64" tileheight="32" infinite="0" nextlayerid="8" nextobjectid="1">
 <tileset firstgid="1" source="outdoor pack.tsx"/>
 <layer id="1" name="Ground" width="16" height="16">
  <data encoding="csv">
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,31,11,11,11,
11,11,11,11,11,31,11,11,13,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,31,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,13,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,
11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11
</data>
 </layer>
 <layer id="2" name="Floor" width="16" height="16">
  <data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,175,175,175,175,175,175,175,175,0,0,0,0,
0,0,0,175,175,175,175,175,175,175,175,175,0,0,0,0,
0,0,0,175,175,172,172,172,171,175,175,175,175,0,0,0,
0,0,0,175,0,0,0,0,0,0,175,175,175,175,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,175,175,175,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
 </layer>
 <layer id="3" name="M1" width="16" height="16">
  <data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,255,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,256,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,240,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,273,0,0,0,0,0,0,111,0,0,
0,0,0,0,0,0,0,0,0,0,256,0,0,0,0,0,
0,0,0,0,0,255,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,94,0,
0,0,0,0,0,0,251,0,0,0,0,0,0,0,0,76,
0,0,0,0,0,0,0,0,0,0,0,0,93,0,92,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
 </layer>
 <layer id="4" name="M2" width="16" height="16">
  <data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,230,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,263,0,0,0,0,0,0,101,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,84,0,0,
0,0,0,0,0,0,241,0,0,0,0,0,0,0,66,0,
0,0,0,0,0,0,0,0,0,0,0,83,0,82,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
 </layer>
 <layer id="5" name="M3" width="16" height="16">
  <data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,253,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
 </layer>
 <layer id="6" name="m4" width="16" height="16">
  <data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,243,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
 </layer>
 <layer id="7" name="m5" width="16" height="16">
  <data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
 </layer>
</map>
Reply
#18
Alright, give this a shot:
import pygame as pg
import pytmx
 
WIDTH = 1024   
HEIGHT = 768
TILESIZE = 16
GRIDWIDTH = WIDTH / TILESIZE
GRIDHEIGHT = HEIGHT / TILESIZE

class TiledMap:
    def __init__(self, filename):
        tm = pytmx.load_pygame(filename, pixelalpha=True)
        self.width = tm.width * tm.tilewidth
        self.height = tm.height * tm.tileheight
        self.tmxdata = tm
 
    def render(self, surface):
        ti = self.tmxdata.get_tile_image_by_gid
        for layer in self.tmxdata.visible_layers:
            if isinstance(layer, pytmx.TiledTileLayer):
                # top left corner, is in the middle of the screen, at the top
                x_start = (WIDTH / 2)
                y_start = 0

                x_offset = x_start
                y_offset = y_start
                for x, y, gid, in layer:
                    if x == 0:
                        # each new row resets the y-offset, and we back the x-offset a little more to the left
                        # the map is defined as left to right, then top to bottom
                        y_start += self.tmxdata.tileheight
                        x_start -= (self.tmxdata.tilewidth / 2)
                        y_offset = y_start
                        x_offset = x_start

                    tile = ti(gid)
                    if tile:
                        surface.blit(tile, (x_offset, y_offset))
                    
                    # move the offset for the next tile
                    y_offset += self.tmxdata.tileheight
                    x_offset += (self.tmxdata.tilewidth / 2)

    def make_map(self):
        temp_surface = pg.Surface((self.width, self.height))
        self.render(temp_surface)
        return temp_surface

if __name__ == "__main__":
    pg.init()
    win = pg.display.set_mode((WIDTH, HEIGHT))
    iso = TiledMap("iso.tmx")
    running = True
    while running:
        for ev in pg.event.get():
            if pg.QUIT == ev.type:
                running = False
        
        iso.render(win)
        pg.display.flip()
The map is defined in a left to right, then top to bottom fashion. So (0, 0) is the "top left" tile, which should be rendered at the very top of the window, center screen. By doing a little calculation to get the center of the window (WIDTH / 2), all that's left is to move the offset by a tile (or half a tile, for the x-axis) to get the position of the next tile, and then reset the offsets for the next "row".
mattwins likes this post
Reply
#19
...and once I actually woke up, I realized all that math can be based entirely off the indices, instead of having temporary variables. And also, that the tiles needed to be rotated 45 degrees:
    def render(self, surface):
        ti = self.tmxdata.get_tile_image_by_gid
        for layer in self.tmxdata.visible_layers:
            if isinstance(layer, pytmx.TiledTileLayer):
                for x, y, gid, in layer:
                    x_offset = (WIDTH / 2) + (self.tmxdata.tilewidth / 2 * (x+1)) - (self.tmxdata.tilewidth / 2 * (y+1))
                    y_offset = ((y+1) * self.tmxdata.tileheight) + (self.tmxdata.tileheight * x)

                    tile = ti(gid)
                    if tile:
                        rotated = pg.transform.rotate(tile.convert_alpha(), 45)
                        surface.blit(rotated, (x_offset, y_offset))
mattwins likes this post
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Isometric game pygame Tiled howardberger 1 476 Jan-31-2024, 10:01 PM
Last Post: deanhystad
  [PyGame] Isometric Movement on Tiled Map Josselin 0 2,321 Nov-02-2021, 06:56 AM
Last Post: Josselin
Thumbs Up [PyGame] Simple code for isometric 2D games ThePhi 1 15,172 Nov-17-2020, 12:58 PM
Last Post: mattwins
  [split] How to display isometric maps with pygame? mattwins 6 6,142 Nov-17-2020, 12:54 PM
Last Post: mattwins
  Problems with pytmx Piethon 15 9,198 Jan-05-2020, 10:44 AM
Last Post: Piethon

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020