Sprite:update stops being called when the sprite's animator completes

This is on:

  • macOS 12.3.1
  • SDK 1.10.0

Context

I'm making a game that plays on a grid with a cursor you can move around. When the player taps on the d-pad, the cursor animates between positions. Not only that, but the "color" of the cursor changes based on what square the cursor's on, like on a checkerboard. I want to animate both the cursor's position and its color.

The bug occurs when the color animation outlasts the position animation.

The Code

When the user taps a direction, I create animators for both the position and the color. I use sprite:setAnimator for the position, but have to use my own property for the color animator. (Math omitted for brevity)

function Cursor:movePosition(newX, newY)
    // ...
    local anim = gfx.animator.new(positionTransitionDuration, oldPos, newPos, playdate.easingFunctions.linear)
    self:setAnimator(anim)

    // ...
    self.colorAnimator = gfx.animator.new(colorTransitionDuration, fromColor, toColor, playdate.easingFunctions.linear)
end

Then, in sprite:draw I use the colorAnimator I set to change the dithering pattern I draw with. When that animator ends, I save it to the sprite's image to avoid re-drawing. In sprite:update, I see if the sprite's been moved -- if it has, I remove the image and mark it as dirty so the color can animate again.

-- Compute x/y based on position, re-draw if it's moved.
function Cursor:update()
    local x = self.gameRect.x + self.position.x * self.xStep
    local y = self.gameRect.y + self.position.y * self.yStep
    if x ~= self.x or y ~= self.y then
        self:setImage(nil)
        self:markDirty()
    end
end

-- Draw a black border with dithering
function Cursor:draw(x, y, width, height)
    local image = gfx.image.new(width, height)
    gfx.pushContext(image)
    gfx.drawRect(x, y, width, height)
    local ditherValue = self.colorAnimator:currentValue()
    gfx.setDitherPattern(ditherValue)
    gfx.fillRect(x, y, width, height)
    gfx.popContext()

    -- Always draw. Without it, it flickers in the last frame.
    print("Drawing " .. ditherValue)
    image:draw(x, y)

    -- If we're done animating, then save the image.
    if self.colorAnimator:ended() then
        print("Preserving " .. ditherValue)
        self:setImage(image)
    end
end

Messing with Animation timings

If both the position and color animations take the same amount of time, it works great.

positionTransitionDuration = 500.0
colorTransitionDuration = 500.0

equal-durations

However, if I want the color animation to occur more slowly -- to continue changing color after the cursor as finished moving, it just... stops changing color as soon as the position animation ends

positionTransitionDuration = 500.0
colorTransitionDuration = 2000.0

inequal-animations


It looks like sprite:update just stops being called once the animator set in sprite:setAnimator ends.

Test Project

You can try it for yourself with the attached code. Thanks!
test-custom-drawing-sprite.zip (101.1 KB)

update is still firing, which can be seen by putting print("Cursor:update") at the start of that update function.

The problem is because you only mark dirty if the x/y coordinates change.
So as soon as the sprite stops moving it's no longer marked as dirty, and the fade seems to stop (though the fade is still happening behind the scenes you don't see any changes).

If you comment out if x ~= self.x or y ~= self.y then it works as intended.

Of course, your real fix should be more optimised!

Ah dangit. That's what I missed. This seems to fix it entirely

    if x ~= self.x or y ~= self.y or not self.colorAnimator:ended() then
        self:setImage(nil)
        self:markDirty()
    end

Thank you!

1 Like