How to play an animation continuously (Lua)

Basically, the player in my game is a sprite sheet that gets looped through continuously to make an animation. I know it has something to do with playdate.graphics.animation.loop.new() and maybe playdate.graphics.animation.loop:draw() functions, but I can't figure out how to get a sprite sheet to even display on the screen using these functions, let alone animate continuously. This animation would be on the character sprite that gets moved around with the arrow keys.

Any help would be appreciated!

Here's my take on this. It's a sprite with a continuous animation. I haven't added controls etc.—let me know if you also need help with that.

import "CoreLibs/sprites"
import "CoreLibs/animation"

-- performance shortcut
local gfx = playdate.graphics

-- Player sprite

local player = gfx.sprite.new()
player:moveTo(100, 100)

-- Let's set the imagetable for the sprite to use
-- it'll be a member of the sprite object for cleanness
player.imagetable = gfx.imagetable.new("Bicycle")

-- this handy SDK function will tell us what frame to draw
-- you just give it the delay betwen frames and what imagetable to use
-- see https://sdk.play.date/#f-graphics.animation.loop.new
player.animation = gfx.animation.loop.new(100, player.imagetable, true)

function player:update()
	-- the above animation loop will tell us what frame to use
	-- but we still have to tell the sprite TO use it when it updates
	self:setImage(self.animation:image())
	-- note that the sprite API knows when you've asked it to set the same image again,
	-- and it won't update needlessly. This means it will only redraw when really needed
end

player:add()

-- Here we go

function playdate.update()
	
	gfx.sprite.update()
	
