Pytmx рендеринг анимированных листов с тайловой карты

Итак, я работаю над проектом в течение некоторого времени, и я действительно хотел добавить в игру анимированные плитки. Я создаю игру в стиле 2d пиксельной графики с помощью pygame, и я использую редактор Tiled для создания карты. Tiled создает файл .tmx, а также файл .tsx, который будет использоваться для визуализации карты. Я получил карту для рендеринга без каких-либо проблем. Проблема связана с отрисовкой анимированных плиток. Они просто не оживляются. Я понимаю основы работы с анимацией. Мне просто нужно получить первое изображение анимации, подождать промежутка времени между кадрами, а затем отрендерить следующий кадр. Но я просто не могу понять, как заставить его работать. Существует минимальная документация по pytmx и тому, как он читает анимацию из файлов Tiled.

Это файл .tmx:

<?xml version="1.0" encoding="UTF-8"?>
<map version="1.2" tiledversion="1.3.4" orientation="orthogonal" renderorder="right-down" width="32" height="32" tilewidth="96" tileheight="96" infinite="0" nextlayerid="5" nextobjectid="5">
 <tileset firstgid="1" source="Bigger-Textures(96x96).tsx"/>
 <layer id="1" name="Tile Layer 1" width="32" height="32">
  <data encoding="csv">
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,4,6,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,13,15,1,1,1,1,1,1,12,12,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,13,15,1,1,1,1,1,1,11,12,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,13,25,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,22,23,23,23,23,23,23,23,9,7,23,23,23,23,23,23,24,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,13,15,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,13,15,1,1,1,1,12,3,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,12,12,1,4,5,5,27,25,5,5,6,1,1,12,12,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,11,12,1,13,7,8,9,7,8,9,15,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,12,1,1,13,16,17,18,16,11,18,15,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,13,25,26,27,25,26,27,15,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,13,7,23,23,23,23,23,24,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,13,15,1,1,1,1,1,10,12,12,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,13,15,1,1,1,1,1,12,1,12,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,13,25,5,5,5,5,5,5,5,5,5,6,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,22,23,23,23,23,23,23,23,23,23,23,24,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
</data>
 </layer>
 <objectgroup id="4" name="Obstacles">
  <object id="1" name="wall" x="0" y="-96" width="3168" height="96"/>
  <object id="2" name="wall" x="3072" y="0" width="96" height="3168"/>
  <object id="3" name="wall" x="-96" y="3072" width="3168" height="96"/>
  <object id="4" name="wall" x="-96" y="-96" width="96" height="3168"/>
 </objectgroup>
</map>

А это файл .tsx:

<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.2" tiledversion="1.3.4" name="Bigger-Textures (96x96)" tilewidth="96" tileheight="96" tilecount="81" columns="9">
 <image source="../gfx/tiles/tilesheets/Textures-sprite-sheet-4X.png" width="864" height="864"/>
 <tile id="1">
  <animation>
   <frame tileid="1" duration="500"/>
   <frame tileid="2" duration="500"/>
  </animation>
 </tile>
 <tile id="2">
  <animation>
   <frame tileid="2" duration="500"/>
   <frame tileid="1" duration="500"/>
  </animation>
 </tile>
 <tile id="9">
  <animation>
   <frame tileid="9" duration="500"/>
   <frame tileid="10" duration="500"/>
  </animation>
 </tile>
 <tile id="10">
  <animation>
   <frame tileid="10" duration="500"/>
   <frame tileid="9" duration="500"/>
  </animation>
 </tile>
</tileset>

