I'm new to PICO-8 so please forgive me if something simlar has already been posted (I searched and couldn't find what I wanted)
I wanted to be able to draw on the map and then have some of those map tiles animated.
The following code will animate a map tile by copying memory from a number of sprites to a main sprite which you draw on the map.
The main sprite must be immediatly followed by the animation frames, and (for simplicity of code) all frames must be on the same row of the sprite sheet. This gives a max of 15 frames of animation in a row, with 1 main sprite.
Animation is by rotation (frame 1,2,3,4,1,2,3,4 etc) if you want ping-pong (1,2,3,4,3,2,1,2,3,4 etc) you'll need to tweak the code.
Please forgive my code, I've never used LUA before - I'm used to Atari BASIC and 6502 Assembler. I'm sure this can be done better, so if someone more experianced with LUA would like to re-write it then that would be great.
There are 5 paramters of map_animator{a,b,c,d,e}
a=sprite number to animate
b=number of frames
c=current frame (set this to 1, the function will change it)
d=speed (1= every frame, 2=every 2 frames etc. if you set this to 0 the animation will stop)
e=speed counter (set it to 0, function will change it)
if you want multiple tiles to animate then add an additional group of 5 parameters
You'll need to call it once for every tile that animates, so if you have 2 tiles you do:
map_animate(1)
map_animate(2)
Hope this is useful for someone.
edit. If you don't want to use up all your sprite sheet with frames you could store the animation frames data in a table and copy it to the main tile and just have your main tile in the sprite sheet. If I'm correct in the way that integers are stored then it would take 4 tokens per frame.
edit2: I just realised that strings cost same tokens regardless of how big they are, so you could use a single string to store as many frames as you want for minimal tokens.
Hi preppie, welcome!
This is a neat idea, changing a single sprite on the sprite sheet to animate a bunch of tiles simultaneously on the map. Nice job!
There are definitely some tweaks that can be made to your code. Some of that is Lua in general and some is Pico-8 specific. Others might be able to help you reduce this even more but here are some things I noticed which are hopefully helpful.
Forward declarations
You don't need them. Mostly. There are situation where forward declarations are necessary but it doesn't come up super often so mostly you can just declare variables the first time you use them. So instead of...
local x x = 10 |
...you can just do this:
local x = 10 |
Loop counters in particular don't need to be declared ahead of time. Actually, they can't be declared ahead of time. They're automatically created when the loop starts and they're local to the loop.
i = 'hello' -- this i... for i=1,5 do -- ...and this one, are not the same variable. print(i) -- prints 1 through 5. end -- second i ceases to exist here. print(i) -- prints hello |
That said, if you prefer forward declaring all your variables there's nothing wrong with it. It does use an extra token per variable though.
Multi-dimensional arrays
Lua quite happily lets you add arrays as elements to other arrays. So your map_animator array can be written like this:
map_animator={{1,7,1,4,0},{21,7,1,8,0}} |
Apart from visually grouping your animation parameters in a nicer way, this is also practically useful thanks to the handy unpack
function which you can use like this to assign all your variables at once.
function map_animate(num) local ani_tile, ani_no_frames, ani_cur_frame, ani_time, ani_count = unpack(map_animator[num]) ... ... end |
Similarly, you can assign all your modified variables back to map_animator in a single step by just replacing the old array with a new one:
map_animator[num] = { ani_tile, ani_no_frames, ani_cur_frame, ani_time, ani_count } |
memcpy
memcpy
lets you copy arbitrary sections of memory to other areas of memory. Or you can specify the number of bytes to read/write to peek and poke. For instance your nested loop for copying the sprite can be re-written like this:
for i=0,7 do memcpy(t+(i*64), c+(i*64), 4) end |
Integer division
This one's pretty minor but it does come in handy. I think it Pico-8 specific. Lua has an integer/floor division operator //
but in Pico-8 Lua it's \
. So you can save a couple tokens and get rid of those calls to flr
local t=((ani_tile)\16)*512+(ani_tile-(ani_tile\16)*16)*4 |
Anyway, here's a cart with all those changes.
Thank you so much, there's a lot of good stuff here for this beginner Lua programmer. I knew a lot of my code could be streamlined but I didn't realise it was that bad lol
Not sure how I missed the memcpy command, that was something I was looking for.
Something else I spotted while playing around today was that the sprite sheet appears to be copied into memory before every frame because altering the sprites with pokes doesn't actually alter the sprite sheet itself. This means you don't need a 'main' sprite, it could just be the first sprite in the animation because when it's changed it gets overwritten by the sprite sheet before the next frame.
Happy to help. I was actually just coming back because I realized you can shorten your animation definitions as well. Since c and e are always initially set to 1 and 0 at the start you can leave them out initially. You just have to move those two variables so they're the last two and unpack
still works. It will assign nil
to any left-over variables and since nil
is a false-y value so you can use logical operators to set a default.
function _init() --sprite to animate --number of frames --speed (1=every frame, 2=every 2 frames .etc) --current frame --counter for speed map_animator={{1,7,4},{21,7,8}} end ... function map_animate(num) local ani_tile, ani_no_frames, ani_time, ani_cur_frame, ani_count = unpack(map_animator[num]) ani_cur_frame = ani_cur_frame or 1 ani_count = ani_count or 0 ... map_animator[num] = { ani_tile, ani_no_frames, ani_time, ani_cur_frame, ani_count } |
You can use the same trick to define functions which take default parameters.
function f(a, b, default) default = default or 0 print(a+b+default) end f(1, 2, 3) -- prints 6 f(1, 2) -- prints 3 |
Because of the way sprite data is placed in memory you need to do 8 memcpy's to move the data instead of a single mset, so it will be slower.
However, once you have changed the sprite with memcpy then every instance of that sprite on the tile map will change. In order to do this with mset you'd need to loop through every tile on the map (or at the very least, every tile on the screen) and change them when found.
It will depend on your situation, but in many cases altering the sprite via memory will be significantly faster.
This is coming from an absolute beginner at PICO-8, so I could be totaly wrong.
[Please log in to post a comment]