Well, as usual, I should have played with this for a few more hours before posting. I think I've figured out the appropriate solution, but I'd welcome improvements. I wound up pushing a graphics context for a new image which I render the dither pattern into. I'm choosing a dither pattern based on the specified velocity, which impacts both pattern direction and the animation speed. Here's the setup:
self.beltImage = gfx.image.new(16, 16) -- twice the size of the 8x8 pattern we'll extract
gfx.pushContext(self.beltImage)
gfx.setColor(gfx.kColorBlack)
if self.velocity.x == 0 then
gfx.setDitherPattern(0.5, gfx.image.kDitherTypeHorizontalLine)
elseif self.velocity.y == 0 then
gfx.setDitherPattern(0.5, gfx.image.kDitherTypeVerticalLine)
else
gfx.setDitherPattern(0.5, gfx.image.kDitherTypeDiagonalLine)
self.diagonal = true
end
gfx.fillRect(0, 0, 16, 16)
gfx.popContext()
This approach has the benefit of enabling rotation, which is needed to account for diagonal belts which run orthogonal to the diagonal dither pattern.
-- rotate the image to acount for diagonals orthogonal to the dither pattern
if (velocity.x < 0 and velocity.y > 0) or (velocity.x > 0 and velocity.y < 0) then
self.beltImage = self.beltImage:rotatedImage(90)
end
In playdate.update()
I increment my X and Y offsets (xo
and yo
) based on the velocity of the belt, making sure to mark the sprite dirty for redraw:
function Belt:update()
Belt.super.update(self)
-- collision logic for affecting objects on belt removed for this example
self.xo -= self.velocity.x * dt
self.yo -= self.velocity.y * dt
self:markDirty()
end
Finally, I sample this pattern image using the offsets when drawing the sprite. Because the pattern doesn't shift every frame, these offsets are incremented as floats. As such, they need to be floored and modded by 8 to decide which 8x8 section of our 16x16 pattern image to draw.
function Belt:draw()
gfx.pushContext()
-- draw a border
gfx.setColor(gfx.kColorBlack)
gfx.setLineWidth(2)
gfx.drawRect(0, 0, self.width, self.height)
-- animate the belt using our chosen pattern image and time-based offsets
gfx.setPattern(self.beltImage,
math.floor(self.xo) % 8,
self.diagonal and 0 or math.floor(self.yo) % 8)
gfx.fillRect(0, 0, self.width, self.height)
gfx.popContext()
end
The one oddity in there is the "ternary" used to zero out the Y offset for diagonal belts. We only need to move a diagonal pattern in one axis to animate it, and in fact moving it in both axes causes an unwelcome flashing effect. There might be a cleaner way to deal with that, but it works.
I hope someone finds this helpful! Let me know if you have a better way to achieve this effect.