Dynamic Retina SpriteSheets? Here's how!

I have to say I’m extremely pleased with the update if just because I can now finally add retina spritesheets to my game and thus having complete support for highres displays! Since I’m using even spaced SpriteSheets it was easy to do. If you’re using custom sizes it should still be possible, but you’re on your own.

Here’s how:

[lua]if display.contentScaleX == .5 then --this wonderful new API will let you know if you’re scaling your content and thus if it’s playing on a highres device. Since my config.lua has an x size of 480 and the device x is 960, we get the .5.

sheet = sprite.newSpriteSheet( “sprite2.png”, 512, 512 ) --the sprite2 (or whatever you wanna name it) should be 2x the normal sprite.png
else
sheet = sprite.newSpriteSheet( “sprite.png”, 256, 256 )
end

spriteSet = sprite.newSpriteSet(sheet, 1, 8)
sprite.add( spriteSet, “animate”, 1, 8, 1000, 0 ) – play 8 frames every 1000 ms

instance = sprite.newSprite( spriteSet )

if display.contentScaleX == .5 then --scale the highres spritesheet if you’re on a retina device.
instance.xScale = .5; instance.yScale = .5
end
instance.x = display.contentWidth / 2
instance.y = display.contentHeight / 2

instance:prepare(“animate”)
instance:play()
[lua]Ta da! Now you have beautiful high res spritesheets on your iphone4 (or 4th gen ipod touch), watch out for memory usage though, it can be a lot higher.
[import]uid: 10835 topic_id: 4284 reply_id: 304284[/import]

Hey Ig, was there a recent update? Where do we find out about updates? [import]uid: 7863 topic_id: 4284 reply_id: 13303[/import]

Dude, under what rock have you been living?

http://www.anscamobile.com/corona/whats-new.html
[import]uid: 10835 topic_id: 4284 reply_id: 13305[/import]

LOL a very large rock by the looks of things. Awesome!! Nice surprise to find this out [import]uid: 7863 topic_id: 4284 reply_id: 13306[/import]

display.contentScaleX and display.contentScaleY not listed in the API [import]uid: 9371 topic_id: 4284 reply_id: 13350[/import]

They’re a bit slow updating docs. Check the API tuneup page. [import]uid: 3953 topic_id: 4284 reply_id: 15796[/import]

What I’ve been doing is similar to this, but I’ve found it’s easier to concatenate a suffix so I can append it to both the sprite .png and the sprite data sheet:

[lua]local suffix = “”
local displayScale = display.contentScaleX

if displayScale < 1 then --this way it scales for iPad as well as iPhone
suffix = “@2x
end

local sprites = spriteGrabber.grabSheet(“spriteSheetData”…suffix,“img/spriteSheets/spriteSheet”…suffix…".png")

local mySprite = sprites:grabSprite(“spriteName”,true)
mySprite.xScale, mySprite.yScale = displayScale, displayScale
mySprite.x, mySprite.y = display.contentCenterX, display.contentCenterY[/lua]

The other thing that this allows is you can store the suffix variable globally ( _G.suffix = “” ) so you only have to check your scale at the beginning of your application.

Cheers!

a. [import]uid: 12405 topic_id: 4284 reply_id: 15849[/import]

Nice Adam! Works great with Texture Packer sheets! [import]uid: 10361 topic_id: 4284 reply_id: 28058[/import]

this allows is you can store the suffix variable globally ( _G.suffix = “” ) so you only have to check your scale at the beginning of your application.

duh why didn’t I think of that

thanks! [import]uid: 12108 topic_id: 4284 reply_id: 28142[/import]

Hi y’all,
I tried playing with the settings from the first post and added physics to it; although it works, I noticed when I set draw mode to ‘hybrid’, that the object is huge (dimensions of @2x sprisheet), but the content is scaled down. Therefore, giving the illusion that the object is floating.

any tips on how to fix this?

Thanks,
RD [import]uid: 7856 topic_id: 4284 reply_id: 30539[/import]

@adam,
I tried your code as is (except replace the texturepackage reference name and png file to ‘car’) and I get the following error:

