So I have been working on a project for some time now and I realy wanted to get animated tiles in to the game. Im creating a 2d pixel art styled game with pygame and Im using the an editor called Tiled to create the map. Tiled generates a .tmx file as well as a .tsx file to be used to render the map. I have gotten the map to render without any problems. The problems comes with rendering animated tiles. They just dont get animated. I understand the basics of how the animation works. I just need to get the first image of the animation, wait the duration between frames and then render the next frame. But I just cant figure out how to get it working. There is minimal documentation of pytmx and how it reads animations from Tiled files.
This is the .tmx file:
<?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>
And this is the .tsx file:
<?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>
And this is how I currently render the tiles:
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))
This is what it says on the Pytmx github :
# 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
...
Any help is greatly appreciated!
Here is the project on GitHub if it is to any use :D
I had fun looking for this today. And it's not that complicated after all.
I made something like this in Tiled
Note the animated water tiles.
Now look at the code below:
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()
when you browse the list of tiles by layers, you recover the image by position (x and y).
The principle is that, before displaying the current image (the tile), we check that it is not in fact an animation.
For that you have to get all the animated tiles.
So we do
for gid, props in self.tmx_data.tile_properties.items():
All animated tiles are in props['frames'].
If you look at your tsx file, you could see something like this :
<?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>
...
So each part of props['frame'] are tables represent each animation nodes.
So
if image == self.tmx_data.get_tile_image_by_gid(props['frames'][0].gid):
means that current image is an animated tile. In that case all you need to de is to blit one of the tile in your props['frame'] table. As you can see I created a current_anim_index attribute. I vary it in the update method.
I make sure that this is called in my game loop. The frame argument varies from 0 to 60 (Yes, 60 FPS). And getFrequencyList(6) return a table like [0, 10, 20, 30, 40, 50].
I am working with .svg files exported from GIMP. I need the nodes on each path moved to the nearest vertex (on a 70px grid) and remove duplicates.
A sample .svg looks like this:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="1283.95mm" height="1037.04mm"
viewBox="0 0 3640 2940">
<path id="Selection"
fill="none" stroke="black" stroke-width="1"
d="M 1475.00,205.00
C 1475.00,225.21 1471.02,215.59 1471.00,231.00
1471.00,231.00 1471.00,329.00 1471.00,329.00
1471.00,331.56 1470.89,334.55 1471.65,337.00
1472.95,341.22 1476.88,346.28 1481.01,347.99
1484.25,349.34 1489.44,349.00 1493.00,349.00
1493.00,349.00 1515.00,349.00 1515.00,349.00
1515.00,349.00 1660.00,349.00 1660.00,349.00
1662.50,349.00 1665.62,349.11 1668.00,348.35
1682.12,343.87 1679.00,324.35 1679.00,313.00
1679.00,313.00 1679.00,231.00 1679.00,231.00
1678.98,215.59 1675.00,225.21 1675.00,205.25
1675.00,205.25 1685.00,205.25 1685.00,205.25
1685.00,205.25 1698.00,209.00 1698.00,209.00
1698.00,209.00 1731.00,209.00 1731.00,209.00
1731.00,209.00 1755.00,205.00 1755.00,205.00
1755.00,205.00 1755.00,209.00 1755.00,209.00
1754.97,222.24 1751.16,217.55 1751.00,229.00
1751.00,229.00 1751.00,314.00 1751.00,314.00
1751.00,314.00 1751.00,469.00 1751.00,469.00
1751.02,484.41 1755.00,474.79 1755.00,494.75
1755.00,494.75 1745.00,494.75 1745.00,494.75
1745.00,494.75 1732.00,491.00 1732.00,491.00
1732.00,491.00 1630.00,491.00 1630.00,491.00
1630.00,491.00 1605.00,495.00 1605.00,495.00
1605.00,474.79 1608.98,484.41 1609.00,469.00
1609.00,469.00 1609.00,441.00 1609.00,441.00
1608.99,433.51 1607.29,426.29 1599.96,422.45
1596.23,420.50 1590.22,421.00 1586.00,421.00
1586.00,421.00 1559.00,421.00 1559.00,421.00
1547.59,421.06 1541.16,427.47 1541.00,439.00
1541.00,439.00 1541.00,470.00 1541.00,470.00
1541.05,480.13 1542.88,477.97 1544.75,484.00
1545.21,486.62 1545.00,492.08 1544.75,494.75
1544.75,494.75 1535.00,494.75 1535.00,494.75
1535.00,494.75 1522.00,491.00 1522.00,491.00
1522.00,491.00 1420.00,491.00 1420.00,491.00
1420.00,491.00 1395.00,495.00 1395.00,495.00
1395.00,495.00 1395.00,491.00 1395.00,491.00
1395.03,477.76 1398.84,482.45 1399.00,471.00
1399.00,471.00 1399.00,386.00 1399.00,386.00
1399.00,386.00 1399.00,231.00 1399.00,231.00
1398.98,215.59 1395.00,225.21 1395.00,205.25
1395.00,205.25 1405.00,205.25 1405.00,205.25
1405.00,205.25 1418.00,209.00 1418.00,209.00
1418.00,209.00 1451.00,209.00 1451.00,209.00
1451.00,209.00 1475.00,205.00 1475.00,205.00 Z
M 2035.00,1255.00
C 2035.00,1255.00 2035.00,1260.00 2035.00,1260.00
2034.92,1272.31 2031.02,1266.58 2031.00,1281.00
2031.00,1281.00 2031.00,1449.00 2031.00,1449.00
2031.02,1464.41 2035.00,1454.79 2035.00,1474.75
2035.00,1474.75 2025.00,1474.75 2025.00,1474.75
2025.00,1474.75 2012.00,1471.00 2012.00,1471.00
2012.00,1471.00 1910.00,1471.00 1910.00,1471.00
1910.00,1471.00 1885.00,1475.00 1885.00,1475.00
1885.00,1475.00 1885.00,1465.00 1885.00,1465.00
1903.68,1465.00 1894.73,1468.98 1910.00,1469.00
1910.00,1469.00 2010.00,1469.00 2010.00,1469.00
2012.98,1469.00 2016.18,1469.16 2018.99,1467.99
2023.12,1466.28 2027.05,1461.22 2028.35,1457.00
2028.35,1457.00 2029.00,1425.00 2029.00,1425.00
2029.00,1425.00 2029.00,1281.00 2029.00,1281.00
2028.98,1267.66 2023.60,1261.02 2010.00,1261.00
2010.00,1261.00 1916.00,1261.00 1916.00,1261.00
1916.00,1261.00 1770.00,1261.00 1770.00,1261.00
1767.02,1261.00 1763.82,1260.84 1761.01,1262.01
1756.88,1263.72 1752.95,1268.78 1751.65,1273.00
1751.65,1273.00 1751.00,1305.00 1751.00,1305.00
1751.00,1305.00 1751.00,1449.00 1751.00,1449.00
1751.01,1456.49 1752.71,1463.71 1760.04,1467.55
1763.77,1469.50 1769.78,1469.00 1774.00,1469.00
1774.00,1469.00 1800.00,1469.00 1800.00,1469.00
1815.27,1468.98 1806.32,1465.00 1825.00,1465.00
1825.00,1465.00 1825.00,1474.75 1825.00,1474.75
1825.00,1474.75 1815.00,1474.75 1815.00,1474.75
1815.00,1474.75 1802.00,1471.00 1802.00,1471.00
1802.00,1471.00 1769.00,1471.00 1769.00,1471.00
1769.00,1471.00 1745.00,1475.00 1745.00,1475.00
1745.00,1475.00 1745.00,1470.00 1745.00,1470.00
1745.08,1457.69 1748.98,1463.42 1749.00,1449.00
1749.00,1449.00 1749.00,1281.00 1749.00,1281.00
1748.98,1265.59 1745.00,1275.21 1745.00,1255.00
1745.00,1255.00 1749.00,1255.00 1749.00,1255.00
1749.00,1255.00 1770.00,1259.00 1770.00,1259.00
1770.00,1259.00 2010.00,1259.00 2010.00,1259.00
2010.00,1259.00 2035.00,1255.00 2035.00,1255.00 Z
M 1054.75,2095.00
C 1055.00,2097.92 1055.21,2103.38 1054.75,2106.00
1053.06,2111.36 1051.12,2110.34 1051.00,2119.00
1051.00,2119.00 1051.00,2219.00 1051.00,2219.00
1051.02,2234.41 1055.00,2224.79 1055.00,2244.75
1055.00,2244.75 1045.00,2244.75 1045.00,2244.75
1045.00,2244.75 1032.00,2241.00 1032.00,2241.00
1032.00,2241.00 999.00,2241.00 999.00,2241.00
999.00,2241.00 975.25,2245.00 975.25,2245.00
975.00,2242.08 974.79,2236.62 975.25,2234.00
976.94,2228.64 978.88,2229.66 979.00,2221.00
979.00,2221.00 979.00,2121.00 979.00,2121.00
978.98,2105.59 975.00,2115.21 975.00,2095.25
975.00,2095.25 985.00,2095.25 985.00,2095.25
985.00,2095.25 998.00,2099.00 998.00,2099.00
998.00,2099.00 1031.00,2099.00 1031.00,2099.00
1031.00,2099.00 1054.75,2095.00 1054.75,2095.00 Z
M 3447.00,2899.00
C 3440.66,2897.20 3422.63,2898.43 3415.00,2898.18
3415.00,2898.18 3379.00,2898.18 3379.00,2898.18
3379.00,2898.18 3366.99,2898.18 3366.99,2898.18
3366.99,2898.18 3361.42,2898.87 3361.42,2898.87
3361.42,2898.87 3355.00,2898.04 3355.00,2898.04
3344.76,2897.54 3340.14,2901.77 3340.00,2912.00
3339.93,2917.84 3339.11,2925.22 3345.10,2928.55
3349.34,2930.90 3353.58,2929.57 3358.00,2929.77
3358.00,2929.77 3366.00,2929.77 3366.00,2929.77
3366.00,2929.77 3413.00,2929.77 3413.00,2929.77
3421.78,2929.42 3439.43,2931.15 3447.00,2929.00
3447.01,2931.77 3446.67,2936.29 3448.74,2938.40
3451.05,2940.76 3464.95,2940.76 3467.26,2938.40
3469.33,2936.29 3468.99,2931.77 3469.00,2929.00
3474.26,2930.50 3487.74,2929.97 3494.00,2930.00
3504.82,2930.06 3502.45,2932.75 3511.00,2932.98
3523.14,2933.32 3518.81,2930.02 3532.00,2930.00
3532.00,2930.00 3619.00,2930.00 3619.00,2930.00
3630.80,2929.94 3632.98,2926.21 3633.00,2915.00
3633.01,2907.54 3633.86,2898.49 3624.00,2897.23
3618.29,2896.49 3615.45,2899.85 3611.00,2901.09
3611.00,2901.09 3599.00,2901.09 3599.00,2901.09
3599.00,2901.09 3576.00,2901.09 3576.00,2901.09
3569.17,2899.98 3573.24,2897.81 3559.00,2898.00
3550.73,2898.11 3551.68,2899.93 3545.00,2901.12
3545.00,2901.12 3530.00,2901.12 3530.00,2901.12
3530.00,2901.12 3502.00,2901.12 3502.00,2901.12
3502.00,2901.12 3492.00,2901.88 3492.00,2901.88
3484.85,2901.36 3481.54,2895.44 3469.00,2899.00
3468.97,2895.56 3469.08,2892.17 3466.99,2889.21
3463.30,2883.98 3453.87,2883.66 3449.65,2888.39
3446.93,2891.45 3447.04,2895.19 3447.00,2899.00 Z" />
</svg>
I would need to replace each number inside the string to the closest number divisible by 70. Once that's done it's very likely that there will be duplicate paths so they should be removed until only one remains. Problem is I have absolutely no idea how to best achieve that in python.
For the first part perhaps using regex to find all numbers and for each replace with something like:
round(n / 70) * 70
For the second, I'm not sure yet if each line of 6 numbers would be equal to another full line, or only some of the numbers.