Confession: I fear sprites and image sheets. I don’t know why. They do wonderful things, conserve memory, and improve performance — and yet I almost instinctively avoid them. Perhaps when I started using Corona SDK a few years ago, the original, deprecated “sprite” library felt overly complicated, so I avoided sprites and didn’t genuinely attempt to adopt the current sprite methods when they were introduced over a year ago.

Around the time I started using Corona SDK, there was an open source Lua module called MovieClip that I really enjoyed using. You’d simply provide a list of image file names (one image per frame) and it would load them all into memory. Then you could play them in a looping sequence, front-to-back, back-to-front, or just jump to a specific frame. MovieClip had some dark sides, however. Most obvious was that loading a whole slew of images for a “flipbook” animation was wasteful in regards to both memory and processing time. Another negative aspect was that it was open source, so people took the code and changed it to suit their needs. That sounds good in theory, but when one person built MovieClip 1.4 with feature set “X” and another developer built a different MovieClip 1.4 with feature set “Y”, nobody seemed to know which version did what. Essentially, only the author of a specific version could support it, and with no source control, the MovieClip module ran wild.

Although you can still find MovieClip floating around, it’s been effectively deprecated. And so, I realize that I must finally adapt to sprites and image sheets. In today’s tutorial, follow along as I get over my “fear of sprites” and show you how to use them in some interesting, practical ways.


Frequent Uses

A common question for beginners is how to change the “frame,” or the source file, of an image declared with newImage() or newImageRect(). This isn’t technically allowed, but obviously we still need a method to change images on the fly. There are plenty of cases in which you might need this, including:

  • A two-state image, i.e. a lightbulb which turns on and off.
  • Card games where some of the cards are hidden (flipped over).
  • An incrementing health bar.

My first Corona-built game, OmniBlaster, used MovieClip to control the health bar, in addition to the following game aspects:

  • Selecting which of the three ships (images) the player is currently using.
  • Setting one of five “sensitivity” options on the settings screen.
  • Setting one of the three difficulty settings.
  • Controlling the current shields around the ship.
  • Displaying a bar graph for  the two power-up timers.
  • Displaying a bar graph to represent the current health of the ship.

Fortunately, all of these can be easily accomplished with sprites. Let’s learn how…


Getting Started: Building the Image Sheet

In Corona SDK, all sprites are assembled from one or more image sheets. An image sheet is merely a larger “canvas” which includes all of the animation frames for the sprite(s).

To create an image sheet, you can either assemble it manually using an image editor (Photoshop, the Gimp, etc.) or use an image packing tool like Texture Packer or SpriteHelper. The great thing about packing tools is that they support multiple sizes of image sheets for different screen resolutions, and they pack individual images into the most compact, memory-efficient arrangement possible. In addition, Corona SDK only has to load one image file, versus a considerable number of images as was required with MovieClip.

As a starting point for implementing sprites, I’m going to rebuild my “health bar.” Each individual image is designed to look like an LED, with green/blue representing full health and red indicating the ship is almost destroyed. Each image is 60×20 in size and there are six frames in total, so this image sheet will be very easy to create.

img-omni-1

In the image editor, I simply create a canvas that is 60 pixels wide and 120 pixels tall. When I paste each frame on the canvas and progress downward, the result is this:

img-omni-2


Coding the Image Sheet

Coding the sprite for the health bar (or any sprite) involves three basic steps:

  1. Create the image sheet.
  2. Create the sprite and position it.
  3. Play the sprite.

First, let’s create the options for the image sheet:

This block of code defines the parameters of the image sheet. Each frame is 60 pixels wide (width), 20 pixels tall (height), and there are 6 total frames (numFrames).

Next, I’ll create six sequences of frames to play — a simple animation of the health bar incrementing from zero to a certain value. In OmniBlaster, these sequences are used as an animated power-up toward a certain health increment.

All sequences must be identified by a unique name so you can refer to them later. In this case, I name them “health” plus a digit that indicates the life value. Then, I create the sprite using the display.newSprite API and assign the sequences to the new sprite. Finally, I position the object and add it to a display group.


Animating the Health Bar

In OmniBlaster, there’s a variable called lives which is a number from 0 (dead) to 5 (max health). When I want to animate the health bar from frame 1 to a certain destination frame, I can use the lives variable to set the sequence using basic Lua concatenation, as follows:

And then, playing the sprite is as simple as this:

Quite simple! For sake of comparison, let’s examine the complete code that MovieClip would have required…

Although the setup is simple and the overall number of lines is less than the sprite method, in the next section we’ll explore how the same sprite object can be used for other purposes. Multi-purpose equates to higher efficiency and it also makes your life easier.


Image Swapping

In OmniBlaster, I need to animate the health bar, but I often need to set the bar to a specific increment as well. For example, if all of the shields are gone and the ship takes a hit, the player loses a health point. I could have accomplished this using MovieClip, but since this tutorial is about sprites, let’s examine the same process using that method.

If you want to set a sprite frame to any frame in the overall sheet, logically you must create a sequence that contains all of the frames. Using the OmniBlaster health bar again, that sequence is “health5″ — this is the only sequence that encompasses all six frames in the sheet. With this in mind, we set the sequence as follows:

Then, to set the frame to a specific value, i.e from a visual increment of 4 to 3, the code is simple:

Why did I use 4 instead of 3? Because if you examine the image sheet, the visual increment of three notches in the health bar is actually the fourth frame.


Careful Now…

It’s important to understand that when you explicitly set the frame of a sprite, the integer value that you declare is related to the sequence, not the overall sheet. For example, if you configure an image sheet of six frames and a sequence that uses frames 4, 5, and 6, you must use the following to jump to frame 6:

Why? Because frame 3 in the sequence equates to frame 6 in the overall image sheet. To illustrate this, let’s use a modified version from the OmniBlaster sheet:

Notice how in this new sequence, we are setting start=4 and count=3, which will encompass frames 4, 5, and 6 of the overall sheet. Then, if we use (set) this sequence and want to jump to frame 6 of the overall sheet, the proper code is:

If you use the second example, Corona will issue a warning similar to this:

This will not terminate/crash your app, but you should understand how to properly set frames within a sequence, or you may encounter problems when dealing with more complex animations and image sheets.


It’s All About Efficiency

Notice that both of our examples above — animating and frame swapping — use the same image (sprite) object, from the same image sheet. This multi-purpose practice is efficient in two key ways:

  1. Code Efficiency — creating one object allows us to declare, reference, track, and clean up our sprites much easier.
  2. Memory Efficiency — as a mobile developer, you need to be consistently mindful of memory usage. As I mentioned in regards to MovieClip, it was easy to use but it was generally not efficient. Creating all of those individual objects from individual image files was a resource hog in regards to both Lua memory and texture memory too. Of these two, texture memory is a far greater concern for the mobile developer. If you wish to explore texture memory and how Corona/OpenGL allocate it, please refer to Conserving Texture Memory in the Performance Optimizations tutorial.

In Summary…

As you can see, sprites are not just for typical animations, but for swapping frames of a “static” image as well. Best of all,  if you optimize/pack your individual images into image sheets — and I highly recommend that you do — using sprites requires just a few more lines of setup and minimal additional effort elsewhere in code. In essence, they are flexible, efficient, and provide more control than MovieClip ever did.

View this article: 

Sprites and Image Sheets for Beginners