Failed to find image(car@2xcar@2x.png)

If I change to this:

local sprites = spriteGrabber.grabSheet("car"..suffix)  

It all works fine; however, I still have the same issue as several hours ago…the object is huge (dimensions of @2x sprisheet), but the content is scaled down. Therefore, giving the illusion that the object is floating.

To further elaborate, I have a spritesheet 1024 x 1024 named car@2x.png and a small one 512 x 512 name car.png.

I also added physics to mySprite:
physics.addBody( mySprite, { density = 0, friction = 0, bounce = 0} )

This is where I see the bix box around the image.
This is not being scaled down.

Any help would be appreciated.

Thanks,
RD [import]uid: 7856 topic_id: 4284 reply_id: 30562[/import]

Hi everyone. I’m using the sane method to the one above where a suffix is determined by the contentScale.

I’m having a problem where the @2x image gets scaled down but it still animates as if it were large, i.e the change between each frame is double what it should be.

Is there something you guys do to the high res spritesheet in TexturePacker to prevent this or is it a problem only i have? I can provide an example if you want?

Cheers! [import]uid: 13522 topic_id: 4284 reply_id: 30810[/import]

I’m now running into an issue. I have a sprite sheet where the images are too tall to include on one sheet uniformly so I have TP trimming off the transparent space when necessary. No big deal. However when I try and scale these non-uniform sized sprite frames it jumps the whole thing up and down based on the different vertical sizes. How do I fix this for the @2x version?

Any ideas?

This method works dandy for sprite frames that never change size… But if the frame is a different size I don’t see how you can pull this off.

Will I have to rethink my sheet layout? [import]uid: 10361 topic_id: 4284 reply_id: 32811[/import]

Read my comment (#11) here: http://blog.anscamobile.com/2011/04/whoa-is-right-our-corona-sdk-gets-major-update/#comments

…and this thread: http://developer.anscamobile.com/forum/2011/01/08/sprites-scaling-very-annoying-bug [import]uid: 7356 topic_id: 4284 reply_id: 32812[/import]

Oh… right. Sorry for not finding those in the search. I see it’s a big issue. That sucks.

Hopefully ansca can put their heads together and get this worked out… As much as I was excited for GameCenter… That’s not nearly as important as something like this.

Ansca? [import]uid: 10361 topic_id: 4284 reply_id: 32816[/import]

This a workaround I made for the spriteSheet problem until it is solved al low-level.

It is a module that works very similar to the Corona sprite module with the following differences:

  1. You need to call the init(suffix_table) function as the first step to provide a suffix table. Commonly you will copy and paste the same table you used in config.lua.

  2. There is no newSpriteSheetFromData function. Instead use newSpriteSheet(fileName[, frameWidth, frameHeight]) for both uniform and non-uniform(lua based) sprite sheets. Use frameWidth and frameHeight only for uniform spriteSheets

  3. The newSprite(spriteSet, imageWidth, imageHeight) function receives additional imageWidth and imageHeight parameters and works like the display.newImageRect() function

  4. To dispose spriteSheets use the disposeSpriteSheet(spriteSheet) function instead of spriteSheet:dispose()

  5. Every time you change x, y, xScale, yScale, rotation of the sprite or any ascendants call sprite:invalidate() to force a redraw on next frame

  6. The result of newSprite is not really an sprite. It is a group with an sprite inside. Use getCurrentFrame(), setCurrentFrame(), isAnimating(), play(), prepare(), dispose() methods to acces the real sprite properties.

  7. You can use the newSpriteAnimation function to create an spriteSheet, spriteSet, sequence and sprite all in a single step

Comments will explain most of it:

[lua]–[[
– By: juankpro
– spriteSheetFix fixes bug when treating uniform and non-uniform spriteSheets
– for multiple target devices. This is not supported naturally.

– 1) First you must include the module:

local spriteSheetFix = require(“spriteSheetFix”);

– 2) Then you must configure suffixes. This is an only-one time process.
– You tipically create a suffix table like the one used in config.lua
– and then register this table using the init() function:

imageSuffix =
{
["@2"] = 2,
["-ipad"] = 2.1,
["-foo"] = 3,
};

spriteSheetFix.init(imageSuffix);

– 3) Now you can start using the spriteSheets

