Parts of sprite rendering after moving it

Hello there,

In a 2d top down game im working on, a player picks up items off the ground, they animate moving to the player before disappearing via remove(). I noticed a bug recently where sometimes parts of the sprite of the item on the ground still render in the place it dropped after the item is picked up. It will disappear upon the player moving, which triggers the whole scene to update due to camera implementation.

One thing i thought it might be is the "miss" text as that was a recent change, but after removing that feature the bug still occurs.

The sprite itself is relatively simple:


local pickupVelocity = 4

function ItemDrop:init(item)

  local frame = item.animation:image()
  self.item = item
  self:setSize(frame.width, frame.height)
  self:setCollideRect(0, 0, self:getSize())
  self.isPickedUp = false

function ItemDrop:update()

  if self.isPickedUp then
    local dirX = self.pickupTarget.x - self.x
    local dirY = self.pickupTarget.y - self.y
    local x = pickupVelocity
    local y = pickupVelocity
    if math.abs(dirX) < x then
      x = dirX
    elseif dirX < 0 then
      x = x * -1

    if math.abs(dirY) < y then
      y = dirY
    elseif dirY < 0 then
      y = y * -1

    self:moveBy(x, y)
    if self.x == self.pickupTarget.x and self.y == self.pickupTarget.y then
    local overlappingSprites = self:overlappingSprites()
    for _, sprite in pairs(overlappingSprites) do
      if sprite:getTag() == ITEM_DROP_TAG then
        local r1 = self:getCollideRect()
        r1.x = r1.x + self.x
        r1.y = r1.y + self.y
        local r2 = sprite:getCollideRect()
        r2.x = r2.x + sprite.x
        r2.y = r2.y + sprite.y
        local intersection = r1:intersection(r2)
        local x1 = math.random(2, 4)
        local x2 = math.random(2, 4)
        local y1 = math.random(2, 4)
        local y2 = math.random(2, 4)
        self:moveBy(intersection.width / 2 + x1, intersection.height / 2 + y1)
        sprite:moveBy(-(intersection.width / 2 + x2), -(intersection.height / 2 + y2))

function ItemDrop:print()
  local type = ''
  if self.item.type == WEAPON_ITEM then
    type = 'Weapon'
  elseif self.item.type == ARMOR_ITEM then
    type = 'Armor'
  elseif self.item.type == SHIELD_ITEM then
    type = 'Shield'
  elseif self.item.type == POTION_ITEM then
    type = 'Potion'
  print('Dropped', type, self.x, self.y)

function ItemDrop:getRect()
  local rect = self:getCollideRect()

  rect.x = rect.x + self.x
  rect.y = rect.y + self.y

  return rect

function ItemDrop:pickedUpBy(x, y)
  self.isPickedUp = true
  self.pickupTarget =, y)

Here's a screenshot where you can see the full sword close the player, and the bits of the hilt below them:

Likewise here's the full video: Playdate update bug - YouTube

This looks like the sprite's dirty rect hasn't been adjusted for setDrawOffset() (which I assume you're using to move the camera around?) but I know we fixed a bug like that a while ago. I make a quick test, moving a sprite after changing the draw offset, couldn't reproduce this. What SDK version are you running? Can you post a pdx build which demonstrates this bug (or DM it to me if you don't want it public)?

Screen Shot 2023-03-29 at 1.16.47 PM (126.9 KB)
Attached a zip of the PDF for you. I'm using version 1.13.2 of the SDK. And yes, I'm using setDrawOffset for moving the camera around. If you need me to share additional code, let me know and I can DM you a zip of it.

As far as testing it, you can move up to an enemy sprite and attack them with the A button. Each enemy will always drop 3 items, there's 3 enemies in the small map there total.

Awesome, thanks so much! You found an interesting bug! :slight_smile: What's happening is I've got a shortcut where it says "if we're already redrawing the whole screen, don't bother checking the individual sprite dirty rects" and that's somehow keeping it from updating that sprite's dirty rect to reflect the offset change. I'm guessing that full-screen sprite marking the entire screen dirty is your HUD containing the health meter? If so you can use sprite:setIgnoresDrawOffset(true) to keep it anchored to the screen so you don't have to move it around. (You should also use sprite:setZIndex(<some large number>) to put it above the other sprites--right now the dropped items will be drawn above it.) I'll get this fixed as soon as I can, seems like it's pretty easy to accidentally trigger.

Here's another example showing the bug. Press A twice and you get two black squares:

gfx =
image =,40,gfx.kColorBlack)
s =

n = 0

function playdate.AButtonDown()
	if n == 0 then
		gfx.setDrawOffset(50, 50)
		gfx.sprite.addDirtyRect(0, 0, 400, 240)
		n = 1

function playdate.update()

Thanks Dave, that did the trick. I updated the sprite that i set to screen size to setIgnoresDrawOffset(true), and set its position to center screen.

function UiContainer:init()

  self:moveTo(screenWidth / 2, screenHeight / 2)

  self:setSize(screenWidth, screenHeight)
  self.healthPercentage = 1
1 Like