Posted on by

Today’s guest tutorial comes to you courtesy of Omid Ahourai, an indie game developer who goes by the alias “ArdentKid.” He’s been working with Corona SDK for over two years and will soon release his first highly-anticipated game, “Balloon Bazooka.” Please check out his work and blog at www.ardentkid.com.


Basic Sprite Sheets

If you missed last week’s animation tutorial, please read it first, as this tutorial expands on the basic sprite methods discussed therein.

Sprite sheets are great because they’re not CPU-intensive, but there are a couple of downsides in comparison to vector animation. They can easily take up a lot of memory and result in large app file sizes (we want to stay under Apple’s 20 MB restrictive wifi-required download). Also, sprite sheets are limited in that they usually consist of single-colored un-swappable assets. If we need more flexibility such as changing a character’s clothing or accessories, we would need to duplicate all of our animation frames for that character with those changes, taking up twice as much texture memory.

This tutorial will discuss a possible way to eliminate both of these bottlenecks. The secret is a more intimate relationship between code and animation. Let’s start with a sample sprite sheet of a kid from my upcoming game, “Balloon Bazooka.”

This sheet is designed for basic sprite animation in Corona in which you load the image sheet, set up sequences, and then play the desired sequence. If you attempt the more advanced techniques I’m going to discuss below, I recommend that you first become familiar with the basic methods.


Splitting the Animation Elements

Since we’re going to have multiple kids of the same sprite appear on screen simultaneously, we want to give them varying colored wardrobes, different skin tones, etc. Fortunately, we can mimic some animations by tweening visual properties programmatically — scale, rotation, and translation (for simplicity, we’ll only code the foot animation and leave the arm frames intact).

With that in mind, let’s first “decapitate” the kid, add different colors, and piece him back together in a single display group. This is what the new image sheet would look like:

Notice that the file size of the new sheet is reduced from 225 KB to 109 KB and we gain the ability to use varied wardrobes and skin tones — over 50% reduction in file size and virtually no limit to how we can combine the elements!

Now we’ll declare the split elements as individual sprites in code:

-- DECLARE CHARACTER SEQUENCES (MOST ARE JUST STATIC CHERRY-PICKED FRAMES!)
local sequenceData = {

   { name="beachboy_hat_yellow", frames={1} },
   { name="beachboy_body_dark", frames={34} },
   { name="beachboy_shorts_red", frames={42} },
   { name="beachboy_arm_dark", frames={ 5,12,15,19,23,26 }, loopDirection="bounce" },
   { name="beachboy_foot_dark", frames={47} }
}

-- CREATE A DISPLAY GROUP FOR THE CHARACTER
local beachboy = display.newGroup()

-- CREATE BODY PARTS AS SPRITES
local hat = display.newSprite( sheet, sequenceData )
      hat:setSequence( "beachboy_hat_yellow" )
local body = display.newSprite( sheet, sequenceData )
      body:setSequence( "beachboy_body_dark" )
local shorts = display.newSprite( sheet, sequenceData )
      shorts:setSequence( "beachboy_shorts_red" )
local rightArm = display.newSprite( sheet, sequenceData )
      rightArm:setSequence( "beachboy_arm_dark" )
local leftArm = display.newSprite( sheet, sequenceData )
      leftArm:setSequence( "beachboy_arm_dark" )
local rightFoot = display.newSprite( sheet, sequenceData )
      rightFoot:setSequence( "beachboy_foot_dark" )
local leftFoot = display.newSprite( sheet, sequenceData )
      leftFoot:setSequence( "beachboy_foot_dark" )

-- POSITION PARTS & ORIENT L/R SIDES WITH SCALING
shorts.x, shorts.y = 0, 15
leftArm.x, leftArm.y = -20, -18
leftArm.xScale = -1 --flip 'leftArm' horizontally
-- etc...

-- INSERT ELEMENTS INTO THE DISPLAY GROUP, ORDERED BOTTOM TO TOP
beachboy:insert( leftFoot )
beachboy:insert( rightFoot )
beachboy:insert( shorts )
-- etc...

-- STORE REFERENCES TO EACH ELEMENT
beachboy["feet"] = { leftFoot, rightFoot }
beachboy["shorts"] = shorts
beachboy["body"] = body
-- etc...

Animating the Elements

Once your character shows up with all of the elements in the correct place, we can animate his fancy footwork:

--WALK SEQUENCE
function beachboy:walk()

   local feet = self.feet

   --CLEAR EXISTING TRANSITIONS IF RUNNING
   if not ( self.currentTransitions ) then self.currentTransitions = {} end
   local currTrans = self.currentTransitions
   local tot = #currTrans
   for i = tot,1,-1 do
      if (currTrans[i]) then transition.cancel(currTrans[i]) ; currTrans[i] = nil end
   end

   local t = 500 --WALK CYCLE TIMING
   local dist = 160 --FOOT MOVEMENT DISTANCE
   local ease = easing.inOutQuad --FOR A NATURAL SWINGING MOTION

   --RECURSIVE ANIMATION FUNCTION
   local function anim()
   for i = 1,#feet do
         currTrans[i] = transition.to(feet[i], {y=dist, time=t, transition=ease, onComplete=function()
         currTrans[i] = transition.to(feet[i], {y=0, time=t, transition=ease, onComplete=function()
         anim()
         end})
      end})
   end
end

--START THE ANIMATION
anim()

end

Here, we’re calling our own Runtime animation code instead of a sprite object :play() function. We’re using recursive transitions in this tutorial, but if you want the arms to sync up with the feet properly, you’ll need to do a bit more work (I’ll leave it out for now). Otherwise, this is a great start!  We now have access to several different colors using the same amount of memory. Could it get any better than that? Well, yes, it can!


