Наложение тайлов на спрайт в pygame

Я создаю 2-мерный мир на основе тайлов для игры - в значительной степени под влиянием Pokemon - используя pygame / python, Tiled для файлов .tmx и библиотеку tmx Ричарда Джонса. Код, который я использую, в основном основан на этой замечательной демонстрации Pallet Town на Python.

Игра работает нормально, однако у меня возникают проблемы с тем, чтобы плитки на карте (например, дома, деревья) перекрывали спрайт игрока, когда было бы разумно, чтобы спрайт игрока исчез за ними. Например: на изображении здесь принципы восприятия глубины говорят нам, что дом на переднем плане должен перекрывать игрок в фоновом режиме, но поскольку карта 2D, нет глубины и, следовательно, нет окклюзии. Я бы хотел добавить глубины, но, поскольку я новичок в pygame (и python в целом), я не понимаю, как рисовать соответствующие объекты переднего плана поверх спрайта.

К счастью, я не одинок в этой проблеме, и существует множество документации о возможных решениях. Например:

Однако этот код обычно не пишется для python, и я не уверен, как его реализовать в моей ситуации. Сортировка / рисование по положению z (или по свойству «глубины») кажется наиболее разумным, но, глядя на библиотеку tmx, я могу найти только упомянутые значения x и y. Добавление спрайта игрока к пустому объектному слою в Tiled также является решением, но я снова не знаю, как это сделать, и все мои попытки приводили к сообщениям об ошибках. (Попытки здесь не описаны, потому что я, честно говоря, не знаю, что делал, и это все равно не сработало.)

Мой текущий код выглядит следующим образом:

class Player(pygame.sprite.Sprite):
    def __init__(self, location, collStart, orientation, *groups):
        super(Player, self).__init__(*groups)
        self.image = pygame.image.load('sprites/player.png')
        self.imageDefault = self.image.copy()
        self.rect = pygame.Rect(location, (26,26))
        self.collider = pygame.Rect(collStart, (13,13))
        self.orient = orientation 
        self.holdTime = 0
        self.walking = False
        self.dx = 0
        self.step = 'rightFoot'
        # Set default orientation
        self.setSprite()
        self.speed = pygame.time.get_ticks() + 50  # slows down walking speed 
        by .5 sec (current time + 50 ms)


    def setSprite(self):
        # this function contains information about where to find which sprite 
        in the sprite sheet, probably not relevant here.

    def update(self, dt, game):
        key = pygame.key.get_pressed()
        if pygame.time.get_ticks() >= self.speed:
            self.speed = pygame.time.get_ticks() + 50
            # Setting orientation and sprite based on key input, removed the 
            #code here because it wasn't relevant
            #[....]   
            # Walking mode enabled if a button is held for 0.1 seconds
            if self.holdTime >= 100:
                self.walking = True
            lastRect = self.rect.copy()
            lastColl = self.collider.copy() # collider covers the bottom section of the sprite
            # Code for walking in the direction the player is facing, not relevant here
            #[....]      
            # Collision detection:
            # Reset to the previous rectangle if player collides
            # with anything in the foreground layer
            if len(game.tilemap.layers['triggers'].collide(self.collider,
                                                            'solid')) > 0:
                self.rect = lastRect
                self.collider = lastColl
            # Area entry detection, loads dialog screen from the dialog file:
            elif len(game.tilemap.layers['triggers'].collide(self.collider,
                                                            'entry')) > 0:
                entryCell = game.tilemap.layers['triggers'].find('entry')[0]
                game.fadeOut()
                run()
                pygame.quit()
                quit()

                return
            if self.dx == 16:
                # Makes the player appear to take steps w/ different feet, not relevant here
            #[....]
            # After traversing 32 pixels, the walking animation is done
            if self.dx == 32:
                self.walking = False
                self.setSprite()
                self.dx = 0

            game.tilemap.set_focus(self.rect.x, self.rect.y)