– Usage 1: You can use the module in a very similar fashion as the Corona sprite module.

– Create a spriteSheet. The fix will look for a Lua file with the same name as the PNG file
– (e.g. sample.png sample.lua for iPod, sample@2.png and sample@2.lua for iPod4, etc)
– To create regular spriteSheets add a frameWidth and frameHeight pàrameters
local spriteSheet = spriteSheetFix.newSpriteSheet(“sample.png”);

– Create a spriteSet defining frames inside the spriteSheet
– (spriteSheet, startFrame, frameCount)
local spriteSet = spriteSheetFix.newSpriteSet(spriteSheet, 1, 21);

– Register a sequence by name defining frames inside the spriteSet
– (spriteSet, sequenceName, startFrame, frameCount, playTime, loop)
spriteSheetFix.add(spriteSet, “c1”, 1, 21, 700, true);

– Create the sprite and define the space to occupy in the base device (commonly ipod 340 x 480)
(spriteSet, rectWidth, rectHeight)
local sprite = spriteSheetFix.newSprite(spriteSet, 90, 56);

– Prepare the sequence to use
sprite:prepare(“c1”);

– Usage 2: You can set all parameters at once using the newSpriteAnimation() method.

– Here you define rectWidth, rectHeight, spriteSheet parameters,
– spriteSet parameters and sequence parameters.
– In a similar way as in Usage 1.
local sprite, spriteSheet = spriteSheetFix.newSpriteAnimation(
90,
56,
{
fileName = “cavando.png”,
},
{
startFrame = 1,
frameCount = 21,
},
{
name = “c1”,
startFrame = 1,
frameCount = 21,
playTime = 700,
loop = true,
}
);

– After this you can:

– play/pause the animation
sprite:play();
sprite:pause();

– get and set animation properties
sprite:isAnimating()
sprite:getCurrentFrame();
sprite:setCurrentFrame(3);

– read animation real size (not scaled and not frame size)
sprite:getAnimationWidth();
sprite:getAnimationHeight();

– to properly clean memory used by the sprite call
sprite:dispose();

– if you want to clean memory used by a spriteSheet disposeSpriteSheet()
– this will clear all sprites, spriteSets and sequences related to the
– spriteSheet.
spriteSheetFix.disposeSpriteSheet(spriteSheet);

– The fix uses an invalidation model to fix the spriteSheet position and scale.
– This models prevent running cpu-intensive calculations all the time.
– If you change the x, y, xScale, yScale, and rotation properties in the sprite
– or any of its ascendants you should call invalidate to forcce redrawing on next frame.
sprite:invalidate();

– Use drawNow() to draw immediately and not in the next frame (use sparingly)
sprite:drawNow();

– Use validateNow() to invalidate and draw immediately
– and not in the next frame (use sparingly)
sprite:drawNow();

Note:
The sprite returned is not really a sprite.
It is a group with an sprite inside and added some methods to access
sprite properties and methods.
To access the real sprite use the sprite property.
sprite.sprite.addEventListener(“sprite”, handler);
]]

module(…, package.seeall)

local sprite = require(“sprite”);

local listenerMap = {};

local spriteSheetDataMap = {};
local spriteSetDataMap = {};

suffix = nil;

–[[
Calculate a concatenated transfrom up to the stage object.
]]
local function getConcatenatedTransform(sprite)
local t = {x = 0, y = 0, xScale=1, yScale=1, rotation=0};
local obj = sprite;
repeat
t.x = t.x + obj.x;
t.y = t.y + obj.y;
t.xScale = t.xScale * obj.xScale;
t.yScale = t.yScale * obj.yScale;
t.rotation = t.rotation + obj.rotation;
obj = obj.parent;
until obj == nil
return t;
end

–[[
Transform a point using a transform object
]]
local function transformPoint(point, transform)
– Scale point
point.x = point.x * transform.xScale;
point.y = point.y * transform.yScale;