“Tinting” the Elements

We can nearly eliminate static colors completely using programatic tinting on each part — that is, using Corona’s setFillColor() method. We’ll make all the body parts white and use shades of grey for variance. Here’s what that looks like in image sheet format:

And here’s how we tint the sprite objects in code:

-- ACCESS EACH ELEMENT FROM THE CONTAINER TABLE BY INDEX NAME
beachboy.hat:setFillColor(252, 206, 0)
beachboy.body:setFillColor(126, 79, 33)
beachboy.shorts:setFillColor(220, 0, 0)
beachboy.feet[1]:setFillColor(126, 79, 33) --access 'leftFoot' by position within 'feet' table
beachboy.feet[2]:setFillColor(126, 79, 33) --access 'rightFoot' by position within 'feet' table
-- etc...

We’re now able to randomize or use any color we want, on the fly! The downsides here are minor, depending on your needs, but there are a few. First, we are compromising a few processor cycles because we have to tint each sprite element every time we play a different animation sequence for the first time. Second, our character movement requires more work from the CPU, since we’re moving each individual body part instead of as one whole. Lastly, we have to restrict a single color to each part. For example, a rainbow-colored hat isn’t possible, but we could get various results by using a color other than white or grey.


In Summary

I’ve found that performance is exceptional using this method, especially with modern mobile devices — and the benefits of using much less texture memory can help streamline your app immensely.  I’ll emphasize again that we have reduced the texture memory required for this “beachboy” character from 225 KB (original sheet) to 109 KB (elements separated) all the way down to an ultra-efficient 12 KB. That’s a 95% reduction in texture memory!

Additionally, we have gained the ability to control each element independently. You can animate one arm, both arms, and even run unique animation sequences for the limbs like “punch” and “kick.”

Finally, we can now tint/color each character element individually and change it on the fly.

In conclusion, dynamically-optimized sprite sheets can be a formidable addition to your coding arsenal and should be considered for advanced sprite integration.


Posted by . Thanks for reading...

17 Responses to “Tutorial: Dynamically-Optimized Sprites”

  1. Pawel Maczewski

    Great article, thanks.

    Just a side-note: since iPad 3 was released (the one with Retina display), the non-WiFi download size for an app was raised to 50MB. Gives a bit more flexibility.

    Reply
  2. Chris Leyton (SegaBoy)

    Thank you Corona and thanks ArdentKid – I follow your tutorials on your blog, and like I said on the forums you really know your stuff.

    This is exactly the type of a slightly more advanced tutorials we need to see on here.

    Great stuff.

    Reply
  3. Haakon

    The file size itself doesn’t really matter – it does not affect the texture memory used. The sprite sheet’s dimension is what matters, and it is scaled up to the closest powers of 4. Or has something changed that I’m not aware of?

    Reply
  4. Arturitu

    Brent, are you working on implementing “Spriter” for Corona to make this kind of animations? Because Spriter tool looks amazing for Dynamically-Optimized aminations

    Reply
  5. Brent Sorrentino

    Hi Arturitu,
    Personally, I don’t use a 3rd-party sprite packing/optimization tool, of which Spriter is just one of them. As long as a sprite optimization tool supports output to Corona in the current sprite API methods, it should be fine to use it for this tutorial’s advanced method(s).

    Reply
  6. Brent Sorrentino

    Hi Haakon,
    Sorry about the slight confusion (Omid wrote this tutorial, but I helped edit it and I should have clarified that point). Anyway, I believe you’re correct. The actual “file size” isn’t directly indicative of the texture memory used by OpenGL. While I’m not an authority on OpenGL and low-level engineering issues, there seems to be a fair amount of debate about whether developers should use “Power-of-2″ image(sheet) sizes, or if “NPoT” (Non-Power-of-2) sheet sizes are acceptable. The consensus that I’ve found is that it’s “better” to use “PoT” sheet sizes, but NPoT is fine too (it has been supported since OpenGL 2.0). Corona allows for both… personally, I use PoT image sheet sizes, but it’s not required in Corona.

    Reply
    • Omid Ahourai

      dellos- I’m not sure what you mean exactly. I think you’re asking if you can set (or remove) the color during specific frames of a sprite animation. To do that, you could define an enterFrame function like this:

      function changeColorOnFrame(event)
      if (sprite.frame == whateverFrameNumber) then
      sprite:setFillColor(255) –remove color tint
      end
      end
      Runtime:addEventListener(‘enterFrame’, changeColorOnFrame)

      Reply
  7. Nick

    Really great tutorial! I just realized something though, in the second and final spritesheet there are only frames for moving downwards which means that final file size would probably more like 12*4kb.

    Reply
    • Juan Pulido

      No Nick, Ardenkit says: Fortunately, we can mimic some animations by tweening visual properties programmatically — scale, rotation, and translation (for simplicity, we’ll only code the foot animation and leave the arm frames intact).
      This means that, with the sprite of 12k, we can get movement in four directions.

      greetings,

      artjc :-)

      Reply
  8. ArdentKid

    Thanks Juan
    Yes, the sprites parts can be rotated to make him appear sideways as well in some cases. I had a bit of difficulty finding the exact first spritesheet that correlated most closely, at the time of the article’s writing. But the file sizes would be close, if not the same.

    Reply
  9. Timea

    Finally a great tutorial on animating elements using sprite sheets! I was looking for such for quite some time, happy to find it!

    Reply
  10. romin

    A very great and useful tutorial but unfortunately very complicated for newbies.
    Is there any kind soul who can make a video tutorial on this important subject???

    Thanks in advance.

    Reply

Leave a Reply

  • (Will Not Be Published)