I was looking for a way to use animated images with very little hassle and little work. That is, I wanted to be able to swap a static playdate.graphics.image
for an animated one and have it just work. No calls to update a timer, etc.
Thought I'd share an interesting approach which isn't quite a subclass, but more of a proxy. That is, it is a standalone object that forwards undefined methods to the current frame image object. So you can use any function supported by playdate.graphics.image
, and when you call any image draw methods, the correct frame of your animation will be drawn.
It works by catching called to undefined methods and then wrapping that call in a method and storing that method on the animated image object. It's more or less a class that builds its methods out lazily. So it will only proxy methods from playdate.graphics.image
that you use.
Now, in this example, I'm using my own timer class, but you get the idea. If there's interest, I may pull the appropriate bit of code from my timer class so this can stand alone, but you could swap it out for the SDK's animation object easily enough.
local graphics <const> = playdate.graphics
local function string_has_prefix(str, prefix)
return (string.sub(str, 1, string.len(prefix)) == prefix)
end
AnimatedImage = {}
function AnimatedImage.new(path, options)
options = options or {}
local animated_image = {}
setmetatable(animated_image, AnimatedImage)
animated_image.imagetable = graphics.imagetable.new(path)
animated_image.timer = Chrono()
animated_image.last_time = 0
-- Set default options.
local animation_options = {
bounce = options.bounce,
count = options.count,
after = options.after,
decay = options.decay,
start_frame = options.start_frame
}
animated_image.timer:animation(options.delay or 0.1, animated_image.imagetable, options, "animation")
return animated_image
end
AnimatedImage.__index = function(animated_image, key)
local proxy_value = animated_image.imagetable:getImage(animated_image.timer:getAnimationFrame("animation"))[key]
if type(proxy_value) == "function" then
local is_draw_call = string_has_prefix(key, "draw")
rawset(animated_image, key, function(ai, ...)
if is_draw_call then
local last_time = ai.last_time
if last_time == 0 then
ai.last_time = playdate.getCurrentTimeMilliseconds()
else
local cur_time = playdate.getCurrentTimeMilliseconds()
ai.timer:update((cur_time - last_time) / 1000.0)
ai.last_time = cur_time
end
end
local img = ai.imagetable:getImage(ai.timer:getAnimationFrame("animation"))
return img[key](img, ...)
end)
return animated_image[key]
end
return proxy_value
end