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
if self.velocity.x == 0 then
elseif self.velocity.y == 0 then
self.diagonal = true
gfx.fillRect(0, 0, 16, 16)
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)
playdate.update() I increment my X and Y offsets (
yo) based on the velocity of the belt, making sure to mark the sprite dirty for redraw:
-- collision logic for affecting objects on belt removed for this example
self.xo -= self.velocity.x * dt
self.yo -= self.velocity.y * dt
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.
-- draw a border
gfx.drawRect(0, 0, self.width, self.height)
-- animate the belt using our chosen pattern image and time-based offsets
math.floor(self.xo) % 8,
self.diagonal and 0 or math.floor(self.yo) % 8)
gfx.fillRect(0, 0, self.width, self.height)
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.