I've seen some use the subimg functionality in some cool ways, but I don't think anyone has put together a simple way to do sprite sheet animation, so I spent some time today making a SpriteSheet module.
You can find the module code and a layout example on my github:
https://github.com/liquid8d/attract-extra/blob/master/modules/spritesheet.nuthttps://github.com/liquid8d/attract-extra/tree/master/layouts/SpriteSheet%20ExampleBasically, you can have some simple animations using a sprite sheet without having to do your own ontick and subimg coding.
Here's a video of it in action:
http://youtu.be/4WAQuwWIAlkI also uploaded the joystick and button examples here in my extras folder if you want them. You are of course free to use and modify for whatever:
https://github.com/liquid8d/attract-extra/tree/master/extrasIf you want to peruse the code:
///////////////////////////////////////////////////
//
// Attract-Mode Frontend - "spritesheet" module
//
// SpriteSheet allows you to create animations from an image with multiple "sprites"
//
// Usage:
// //create an image object with your spritesheet
// local joy = fe.add_image("joystick-move.png");
// //create a SpriteSheet instance with your object and sprite size
// local sprite = SpriteSheet(joy, 128, 128);
// //settings
// sprite.orientation = "vertical"; //orientation of your sprite frames, default is horizontal
// sprite.repeat = "yoyo"; //a number or either loop or yoyo, default is loop
// sprite.spf = 1; //seconds per frame, default is 1
// sprite.order = [ 3, 0, 1, 3 ]; //optional array with a custom frame index order
//
// //control
// sprite.frame(0); //set to a specific frame
// sprite.reset(); //reset the animation
// sprite.stop(); //stop the animation
// sprite.start(); //start the animation
//
// //info
// local last = sprite.last_frame(); //will tell you what the last frame index is
// local next = sprite.next_frame(); //will tell you what the next frame index will be
// local prev = sprite.prev_frame(); //will tell you what the next frame index will be
// local played = sprite.playCount(); //will tell you the number of times the animation has played (reset on reset)
///////////////////////////////////////////////////
const SPRITESHEET_VERSION=1;
class SpriteSheet
{
//TODO
//single row/column only for now - implement row/columns as frames based on texture size
//bug - doing last frame again after stop?
//spf or fps?
//settings you care about
orientation = "horizontal"; //either horizontal or vertical spritesheet
width = 0; //sprite height
height = 0; //sprite width
spf = 1; //seconds per frame
order = null; //an array with a custom frame order to use from the spritesheet
repeat = "loop"; //a number or either loop or yoyo
//internal stuff
mObj = null; //object to animate
mOffset = 0; //current texture offset, or array index for a frame array
mTimer = 0; //timer to watch elapsed time
mRunning = false; //is animation running?
mReverse = false; //whether we are running animation in reverse
mPlayed = 0; //number of times animation has played
constructor( obj, w, h = null )
{
mObj = obj;
width = w;
height = ( h == null ) ? width : h;
//set initial sprite size
mObj.subimg_width = width;
mObj.subimg_height = height;
fe.add_ticks_callback( this, "on_tick" );
}
function start()
{
mRunning = true;
}
function stop()
{
mRunning = false;
}
function reset()
{
//reset to first frame
mPlayed = 0;
frame( 0 );
}
function playCount()
{
return mPlayed;
}
function on_tick( ttime )
{
if ( mObj != null && mRunning )
{
local elapsed = ttime - mTimer;
if ( elapsed > spf * 1000 )
{
mTimer = ttime - ( elapsed - ( spf * 1000 ) );
//check if we need to reverse the animation for yoyo repeat
if ( repeat == "yoyo" ) mReverse = (mReverse && prev_frame() == last_frame() || !mReverse && next_frame() == 0) ? mReverse = !mReverse : mReverse;
//show the next frame
local playFrame = ( mReverse ) ? prev_frame() : next_frame();
if ( mReverse && playFrame == last_frame() || !mReverse && playFrame == 0) mPlayed += 1;
if ( typeof repeat == "integer" && mPlayed > repeat) stop();
frame( playFrame );
//print( "frame: " + mTimer + " offset: " + mOffset + " reverse: " + mReverse + " played: " + mPlayed + "\n" );
}
}
}
//shows a specific sprite frame
function frame(which)
{
mOffset = which;
switch ( orientation ) {
case "horizontal":
mObj.subimg_x = ( order == null ) ? mOffset * width : order[mOffset] * width;
break;
case "vertical":
mObj.subimg_y = ( order == null ) ? mOffset * height : order[mOffset] * height;
break;
}
}
function last_frame()
{
if ( order == null )
{
switch ( orientation ) {
case "horizontal":
return (mObj.texture_width - width) / width;
case "vertical":
return (mObj.texture_height - height) / height;
}
} else
{
return order.len() - 1;
}
}
//finds out which is the previous frame offset based on settings
function prev_frame()
{
if ( order == null )
{
//iterate each sprite frame until we reach the beginning
switch ( orientation ) {
case "horizontal":
if ( mOffset > 0 ) return mOffset - 1; else return (mObj.texture_width - width) / width;
case "vertical":
if ( mOffset > 0 ) return mOffset - 1; else return (mObj.texture_height - height) / height;
}
} else
{
//get the previous sprite frame in a custom array, or the last if we reach the beginning
if ( mOffset > 0 ) return mOffset - 1; else return order.len() - 1;
}
}
//finds out which is the next frame offset based on settings
function next_frame()
{
if ( order == null )
{
//iterate each sprite frame until we reach the end
switch ( orientation ) {
case "horizontal":
if ( mOffset * width < mObj.texture_width - width ) return mOffset + 1; else return 0;
case "vertical":
if ( mOffset * height < mObj.texture_height - height ) return mOffset + 1; else return 0;
}
} else
{
//get the next sprite frame in a custom array, or the first if we reach the beginning
if ( mOffset < order.len() - 1 ) return mOffset + 1; else return 0;
}
}
}
and the layout:
fe.load_module("spritesheet");
local bg = fe.add_image("bg.png", 0, 0, fe.layout.width, fe.layout.height);
local logo = fe.add_image("logo.png");
logo.set_pos(fe.layout.width / 2 - logo.texture_width / 2, 20);
local list = fe.add_listbox(fe.layout.width / 2, logo.y + logo.texture_height, fe.layout.width / 2, fe.layout.height);
list.charsize = 18;
local joy = fe.add_image("joystick-move.png");
joy.set_pos(logo.x, logo.texture_height + 50);
local sprite = SpriteSheet(joy, 128);
//sprite.orientation = "vertical"; //default is horizontal
sprite.order = [ 0, 3, 0, 3, 0, 4, 0, 4, 0, 1, 0, 2, 0, 1, 0, 2, 0];
sprite.repeat = "loop";
sprite.spf = 0.25;
sprite.frame(0);
sprite.start();
local button = fe.add_image("button-press-white.png");
button.set_pos(logo.x + 128 + 50, joy.y + 10);
local sprite2 = SpriteSheet(button, 128);
sprite2.repeat = "yoyo";
sprite2.order = [ 0, 0, 1, 2, 3, 4, 5, 5 ];
sprite2.spf = 0.2;
sprite2.frame(0);
sprite2.start();
local button2 = fe.add_image("button-press-red.png");
button2.set_pos(button.x + 96, button.y);
local sprite3 = SpriteSheet(button2, 128);
sprite3.frame(0);
sprite3.repeat = "loop";
sprite3.spf = 0.5;
sprite3.order = [ 0, 5, 0, 5, 0, 0, 5 ];
sprite3.start();