How to proxy SDK graphics.image

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 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, 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 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> =

local function string_has_prefix(str, prefix)
	return (string.sub(str, 1, string.len(prefix)) == prefix)

AnimatedImage = {}

function, options)
	options = options or {}
	local animated_image = {}
	setmetatable(animated_image, AnimatedImage)
	animated_image.imagetable =
	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

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()
					local cur_time = playdate.getCurrentTimeMilliseconds()
					ai.timer:update((cur_time - last_time) / 1000.0)
					ai.last_time = cur_time
			local img = ai.imagetable:getImage(ai.timer:getAnimationFrame("animation"))
			return img[key](img, ...)
		return animated_image[key]
	return proxy_value

1 Like

Have you checked that my AnimatedSprite library is not suitable for your purposes?
Yet it's sprite class inheritor, not image, but it has build in timer that you don't need to worry about~

Need to get my head around this!

Runable sample would be great.