– Rotate point:
– First we find the length
local length = math.sqrt(point.x * point.x + point.y * point.y);
– Then we find the new angle
local rads = math.atan2(point.y, point.x);
rads = rads + (transform.rotation / 180 * math.pi);
– We re-calculate the point with the angle and length
point.x = math.cos(rads) * length;
point.y = math.sin(rads) * length;
return point;
end

–[[
Returns a frame in the spriteSheet given a spriteSet frame.

sequenceFrame: The frame in the spriteSet.
sequenceData: The sequence data to use to determine spriteSheet frame.
]]
local function getFrameInSpriteSet(sequenceFrame, sequenceData)
return (sequenceFrame - 1) + sequenceData.startFrame;
end

–[[
Returns a frame in the spriteSheet given a sprite frame.

sequenceFrame: The frame in the sequence.
sequenceData: The sequence data to use to determine spriteSheet frame.
spriteSetData: The spriteSet datato use to determine spriteSheet frame.
]]
local function getFrameInSpriteSheet(sequenceFrame, spriteSetData, sequenceData)
local frameInSpriteSet = getFrameInSpriteSet(sequenceFrame, sequenceData);
return (frameInSpriteSet - 1) + spriteSetData.startFrame;
end

–[[
Draws the sprite to fix its position everytime the sprite has been invalidated
or when a the currentFrame has changed
]]
local function draw(target, event)
– Uniform spriteSheets don’t do anything
if target.uniform then
return;
end

– Only if invalid
if target.isInvalid or (target.animating and target.previousFrame ~= target.currentFrame) then
– Reset location
target.x = 0;
target.y = 0;

– calculate concatenated transforms up to the stage
local transform = getConcatenatedTransform(target);

– cache important data
local spriteSheetData = target.spriteSheetData;
local sequenceData = target.sequenceData;
local spriteSetData = target.spriteSetData;
local frameInSpriteSheet = getFrameInSpriteSheet(target.currentFrame, spriteSetData, sequenceData);
local frameData = spriteSheetData.frames[frameInSpriteSheet];

– Untransformed Animation size
local animWidth = frameData.spriteSourceSize.width;
local animHeight = frameData.spriteSourceSize.height;

– Untransformed position
local xAnim = -(animWidth / 2);
local yAnim = -(animHeight / 2);

– frame position relative to untransformed position
local xFrame = frameData.spriteColorRect.x;
local yFrame = frameData.spriteColorRect.y;

– frame width (each frame has a different with)
local widthFrame = frameData.spriteColorRect.width;
local heightFrame = frameData.spriteColorRect.height;

– Frame center
local xFrameCenter = xAnim + xFrame + (widthFrame / 2);
local yFrameCenter = yAnim + yFrame + (heightFrame / 2);

– current frame “wrong” position
local pos = {x = - ((widthFrame / 2) + xFrame),
y=- ((heightFrame / 2) + yFrame)};
pos = transformPoint(pos, transform);

local xAnimReal = xFrameCenter + pos.x;
local yAnimReal = yFrameCenter + pos.y;

– “right” frame position
local fixedPos = transformPoint({x=-(animWidth / 2), y=-(animHeight / 2)}, transform);
fixedPos.x = fixedPos.x;
fixedPos.y = fixedPos.y;

– offset from “right” position to “wrong” position
local xOffset = fixedPos.x - xAnimReal;
local yOffset = fixedPos.y - yAnimReal;

– Fix position here
target.x = xOffset / target.parent.xScale;
target.y = yOffset / target.parent.yScale;

– Revert parent changes
local rads = math.atan2(target.y, target.x);
local length = math.sqrt(target.x * target.x + target.y * target.y);
target.x = math.cos(rads - (target.parent.rotation / 180 * math.pi)) * length;
target.y = math.sin(rads - (target.parent.rotation / 180 * math.pi)) * length;

– Release invalidation flags
target.isInvalid = false;
target.previousFrame = target.currentFrame;
end
end

–[[
This method is added to the returned sprite.
Tells the sprite to redraw on the next draw process
]]
local function invalidate(me)
me.sprite.isInvalid = true;
end

