Tilemap with multiple regions creates seams/artifacts

,

I’m using a tilemap to represent the elements of a map on my game, both below the player z-index as well as above it. I noticed that when a layer has multiple sparse shapes it somehow creates some artifacts in areas that should have been empty. This is only noticeable when it has a higher z-index, so the artifacts show up above the player sprite. This happens both on the simulator and on the device.

Maybe it’s a problem on how I wrote the code, but this is a sample below. The imageTableObj is just a regular image table with the tilesheet. The layer is a table where data is an array of tile ids representing this layer in the map, same ids from imageTableObj.

local tilemap = gfx.tilemap.new()
tilemap:setImageTable(imagetableObj)
tilemap:setTiles(layer.data, layer.width)
local layerSprite = gfx.sprite.new(tilemap)
layerSprite:setCenter(0, 0)
layerSprite:moveTo(0, 0)
layerSprite:setZIndex(zIndex)
layerSprite:setIgnoresDrawOffset(false)

This is a sample of the issue happening:


The player sprite is being cut off. It didn't happen everywhere, so it took me quite some time to try figuring this out.

I removed the collision sprites to see if it was related to the collisions, but in the gif below you can see it's something where different tiles meet, although the wall and door are on the same z-index.
playdate-20260404-090133

Then I decided to rewrite this using tilemap:getCollisionRects({}) and create individual sprites per rect with the high z-index layer. Although they don't really represent collisions, the method provides an easy way to grab all contiguous shapes. So, I wrote the following code that runs only for the high z-index layer. self.tileSize is the size of the map tiles.

local rects = tilemap:getCollisionRects({})
for _, layerRect in ipairs(rects) do
    local rectPixelWidth = layerRect.width * self.tileSize
    local rectPixelHeight = layerRect.height * self.tileSize
    local rectImage = gfx.image.new(rectPixelWidth, rectPixelHeight)

    gfx.pushContext(rectImage)
    for dy = 0, layerRect.height - 1 do
        local mapTileY = layerRect.y + dy
        for dx = 0, layerRect.width - 1 do
            local mapTileX = layerRect.x + dx
            local layerIndex = (mapTileY * layer.width) + mapTileX + 1
            local tileId = layer.data[layerIndex]

            -- We can safely assume tileId is always great than 0 because getCollisionRects only returns rects for solid tiles, which are defined as non-zero tile IDs in Tiled.
            local tileImage = imagetableObj:getImage(tileId)
            if tileImage then
                tileImage:draw(dx * self.tileSize, dy * self.tileSize)
            end
        end
    end
    gfx.popContext()

    local rectSprite = gfx.sprite.new(rectImage)
    local centerX = (layerRect.x * self.tileSize) + (rectPixelWidth / 2)
    local centerY = (layerRect.y * self.tileSize) + (rectPixelHeight / 2)
    rectSprite:moveTo(centerX, centerY)
    rectSprite:setZIndex(zIndex)
    rectSprite:setIgnoresDrawOffset(false)
    self:addSprite(rectSprite)
end

And with the aforementioned change, I get this now:
playdate-20260405-103707

The conclusion I had was that using tilemap it seems to cause some issue if you create a single sprite. I thought about calling the draw() on every update(), but I would rather delegate it to a sprite as it takes care of that for me and only draws if something changes.

PS: I only do this for the upper layers as I don't care if those artifacts show up below the player sprite 'cause they wouldn't be visible anyway. But I have no idea if that's happening there too.