pythontiletmxpytmx

Player object "phases" through colidible tiles


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.) Player sprite inside of 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.


Solution

  • 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