–[[
This method is added to the returned sprite.
Forces a redraw an the sprite whether invalidate()
has been called before or not.
]]
local function validateNow(me)
me:invalidate();
me.sprite:enterFrame();
end

–[[
This method is added to the returned sprite.
Forces a redraw an the sprite but only if invalidate()
has been called before
]]
local function drawNow(me)
me.sprite:enterFrame();
end

–[[
This method is added to the returned sprite.
Determines if the sprite is currently animating
]]
local function isAnimating(me)
return me.sprite.animating;
end

–[[
This method is added to the returned sprite.
Allows reading the current frame.
]]
local function getCurrentFrame(me)
return me.sprite.currentFrame;
end

–[[
This method is added to the returned sprite.
Allows setting the current frame.
]]
local function setCurrentFrame(me, value)
me.sprite.currentFrame = value;
me:invalidate();
end

–[[
This method is added to the returned sprite.
Pauses the currently running animation
]]
local function pause(me)
me.sprite:pause();
me:invalidate();
end

–[[
This method is added to the returned sprite.
Plays a sequence previously prepared by calling the prepare() method.
]]
local function play(me)
me.sprite:play();
me:invalidate();
end

–[[
This method is added to the returned sprite.
Prepares a named sequence to be used for animation.
The named sequence must be created before calling this method.

sequence: The name of the sequence to start playing.
]]
local function prepare(me, sequence)
local sprite = me.sprite;
local spriteSetData = sprite.spriteSetData;

sprite:prepare(sequence);

if not sprite.uniform then
local sequenceList = spriteSetData.sequences;
sprite.sequenceData = sequenceList[sequence];

Runtime:addEventListener(“enterFrame”, sprite);

sprite:enterFrame();
end

sprite.xScale = sprite.imageWidth / me:getAnimationWidth();
sprite.yScale = sprite.imageHeight / me:getAnimationHeight();
end

–[[
This method is added to the returned sprite.
Enabled proper garbage collection of the spriteSheet.
Call it always after you have finished working with it.

If you call disposeSpriteSheet function first all sprites
referencing the spritesheet will call this method automatically
so you don’t have to worry.
]]
local function dispose(me)
Runtime:removeEventListener(“enterFrame”, me.sprite);
end

–[[
This method is added to the returned sprite.
Allows getting the real animation width as defined by the Lua file, if
a non-uniform spriteSheet is used (Lua file), or defined by the frameWidth when
a uniform spriteSheet is used.

You should call this method on the sprite after calling the prepare method.
]]
local function getAnimationWidth(me)
if me.sprite.spriteSheetData.frames == nil then
return me.sprite.spriteSheetData.frameWidth;
elseif me.sprite.sequenceData then
local frameInSpriteSheet = getFrameInSpriteSheet(1, me.sprite.spriteSetData, me.sprite.sequenceData);
return me.sprite.spriteSheetData.frames[frameInSpriteSheet].spriteSourceSize.width;
end
return 0;
end

–[[
This method is added to the returned sprite.
Allows getting the real animation height as defined by the Lua file, if
a non-uniform spriteSheet is used (Lua file), or defined by the frameHeight when
a uniform spriteSheet is used.

You should call this method on the sprite after calling the prepare method.
]]
local function getAnimationHeight(me)
if me.sprite.spriteSheetData.frames == nil then
return me.sprite.spriteSheetData.frameHeight;
elseif me.sprite.sequenceData then
local frameInSpriteSheet = getFrameInSpriteSheet(1, me.sprite.spriteSetData, me.sprite.sequenceData);
return me.sprite.spriteSheetData.frames[frameInSpriteSheet].spriteSourceSize.height;
end
return 0;
end

–[[
Inserts a suffix into a table in the right ordered position
]]
local function insertSuffix(t, elem)
local pushed = false;
– Look for the right position and insert
for k,v in ipairs(t) do
if v.value > elem.value then
table.insert(t, k, elem);
pushed = true;
break;
end
end

if not pushed then
table.insert(t, elem);
end
end

