pythonpygametiledisometricpytmx

Player sprite not behind isometric tiles


I am currently trying to learn how to use isometric maps in a game environment. I'm using Tiled Map Editor to create a map and PyTMX and Pygame to read and use the information. I am currently making the maps in layers and treating them as big Sprites. This way I can use

'''

pygame.sprites.LayerUpdates

'''

Doing this allows me to render in the order:

Ground

Player

Walls

However, this gives me the problem shown in the pictures below:

Player sprite behind a tile

Player sprite covered by tile

I have researched on the internet for a solution but I keep coming up blank! I'm not sure if I should be rendering the layers once at the start of the game and then blitting the layers every frame or should I render the Ground layer once, as that will be the lowest layer (the player sprite will always be on top of that layer), then render the Walls layer every frame with a check to see if the Player is in front or behind a specific tile, (I suspect this will probably take a lot of processing).

I've included the code I have for the main game loop, the sprite classes and the TiledMap reader. Pretty standard stuff I've learned from the internet.

class Game:
    def __init__(self):
        pg.mixer.pre_init(44100, -16, 1, 2048)
        pg.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)
        self.clock = pg.time.Clock()
        pg.key.set_repeat(500, 100)
        self.load_data()
    
    def load_data(self):
        game_folder = path.dirname(__file__)
        self.map_folder = path.join(game_folder, 'maps')
        img_folder = path.join(game_folder, 'img')
        self.player_img = pg.image.load(path.join(img_folder,PLAYER_IMG)).convert_alpha()
        self.player_img = pg.transform.scale(self.player_img, PLAYER_SCALE)

    def new(self):
        self.all_sprites = pg.sprite.LayeredUpdates()
        self.wall_sprites = pg.sprite.Group()
        self.map = TiledMap(path.join(self.map_folder, LEVEL_MAPS), self)
        self.ground_map = Ground(self, self.map)
        self.wall_map = Walls(self, self.map)
        self.camera = Camera(self.ground_map.rect.width, self.ground_map.rect.height)
        for tile_object in self.map.tmxdata.objects:
            if tile_object.name == 'player':
                origin_x = ((self.map.width / 2))# - self.tilewidth / 2)
                tile_x = tile_object.x / self.map.tileheight
                tile_y = tile_object.y / self.map.tileheight
            
                offset = vec((tile_x - tile_y) * self.map.tilewidth / 2 + origin_x,
                             (tile_x + tile_y) * self.map.tileheight / 2)
                self.player = Player(self, offset.x, offset.y)
    
    def run(self):
        self.playing = True
        while self.playing:
            self.dt = self.clock.tick(FPS) / 1000
            self.update()
            self.events()
            self.draw()

    def update(self):
        self.all_sprites.update()
        self.camera.update(self.player)

    def draw(self):
        self.screen.fill(BLACK)
        pg.display.set_caption("{:.2f}".format(self.clock.get_fps()))
        for sprite in self.all_sprites:
            self.screen.blit(sprite.image, self.camera.apply(sprite))
        pg.display.flip()

    def quit(self):
        pg.quit()
        sys.exit()
    
    def events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.quit()
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    self.quit()
g = Game()

def main():
    while True:
        g.new()
        g.run()

if __name__ == '__main__':
    main()

Sprite Classes

def isometric_render(layer, tiled_map):
    temp_surface = pg.Surface((tiled_map.width, tiled_map.height))
    current_layer = tiled_map.tmxdata.get_layer_by_name(layer)
    ti = tiled_map.tmxdata.get_tile_image_by_gid           
    if isinstance(current_layer, pytmx.TiledTileLayer):
        for x, y, gid in current_layer:
            if gid != 0:
                starting_x = ((tiled_map.width / 2) - tiled_map.tilewidth / 2)
                offset = vec(((x - y) * tiled_map.tilewidth / 2) + starting_x,
                             ((x + y) * tiled_map.tileheight / 2) - 370)
                tile = ti(gid)                
                if tile:
                    temp_surface.blit(tile, (offset.x, offset.y))
    return temp_surface

class Player(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self._layer = PLAYER_LAYER
        self.groups = game.all_sprites
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = self.game.player_img
        self.rect = self.image.get_rect()
        self.rect.center = (x, y)
        self.vel = vec(0, 0)
        self.pos = vec(x, y)
        self.rot = 0

class Ground(pg.sprite.Sprite):
    def __init__(self, game, tiled_map):
        self._layer = GROUND_LAYER
        self.groups = game.all_sprites
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = isometric_render('Ground', tiled_map)
        self.rect = self.image.get_rect()
        
class Walls(pg.sprite.Sprite):
    def __init__(self, game, tiled_map):
        self._layer = WALL_LAYER
        self.groups = game.all_sprites, game.wall_sprites
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = isometric_render('Walls', tiled_map)
        self.image.set_colorkey(BLACK)
        self.rect = self.image.get_rect()

TiledMap Class

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

Here is the link to my GitHub page where you can see the full code Isometric Map loading practice GitHub Link

I hope this makes sense, I've only been programming for about a year so my terminology isn't up to scratch. Any code snippets, links to tutorials or general advice will be greatly appreciated.


Solution

  • You have to draw the the objects in the correct order:

    1. draw the isometric map

    2. Sort the objects (player, strair, etc.) by their lower y-coordinate and render them from bottom to top