class Game(object):
    def __init__(self, screen):
        self.screen = screen

    def initArea(self, mapFile):
        """Load maps and initialize sprite layers for each new area"""
        self.tilemap = tmx.load(mapFile, screen.get_size())
        self.players = tmx.SpriteLayer()
        self.objects = tmx.SpriteLayer()
        # In case there is no sprite layer for the current map
        except KeyError:
            pass
        else:
            self.tilemap.layers.append(self.objects)
        # Initializing player sprite
        startCell = self.tilemap.layers['triggers'].find('playerStart')[0]
        self.player = Player((startCell.px, startCell.py), (startCell.px, 
        startCell.bottom-4),
                             startCell['playerStart'], self.players)
        self.tilemap.layers.append(self.players)
        self.tilemap.set_focus(self.player.rect.x, self.player.rect.y)  

    def main(self):
        clock = pygame.time.Clock()
        self.initArea('test tilemap.tmx')

        while 1:
            dt = clock.tick(30)

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return
                if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                    return

            self.tilemap.update(dt, self)
            screen.fill((0,0,0))
            self.tilemap.draw(self.screen)
            pygame.display.flip()

Я снова использую библиотеку tmx, которую можно найти здесь. Может там что-то нужно поменять? Надеюсь, кто-нибудь поможет мне разобраться в этом. Это определенно возможно, как показывает этот клон покемона на Python (к сожалению, исходный код недоступен) . Кроме того, впервые пользователь StackOverflow, поэтому дайте мне знать, если я совершаю какие-либо ложные проходы :)


person Victoria    schedule 28.01.2019    source источник
comment
Можно ли наложить две тайл-карты? Один для фона, один для переднего плана. Порядок рисования: фон → игрок → передний план. Таким образом, трава (или что-то еще) будет нарисовано поверх игрока, создавая ощущение z-порядка. Любые пустые плитки переднего плана просто очищаются.   -  person Kingsley    schedule 29.01.2019
comment
@Kingsley Звучит как хорошее решение! Не могли бы вы помочь мне понять, как это реализовать? В настоящее время спрайт игрока рисуется поверх всего остального, даже если в Tiled есть фон и передний план. Я думаю, это связано с тем, что игрок не является частью слоя в Tiled, а добавляется позже с помощью класса Player. Как мне изменить порядок рисования, чтобы игрок появлялся за слоем переднего плана?   -  person Victoria    schedule 30.01.2019


Ответы (1)


Догадаться! Как предложил Кингсли в комментариях, решение заключалось в изменении порядка прорисовки слоев. Слои рисовались в порядке списка в классе Layers, причем спрайт игрока имел наивысший индекс и, таким образом, рисовался поверх всего остального. Перемещение игрока между фоновым слоем и слоем переднего плана в списке заставляло его появляться за объектами переднего плана. Для этого я добавил в функцию initArea класса Game следующий код:

  def initArea(self, mapFile):
    """Load maps and initialize sprite layers for each new area"""
    self.tilemap = tmx.load(mapFile, screen.get_size())
    self.players = tmx.SpriteLayer()
    self.objects = tmx.SpriteLayer()
    # Initializing player sprite
    startCell = self.tilemap.layers['triggers'].find('playerStart')[0]
    self.player = Player((startCell.px, startCell.py), (startCell.px, startCell.bottom-4),
                         startCell['playerStart'], self.players)
    foregroundItem = self.tilemap.layers.__getitem__("foreground") # finds the layer called foreground
    foregroundIndex = self.tilemap.layers.index(foregroundItem)  # finds the position of the foreground layer in the Layers list (Layers class specified in .tmx file)
    self.tilemap.layers.insert(foregroundIndex-1, self.players)  # move the Player layer one layer below the foreground layer
    self.tilemap.set_focus(self.player.rect.x, self.player.rect.y)

Сегодня вечером я еще немного поэкспериментирую, но пока это решение, похоже, работает. Спасибо за помощь!

person Victoria    schedule 30.01.2019