–[[
Splits an URI into filePath and extensions
]]
local function splitUri(fileName)
local index = 0;
local savedIndex = nil;

– Look for the last point
repeat
savedIndex = index;
index = fileName:find(".", index + 1, true);
until index == nil

local parts = {};

– If point found
if savedIndex ~= 0 then
– Get the ext and fileName
parts.ext = fileName:sub(savedIndex);
parts.fileName = fileName:sub(1, savedIndex - 1);
else
– Otherwise set the whole string as the fileName
parts.fileName = fileName;
end

return parts;
end

–[[
Gets the Lua data for a sprite sheet using a name and a precalculated suffix
]]
local function getSpriteSheetData(fileName)
– Load the spritesheet data from lua
local sheetFile = require(fileName…suffix.key);
local sheetData = sheetFile.getSpriteSheetData();

return sheetData;
end

–[[
Initializes the fix with a resolution table to map
between a suffix and a resolution.
We need this function because of the lack of an Corona API
to access the configuration data.

t: The table typically reflects the table used in the config.lua file.
]]
function init(t)
local suffixList = {};
local vals = {};
local count = 1;
– Order suffixes by percentage value
for k,v in pairs(t) do
local suffixElem = {key = k, value = v};
insertSuffix(suffixList, suffixElem);
end

– Get the current scale
local xScale = 1 / display.contentScaleX;

