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:
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.
You have to draw the the objects in the correct order:
draw the isometric map
Sort the objects (player, strair, etc.) by their lower y-coordinate and render them from bottom to top