end```
3 Likes

Also consider @Whitebrim's AnimatedSprite library.

To animate the polar bear in my TV tuner minigame I only need a few lines of code:

--initialize animation for polar bear
  --> I'm going to use Whitebrim's very convenient AnimatedSprite library
local polarbear_spritesheet = gfx.imagetable.new("Minigames/TV_Tuner/images/polar_bear")
polarbear = AnimatedSprite.new( polarbear_spritesheet )
polarbear:addState("animate", nil, nil, {tickStep = 4}, true)
polarbear:moveTo(190,120)

Then all you need is to add gfx.sprite.update() in your playdate.update() loop like in Neven's example above and you're good to go!

tv_tuner

Hope this helps!

-Drew

1 Like

Let me comment your code.

-- Get imagetable from the file and set it to the variable.
local polarbear_spritesheet = gfx.imagetable.new("Minigames/TV_Tuner/images/polar_bear")

-- Create new AnimatedSprite instance using our predefined imagetable.
-- AnimatedSprite is sprite class, that handles animation for itself,
-- no need to invoke update externally.
polarbear = AnimatedSprite.new( polarbear_spritesheet )

-- AnimatedSprite library has build-in finite state machine:
-- You can split your imagetable into states with different settings and switch between them.
-- Here we're creating new state, named "animation"
-- from the first to the last frame of the imagetable (nil, nil).
-- We set tickStep to 3 (Animation will transit to the next image frame every 4 game ticks (if game runs 30 fps then every 100ms).
-- Final "true" is for autoplay.
--
-- You can find all state settings in this wiki: 
-- https://github.com/Whitebrim/AnimatedSprite/wiki/Config-parameters
polarbear:addState("animate", nil, nil, {tickStep = 3}, true)

-- Moving sprite to x = 190, y = 120
polarbear:moveTo(190,120)

Hope this helps!

3 Likes

Thank you all! At this point I'm wondering if there's something going on with my simulator. I've tried Neven's example along with the AnimatedSprite library (which is so straightforward!), but all I can get is the spritesheet to draw to the screen showing all frames, not animated. Here's my current code (basically exactly the same as the examples above).

import "CoreLibs/sprites"
import "CoreLibs/animation"
import "AnimatedSprite.lua"

-- performance shortcut
local gfx = playdate.graphics

-- Player sprite

local player_spritesheet = gfx.imagetable.new("images/Falling-Sheet-table-160-40")
player = AnimatedSprite.new( player_spritesheet )
player:addState("animate", nil, nil, {tickStep = 4}, true)
player:moveTo(190,120)

-- Here we go

function playdate.update()
	
	gfx.sprite.update()
	
end

I don’t use the AnimatedSprite library myself, but I notice one issue: when defining an imagetable’s path, you should refer just to the part of the filename before the -table. So in your case, images/Falling-Sheet

1 Like

Thanks! Even when using your code example exactly as it is (except for updating the spritesheet image path, making that line player.imagetable = gfx.imagetable.new("images/Falling-Sheet")) just draws the spritesheet. Here's what my emulator looks like when using your code example:

Hm, maybe someone experienced with AnimatedSprite can answer that one. In the meantime, try my approach maybe? :slightly_smiling_face:

Yeah, I switched back to your code example and I'm seeing the same result. Here's the code I have, it's the same as yours but with a different image table path. The screenshot I previously posted is the result.

import "CoreLibs/sprites"
import "CoreLibs/animation"

-- performance shortcut
local gfx = playdate.graphics

-- Player sprite

local player = gfx.sprite.new()
player:moveTo(100, 100)

-- Let's set the imagetable for the sprite to use
-- it'll be a member of the sprite object for cleanness
player.imagetable = gfx.imagetable.new("images/Falling-Sheet")

-- this handy SDK function will tell us what frame to draw
-- you just give it the delay betwen frames and what imagetable to use
-- see https://sdk.play.date/#f-graphics.animation.loop.new
player.animation = gfx.animation.loop.new(100, player.imagetable, true)

function player:update()
	-- the above animation loop will tell us what frame to use
	-- but we still have to tell the sprite TO use it when it updates
	self:setImage(self.animation:image())
	-- note that the sprite API knows when you've asked it to set the same image again,
	-- and it won't update needlessly. This means it will only redraw when really needed
end

player:add()

-- Here we go

function playdate.update()
	
	gfx.sprite.update()
	
end

Wait! Are your images 40 x 40? The numbers in the imagetable filename should specify the size of one tile, not of the whole image :slightly_smiling_face: So rename it to -table-40-40. Right now it thinks there’s just one 160 × 40 frame in it.

This filename schema lets you decide on your tile size, and then expand the table size to whatever you need later, as long as it’s a multiple of those numbers.

1 Like

Yes, that was the problem! What a silly mistake to make. Thank you! I'm starting to understand how Sprites work in the SDK now.

1 Like

i can't animate this spritsheet. imagetable fonction works line by line on this.
asteroid-sprites-

How are you trying to do it? It would be best if you posted your code here.

I use exactly your code.
my file name : "asteroid-sprites-table-64-514.png"

import "CoreLibs/sprites"
import "CoreLibs/animation"

-- performance shortcut

local gfx = playdate.graphics

-- meteor sprite

local meteor = gfx.sprite.new()

meteor:moveTo(100, 100)
meteor.imagetable = gfx.imagetable.new("images/asteroid-sprites")
meteor.animation = gfx.animation.loop.new(100, meteor.imagetable, true)

function meteor:update()

self:setImage(self.animation:image())

end

meteor:add()

function playdate.update()

gfx.sprite.update()

end

playdate-20220410-215054

Your filename there looks odd. In an imagetable filename, the numbers should indicate the size of one tile, which in your case is, I think, 54 × 54. So, rename the file to asteroid-sprites-table-54-54.png.

The reason for this naming is that this way, the compiler knows what size tile to look for. Otherwise, it doesn't know where your tile boundaries are. It also means you can keep enlarging or shrinking your imaginable image without touching your code.

it's ok. Thank you :+1:

I was making the exact same mistake, but renaming the filename to reflect the individual sprite size made Neven’s code work perfectly for me.

Thank you ! It's working like a charm !