– Get the suffix depending on scale
for k,v in ipairs(suffixList) do
if v.value > xScale then
if k > 1 then
suffix = suffixList[k - 1];
else
suffix = {key="", value=1};
end
break;
end
end
if suffix == nil then
suffix = suffixList[#suffixList];
end
end

–[[
Creates a new sprite sheet either uniform by setting the
frameWidth and frameHeight parameters or non-uniform by
setting only the fileName parameter.

fileName: Is the file name of the image to use as a spriteSheet.
If no other parameter is set it will also load a lua file
with the same name of the image file.
This method loads images and lua files by appending a suffix
depending on device resolution and rules loaded by init.
frameWidth: Is the width of each frame used for uniform spriteSheets.
frameHeight: Is the height of each frame used for uniform spriteSheets.
]]
function newSpriteSheet(fileName, frameWidth, frameHeight)
–Use always cached spriteSheets
for k,v in pairs(spriteSheetDataMap) do
if v.name == fileName then
return k;
end
end

– URI parts
local uri = splitUri(fileName);
local spriteSheet;

if uri.ext == nil then
uri.ext = “.png”
end

– The new image file name
local imageFileName = uri.fileName…suffix.key…uri.ext;
local sheetData;

– If the spriteSheet is uniform (defines frameWidth and frameHeight)
if type(frameWidth) == “number” and type(frameHeight) == “number” then
– Use frame width and height to create the spritesheet
frameWidth = math.floor((frameWidth * suffix.value) + 0.5);
frameHeight = math.floor((frameHeight * suffix.value) + 0.5);

spriteSheet = sprite.newSpriteSheet(imageFileName, frameWidth, frameHeight);
– Register spriteSheet data in the map to allow cache
sheetData = {
isUniform = true,
frameWidth=frameWidth,
frameHeight=frameHeight,
};
spriteSheetDataMap[spriteSheet] = sheetData;
else
– If non-uniform load the lua file first and then the image
sheetData = getSpriteSheetData(uri.fileName);

spriteSheet = sprite.newSpriteSheetFromData(imageFileName, sheetData);
– Register spriteSheet Lua data in the map
sheetData.isUniform = false;
spriteSheetDataMap[spriteSheet] = sheetData;
end

– Set the name of the spriteSheet
sheetData.name = fileName;

return spriteSheet;
end

–[[
Used instead of the spriteSheet:dispose() method to allow
proper garbage collection.

spriteSheet: The spriteSheet to dispose.
]]
function disposeSpriteSheet(spriteSheet)
local spriteSheetData = spriteSheetDataMap[spriteSheet];

– First dispose each sprite registered to the spritesheet
local sprites = spriteSheetData.sprites;

if sprites ~= nil then
for k,v in pairs(sprites) do
v:dispose();
end
end

– Then unregister spriteSets related to the spritesheet
local spriteSets = spriteSheetData.spriteSets;

if spriteSets ~= nil then
for k,v in pairs(spriteSets) do
spriteSetDataMap[v] = nil;
end
end

– Unregister the spritesheet
spriteSheetDataMap[spriteSheet] = nil;
– And finally dispose it
spriteSheet:dispose();
end

–[[
Creates a new spriteSet to group frames inside
the spriteSheet logically.

spriteSheet: The spriteSheet from which to extract the spriteSet.
startFrame: The frame of the spriteSheet where the spriteSet starts.
frameCount: Amount of frames to take from the spriteSheet.
]]
function newSpriteSet(spriteSheet, startFrame, frameCount)
local spriteSheetData = spriteSheetDataMap[spriteSheet];
local spriteSets = spriteSheetData.spriteSets;

– Use always the cached spriteSet
if spriteSets ~= nil then
for k,v in pairs(spriteSets) do
if v.startFrame == startFrame and v.frameCount == frameCount then
return k;
end
end
end

– If not cached create a new one
local spriteSet = sprite.newSpriteSet(spriteSheet, 1, 21);
– Register set with its data in the map
spriteSetDataMap[spriteSet] = {
startFrame = startFrame,
frameCount = frameCount,
spriteSheet = spriteSheet,
sequences = {},
};

– Register the sprite set in the specific spritesheet list
if spriteSets == nil then
spriteSets = {};
spriteSheetData.spriteSets = spriteSets;
end

table.insert(spriteSets, spriteSet);

return spriteSet;
end

–[[
Registers a sequence by name. Sequences are attached to spriteSets.
Existing sequences with the same name are replaced.

spriteSet: The sprite used to extract the sequence.
sequenceName: The name of the equence.
startFrame: The frame of the spriteSet where the sequence starts.
frameCount: Amount of frames to take from the sequence.
playTime: Milliseconds the animation will play.
loop: Determines whether to loop forever (true) or a fixed
amount of times if a numeric value is sent.
]]
function add(spriteSet, sequenceName, startFrame, frameCount, playTime, loop)
if loop == nil then
loop = false;
end

– Register the sequence with Corona SDK
sprite.add(spriteSet, sequenceName, startFrame, frameCount, playTime, loop);

– Register using our own data to make the fix work
– We never use cached data here, we just replace the existing sequence
spriteSetDataMap[spriteSet].sequences[sequenceName] = {
startFrame = startFrame,
frameCount = frameCount,
playTime = playTime,
loop = loop,
};
end

–[[
Creates a wrapper group around the sprite to overcome all current issues.

spriteSet: The spriteSet to use for the animation.
You should select the sequence using the
sprite:prepare() method.
imageWidth: The width into which the sprite will fit.
imageHeight: The height into whoch the sprite will fit.
]]
function newSprite(spriteSet, imageWidth, imageHeight)
local container = display.newGroup();
local spriteInstance = sprite.newSprite(spriteSet);

– Add new methods to the group to treat it as if it is a Sprite
container.isAnimating = isAnimating;
container.getCurrentFrame = getCurrentFrame;
container.setCurrentFrame = setCurrentFrame;
container.pause = pause;
container.play = play;
container.prepare = prepare;
container.getAnimationWidth = getAnimationWidth;
container.getAnimationHeight = getAnimationHeight;
container.dispose = dispose;

– Add invalidation methods
container.invalidate = invalidate;
container.drawNow = drawNow;
container.validateNow = validateNow;

– Check invalidation and calculate position each frame
spriteInstance.enterFrame = draw;
spriteInstance.isInvalid = true;

– Save original sprite and spriteSet
container.sprite = spriteInstance;

– Cache spriteSet, spriteSheet and imageWidth and imageHeight data into the sprite
local spriteSetData = spriteSetDataMap[spriteSet];
local spriteSheetData = spriteSheetDataMap[spriteSetData.spriteSheet];

spriteInstance.uniform = spriteSheetData.isUniform;
spriteInstance.spriteSetData = spriteSetData;
spriteInstance.spriteSheetData = spriteSheetData;

spriteInstance.imageWidth = imageWidth;
spriteInstance.imageHeight = imageHeight;

– Register the sprite to allow proper removal when disposing spriteSheets
if spriteSheetData.sprites == nil then
spriteSheetData.sprites = {};
end

table.insert(spriteSheetData.sprites, container);

container:insert(spriteInstance);

return container;
end

–[[
Convenience function to allow creating an spriteSheet, spriteSet, sequence and
sprite all in one step. It the image widht and height plus three tables with configuration data.

imageWidth: The width into which the sprite will fit.
imageHeight: The height into whoch the sprite will fit.

fileData: A table defining image file properties:

fileName: Is the file name of the image to use as a spriteSheet.
If no other parameter is set it will also load a lua file
with the same name of the image file.
This method loads images and lua files by appending a suffix
depending on device resolution and rules loaded by init.
frameWidth: Is the width of each frame used for uniform spriteSheets.
frameHeight: Is the height of each frame used for uniform spriteSheets.

spriteSetData: A table defining spriteSet information:

startFrame: The frame of the spriteSheet where the spriteSet starts.
frameCount: Amount of frames to take from the spriteSheet.

sequenceData: A table defining sequence information:

name: The name of the equence.
startFrame: The frame of the spriteSet where the sequence starts.
frameCount: Amount of frames to take from the sequence.
playTime: Milliseconds the animation will play.
loop: Determines whether to loop forever (true) or a fixed
amount of times if a numeric value is sent.

return the sprite and the spriteSheet created to enabled spriteSheet disposing
]]
function newSpriteAnimation(imageWidth, imageHeight, fileData, spriteSetData, sequenceData)
– Create the spritesheet
local spriteSheet = newSpriteSheet(
fileData.fileName,
fileData.frameWidth,
fileData.frameHeight
);
– Create the spriteset
local spriteSet = newSpriteSet(
spriteSheet,
spriteSetData.startFrame,
spriteSetData.frameCount
);

– Register the sequence by name
add(
spriteSet,
sequenceData.name,
sequenceData.startFrame,
sequenceData.frameCount,
sequenceData.playTime,
sequenceData.loop
);

– create the sprite and prepare the animation
local sprite = newSprite(spriteSet, imageWidth, imageHeight);
sprite:prepare(sequenceData.name);
return sprite, spriteSheet;
end[/lua] [import]uid: 46260 topic_id: 4284 reply_id: 33087[/import]

I forgot to mention that for this module to work correctly be extra careful with spriteSheet raw frames size before creating them in software like Zwoptex or Texture Packager.

What I mean is if for example you have a brick animation and each frame (including surrounding transparency) is 50x50. Then the animation 2x version should use 100x100 frames and so forth…

If you just use random amounts of transparency around the frames for different versions of the spriteSheet the module will not size the spriteSheet correctly even if the main object (after cropping transparency) is in the desired size. [import]uid: 46260 topic_id: 4284 reply_id: 33099[/import]

Big Post there djaramillo

Maybe put the module in text file and link the text file ? [import]uid: 48521 topic_id: 4284 reply_id: 33198[/import]

This is awesome! This should be added to the code exchange :slight_smile: [import]uid: 9099 topic_id: 4284 reply_id: 36226[/import]

I can provide an example if you want

Could you post an image an code that demonstrates the problem? I’m pretty sure I know what you’re talking about, but since I haven’t encountered it personally I’m not entirely certain.

ADDITION: Oh wait I just noticed there’s a download at the top of magenda’s thread, I’ll look at it tonight:
http://developer.anscamobile.com/forum/2011/01/08/sprites-scaling-very-annoying-bug

ADDITION2: y’know, this may actually be desirable behavior in some animations. I mean, this is still a bug because the behavior is different before and after scaling, but it might be nice to always center the frame as it changes size.

The fix I would suggest is adding an alignment parameter to the command to create a sprite: align=“bottom” or align=“center” or align=“top” [import]uid: 12108 topic_id: 4284 reply_id: 36263[/import]