I made a simple test map in Tiled to experiment with custom properties.
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.11.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="32" tileheight="32" infinite="0" nextlayerid="2" nextobjectid="1">
<tileset firstgid="1" name="pixilart-drawing" tilewidth="32" tileheight="32" tilecount="1" columns="1">
<image source="pixilart-drawing.png" width="32" height="32"/>
</tileset>
<tileset firstgid="2" name="pixil-frame-0" tilewidth="32" tileheight="32" tilecount="1" columns="1">
<properties>
<property name="Colider" type="bool" value="true"/>
</properties>
<image source="pixil-frame-0.png" width="32" height="32"/>
</tileset>
<layer id="1" name="Tile Layer 1" width="30" height="20">
<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,2,1,1,1,1,1,2,1,1,1,1,1,2,2,2,2,2,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,2,2,2,2,2,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,2,1,1,1,1,1,2,1,1,1,1,1,2,2,2,2,2,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,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>
</map>
Notice how the "pixil-frame-0" tile has a custom property, "colider"
Then, I wrote a simple script to test this.
import pygame
import pytmx
pygame.init()
WIDTH, HEIGHT = 30*32, 20*32
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
# --- Load TMX Map ---
tmx_data = pytmx.load_pygame(r'C:\Users\miked\OneDrive\Documents\GitHub\RFIDCard\map-rpg-game\Test_map.tmx')
tile_width = tmx_data.tilewidth
tile_height = tmx_data.tileheight
def draw_map():
for layer in tmx_data.visible_layers:
if isinstance(layer, pytmx.TiledTileLayer):
for x, y, gid in layer:
tile = tmx_data.get_tile_image_by_gid(gid)
if tile:
screen.blit(tile, (x * tile_width, y * tile_height))
# --- Player Class ---
lass Player:
def __init__(self, x, y):
self.x = x
self.y = y
self.image = pygame.Surface((32, 32))
self.image.fill((255, 0, 0)) # Red square for the player
def is_collidable(self, x, y, tmx_data): # KEY BLOCK!
for layer in tmx_data.visible_layers:
if isinstance(layer, pytmx.TiledTileLayer):
try:
gid = layer.data[y][x]
except IndexError:
continue
if gid == 0:
continue # Empty tile
props = tmx_data.get_tile_properties_by_gid(gid)
# Check for "Colider" property
if props and props.get("Colider") is True:
return True
return False
def draw(self, screen):
screen.blit(self.image, (self.x, self.y))
def move(self, dx, dy):
new_x = (self.x + dx) // tile_width
new_y = (self.y + dy) // tile_height
# Only move if target tile is not collidable
if not self.is_collidable(new_x, new_y, tmx_data):
self.x += dx
self.y += dy
def update(self, inputs):
if inputs.get('up') or inputs.get('w'):
self.move(0, -32)
if inputs.get('down') or inputs.get('s'):
self.move(0, 32)
if inputs.get('left') or inputs.get('a'):
self.move(-32, 0)
if inputs.get('right') or inputs.get('d'):
self.move(32, 0)
player = Player(5 * 32, 5 * 32)
# --- Main Loop ---
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
keys = pygame.key.get_pressed()
player.update({
'up': keys[pygame.K_UP],
'down': keys[pygame.K_DOWN],
'left': keys[pygame.K_LEFT],
'right': keys[pygame.K_RIGHT],
'w': keys[pygame.K_w],
'a': keys[pygame.K_a],
's': keys[pygame.K_s],
'd': keys[pygame.K_d],
})
screen.fill((0, 0, 0)) # Clear screen
draw_map()
player.draw(screen)
pygame.display.flip()
clock.tick(10)
Unfortunately, the player phases through the collidable tiles as if they don't exist. I'm not sure why. (tile "2" in the data encoding is the colidible tile.)

EDIT 1: The problem can be avoided with:
gid = layer.data[y][x]
if gid == 2:
return True # Treat gid 2 as collidable (Not a permanent solution)
But this is crude, doesn't scale well, and the original question remains unanswered.
Problem is because props is always None.
You have to put <properties> in <tile id="0"> and they will be accessible with gid="2"
(where gid = firstgid + id)
<tileset firstgid="2" name="pixil-frame-0" tilewidth="32" tileheight="32" tilecount="1" columns="1">
<tile id="0">
<properties>
<property name="Colider" type="bool" value="true"/>
</properties>
</tile>
<image source="pixil-frame-0.png" width="32" height="32"/>
</tileset>
props = tmx_data.get_tile_properties_by_gid(2)
print(props)
{'id': 0, 'Colider': True, 'width': 32, 'height': 32, 'frames': []}
To get current properties which are directly in tilesetyou need
tmx_data.tilesets[1].properties.get("Colider")
tmx_data.tilesets is a list so it doesn't use gid