И вот как я сейчас визуализирую плитки:

    def render(self):
        self.ti = self.handler.currentMap.get_tile_image_by_gid
        xStart = max(0, self.handler.camera.xOffset / self.handler.currentMap.tilewidth)
        xEnd = min(self.handler.currentMap.width, (self.handler.camera.xOffset + self.handler.displayWidth) / self.handler.currentMap.tilewidth + 1)
        yStart = max(0, self.handler.camera.yOffset / self.handler.currentMap.tileheight)
        yEnd = min(self.handler.currentMap.height, (self.handler.camera.yOffset + self.handler.displayHeight) / self.handler.currentMap.tileheight + 1)

        for i in range(len(self.handler.currentMap.layers) - 1):
            for x in range(int(xStart), int(xEnd)):
                for y in range(int(yStart), int(yEnd)):
                    tile = self.handler.currentMap.get_tile_image(x, y, i)
                
                    if (tile):
                        self.display.blit(tile, (x * self.handler.currentMap.tilewidth - self.handler.camera.xOffset,
                                                 y * self.handler.currentMap.tileheight - self.handler.camera.yOffset))

Вот что написано на гитхабе Pytmx:

    # just iterate over animated tiles and demo them

    # tmx_map is a TiledMap object
    # tile_properties is a dictionary of all tile properties

    # iterate over the tile properties
        for gid, props in tmx_map.tile_properties.items():

            # iterate over the frames of the animation
            # if there is no animation, this list will be empty
            for animation_frame in props['frames']:
   
                # do something with the gid and duration of the frame
                # this may change in the future, as it is a little awkward now
                image = tmx_map.get_tile_image_by_gid(gid)
                duration = animation_frame.duration
                ...

Любая помощь приветствуется! Вот проект на GitHub, если он нужен: D


person Theo Esberg    schedule 19.04.2021    source источник


Ответы (1)


Мне было весело искать это сегодня. И это не так уж и сложно.

Я сделал что-то подобное в Tiled

Обратите внимание на анимированные плитки с водой.

Теперь посмотрим на код ниже:

 def update(self, frame):
    if frame in getFrequencyList(6):
        self.current_anim_index += 1

    if self.current_anim_index == 4:
        self.current_anim_index = 0

 def getSurface(self):
    for layer in self.tmx_data.visible_layers:
        for x, y, image in layer.tiles():
            for gid, props in self.tmx_data.tile_properties.items():
                if image == self.tmx_data.get_tile_image_by_gid(props['frames'][0].gid):
                    image = self.tmx_data.get_tile_image_by_gid(props['frames'][self.current_anim_index].gid)
                    self.surface.blit(image, (x * 16, y * 16))
                else:
                    self.surface.blit(image, ((x * 16) + layer.offsetx, (y * 16) + layer.offsety))

    return super().getSurface()

когда вы просматриваете список плиток по слоям, вы восстанавливаете изображение по положению (x и y). Принцип заключается в том, что перед отображением текущего изображения (плитки) мы проверяем, что это на самом деле не анимация. Для этого вам нужно собрать все анимированные плитки.

So we do

for gid, props in self.tmx_data.tile_properties.items():

Все анимированные плитки находятся в реквизите ['frames'].

Если вы посмотрите на свой tsx-файл, вы можете увидеть что-то вроде этого:

<?xml version="1.0" encoding="UTF-8"?>
    <tileset version="1.5" tiledversion="1.6.0" name="Overworld (Light)" 
tilewidth="16" tileheight="16" tilecount="1664" columns="52">
        <image source="Overworld (Light).png" trans="ff00ff" width="832" height="512"/>
        <tile id="30">
            <animation>
                <frame tileid="30" duration="250"/>
                <frame tileid="82" duration="250"/>
                <frame tileid="134" duration="250"/>
                <frame tileid="186" duration="250"/>
            </animation>
        </tile>
        ...

Таким образом, каждая часть props ['frame'] - это таблицы, представляющие каждый узел анимации. Так

if image == self.tmx_data.get_tile_image_by_gid(props['frames'][0].gid):

означает, что текущее изображение является анимированной плиткой. В этом случае все, что вам нужно сделать, это скопировать одну из плиток в таблице props ['frame']. Как видите, я создал атрибут current_anim_index. Варьирую в способе обновления. Я убеждаюсь, что это вызывается в моем игровом цикле. Аргумент кадра варьируется от 0 до 60 (да, 60 кадров в секунду). И getFrequencyList (6) возвращает таблицу вида [0, 10, 20, 30, 40, 50].

person Dowdheur    schedule 12.06.2021