The Corona Labs Blog
Posted on . Written by

Tuesday Tutorials are back! Today’s tutorial is from Brent Sorrentino, a Corona Ambassador based in northern Colorado. Brent has been an active part of the Corona community for almost two years. He is a freelance travel photographer, Corona developer, and graphic designer. In addition to using Corona to develop his own apps, he regularly lends a hand in the forums, helping other developers solve coding issues. His website can be accessed here.

Today’s tutorial covers how to implement animated sprites and use the related APIs. While this might seem like a rehash of previous tutorials, several key improvements have been implemented since the current sprite system was introduced earlier this year. In addition, many developers are still confused about how to implement sprites in Corona because, in fact, there are two approaches leading to a similar result:

  1. The old (and now depreciated) sprite.* library.
  2. The current sprite APIs that work cohesively with image sheets and the display.* library.

While Corona is technically “backwards compatible” with the depreciated sprite.* library, we strongly encourage that you use (or migrate to) the current sprite methods. If you haven’t implemented the current methods or haven’t ever used sprites in Corona, this tutorial will walk you through the process.

For those already familiar with basic animation in Corona, this tutorial introduces the full array of sprite event listeners and how to implement them.

Tutorial Contents

  1. Configuring a Basic Image Sheet or “Sprite Sheet”
  2. Defining Animation Sequences
  3. Declaring a Sprite Object
  4. Sprite Control Methods — managing playback and sequences
  5. Sprite Properties — accessing and setting various sprite properties
  6. Sprite Event Listeners — detecting sequence events such as “ended”, “loop”, and “next”

Configuring a Basic Image Sheet or “Sprite Sheet”

What exactly is a image sheet? Imagine it as a sheet of paper on which you draw the individual frames for your animated object(s). Other technical terms include texture atlas, image map, or sprite sheet. In Corona, we’ll simply refer to it as an image sheet, because its usage is not limited to either static or animated objects — you can, and often should, use image sheets for both purposes: static images picked from a portion of an image sheet, and animated sprites utilizing multiple frames from a sheet.

Detailed technical notes and usage examples for the graphics.newImageSheet() API are located here, but in this tutorial we’ll discuss how to use image sheets for animated sprites.

Pictured below is a sample image sheet for a running cat. If you want to use this image sheet to follow through the entire tutorial, you can copy the hi-resolution version from here. This sheet consists of eight “frames” in a specifically ordered  sequence. By default, the animation will begin at the top-left frame, proceed to the next frame (to its right), wrap to the next row when it reaches the end of the current row, and finally stop (or repeat) when the entire sequence is complete.

Let’s proceed with how to configure this image sheet in Corona. First, set up an indexed table for a basic image sheet of uniform-sized frames. You can (and often will) set up an image sheet using frames of different sizes packed in a tight, optimized arrangement (see documentation), but in this tutorial we’ll use a basic image sheet.

IMPORTANT:  sheetContentWidth and sheetContentHeight represent the overall 1:1 dimensions of the image sheet, meaning the 1:1 content scale that you’ve set up in your app’s config.lua file. This allows you to use different image sheets for different devices, for example a @1 sheet for the original iPad and a high-resolution @2 sheet for the Retina iPad. Thus, if your 1:1 sheet is 1024×1024 pixels in overall dimensions (not per-frame), build your 2:1 sheet at 2048×2048 — but always specify the 1:1 dimensions in your image sheet setup. If you’ve defined your config.lua file properly, Corona will take care of the rest!

Now, define the actual image sheet, referencing both the image file and the data table you created. This assumes that your image sheet file resides at the root of your project directory, not in a subfolder.

Defining Animation Sequences

You can define animated frame sequences in two ways:

  • consecutively using a starting frame index and frame count
  • non-consecutively using a specific order of frames

This makes the Corona sprite system very flexible — you can use the same image sheet for multiple animation sequences.  In both cases, you’ll need to define your sequences in a comma-separated array of sub-tables.

Consecutive frames

Non-consecutive frames

Mixed sequences (both consecutive and non-consecutive sequences)

Declaring the Sprite Object

Using the current sprite method, you’ll declare sprites with the display.newSprite() API. The syntax is simple:

display.newSprite( [parent,] imageSheet, sequenceData )
  • parent = The parent display group in which to insert the sprite (optional).
  • imageSheet = The image sheet which the sprite should utilize.
  • sequenceData = The array of animation sequences which you set up. The sprite will default to the first sequence in the array unless you specify otherwise (see Sprite Control Methods below).

In context of this tutorial, your sprite declaration should look like this:

The sprite is now a display object, just like static images, vector objects, etc.  It can be moved, manipulated, linked to a physics body, and more.  To remove a sprite object, simply use object:removeSelf() or display.remove( object ). Don’t forget to set the reference to nil after removal!

Sprite Control Methods

The sprite system provides four key control methods which you’ll use to control the playback and sequence of your sprites.

  • animation:play()
    Start the animation playing. Animations do not begin playing when you create them — you must start each animation manually using this command.
  • animation:pause()
    Pauses the animation. There is no “stop” control method; instead, pause the animation using this method.
  • animation:setFrame( frame )


    Immediately set or skip to the indicated frame index. If you want to “stop and reset” an animation sometime after you have started playing it, use the :pause() and :setFrame( frame ) commands consecutively, setting the frame back to the beginning of the sequence.
  • animation:setSequence( sequence )


    Set the sprite to a specific sequence that you declared in your sequence array. For example, if you want to change your cat animation from “normalRun” to “fastRun”, you would use animation:setSequence( “fastRun” ) and use animation:play() to begin playing it, since the animation will not play automatically after you change the sequence.

Putting It Together

Let’s put the entire animation together. To set up your cat animation with two animation sequences “normalRun” and “fastRun”, your code should look like this:

Go ahead and test your project in the Corona Simulator and see the result!  The cat should appear in the center of the screen, animating on the “normalRun” sequence (since you didn’t specify a sequence, the cat defaults to the first sequence in the sequence array).  Experiment with changing the sequence by typing animation:setSequence( “fastRun” ) on the line before animation:play().

Sprite Properties

Corona provides several properties which can yield information about existing sprites. You can even modify the timeScale (relative speed) of a particular sprite. These properties are as follows:

  • object.frame
    A read-only property that represents the currently shown frame index of the loaded sequence. This does not set the frame — use the :setFrame() command to explicitly set an animation frame.
  • object.isPlaying
    Returns true if the animation is currently playing; false if it is not.
  • object.numFrames
    A read-only property that represents the number of frames in currently loaded sequence.
  • object.sequence
    A read-only property that returns the name of the currently playing sequence.
  • object.timeScale
    Gets or sets the scale to be applied to the animation time. This is used to control a sprite’s animation speed dynamically. For example, a time scale of 1.0 (default) runs the animation at normal speed. A time scale of 2.0 runs the animation twice as fast. A time scale of 0.5 runs the animation at half speed. The maximum allowed value is 20.0 and the minimum allowed value is 0.05. The value supports up to 2 decimal places.

Sprite Event Listeners

Now that you have a basic sprite declared and two sequences (“normalRun” and “fastRun”), let’s examine sprite event listeners and how to implement them. A sprite event listener, by definition, “listens” to the activity of a sprite and sends information to the listener function.

For example, assume that you want your running cat to loop through 4 cycles of the “normalRun” sequence then change to the “fastRun” sequence. This would be effectively impossible (and awkward) using standard timers, so instead we’ll implement a sprite event listener.

Before we proceed to the example, examine the five event phases available to sprites. These phases are implemented in the most recent Public Release of Corona (2012.894) and beyond.

  • began = The sprite has started playing.
  • ended = The sprite has finished playing.
  • bounce = The sprite has bounced from forward to backward while playing.
  • loop = The sprite has looped to the beginning of the sequence.
  • next = The sprite has played a subsequent frame that’s not one of the above phases.

We’ll discuss how to “listen” for these phases below, but first let’s modify the running cat’s “normalRun” sequence by adding a loopCount of 4. This allows you to detect an ended phase when all 4 loops are complete.

Now, let’s write the listener function and add the actual event listener to the running cat. You can place this at the end of your sample code, after the sequences are declared and the sprite object placed on the screen.

A sprite listener transmits all phases to the listener function. It’s your responsibility to filter the phase(s) using conditional if-then clauses and perform actions when they occur in the sprite animation sequence. In this particular case we listen for the ended phase, which occurs after 4 complete loops as dictated by the sequence’s loopCount parameter. When the sequence ends, we switch the cat animation to the “fastRun” sequence and play it.

Note that you can use one sprite listener for all of your sprites — simply get the sprite reference using event.target in the listener function (this was formerly event.sprite in the depreciated sprite library; if migrating from the depreciated sprite library, use event.target instead).

In Summary

This tutorial has examined most of the current sprite methods, including setting up basic image sheets, defining animation sequences, controlling sprite playback, accessing various sprite properties, and using sprite event listeners for fine-tuned control. Hopefully this tutorial has provided some valuable insight and tips for developers using sprites in their apps, and for those migrating from the depreciated sprite library to the current sprite methods.

18 Responses to “Animated Sprites and Methods”

  1. J. A. Whye

    Cool tutorial, wish I’d had it a week ago! I was wanting an “onComplete” for when a sprite sequence was done playing and when I didn’t see that, I ended up using timers. And while it worked, it’s the kind of thing that can break if I add more frames to the sequence, etc.

    Now that I know about sprite event listeners I can do things the “right” way. :)

    Jay

    Reply
    • lalala

      hm, yes. but be cautious with that:

      the sprite.’ended’ event occurs at the BEGINNING of the last frame of an animation. not at the end.

      I don’t know if that’s actually meant to be that way. I find it very unintuitive.

      Reply
  2. Chris Leyton

    Always enjoy a good tutorial – particularly one on sprites. However I do feel that this is going over the same turf again.

    Perhaps we could have a further tutorial on this subject – check out this guy’s blog posts, really enjoying how he organises his sprites and tackles a tricky subject like splitting up sprites:

    http://www.ardentkid.com/blog/2011-07-11/dynamic-character-sprites

    Perhaps we could get him to do a guest post on the subject.

    Reply
  3. Brent Sorrentino

    Regarding dynamic sprite sheets… let’s assume you have the following scaling set up in your “config.lua” file:


    application = {
    content = { fps = 60, width = 704, height = 1024, scale = "letterbox", xAlign = "center", yAlign = "center",

    imageSuffix = {
    ["@1"] = 1.0,
    ["@2"] = 2.0,
    ["@retina"] = 4.0
    }
    }
    }

    You’d then create 3 separate image sheets based on this scaling. If, for example, your “@1″ image sheet is 1024×1024 is total dimensions, your sheets would be sized/named as follows:

    mySheet@1.png = 1024 x 1024
    mySheet@2.png = 2048 x 2048
    mySheet@retina.png = 4096 x 4096

    Now, in terms of using these sheets dynamically for sprites, you must always declare the 1:1 or “@1″ sheet dimensions (1024 x 1024 in this case) as the parameters for “sheetContentWidth” and “sheetContentHeight” when you declare the image sheet parameters.

    The Corona sprite engine will use these parameters to both choose the correct sheet for the animation (depending on the device) AND it will adjust the frame width and height parameters that it selects from the sheet accordingly, behind the scenes.

    Hope that helps!
    Brent

    Reply
  4. Ian

    When I add “mySpriteListener” function, why does it take a few seconds for the cat sprite to start running at the “time=250″ speed?

    Reply
  5. Brent Sorrentino

    Hi Ian,
    The “fastRun” sequence doesn’t begin until the “normalRun” sequence completes 4 full cycles, as dictated by “loopCount=4″ in that sequence… and also the fact that the listener is waiting for the “ended” signal, which occurs after ALL FOUR loops have completed. If you don’t specify a loopCount (or if you set it to 0, which will loop forever), you’ll never get an “ended” phase. If you want to detect the end of just ONE sequence in a forever-repeating animation, you can sense the “loop” phase which is returned when a sequence starts over at the beginning.

    Reply
  6. Leonard

    I copied code from “Putting it Together” paragraph into main.lua file. I also copied PNG file into root folder by dragging it into Macbook download folder, and renamed it to match file name in the code. I noticed that code does not work. I understand it probably is still missing most of what complete main.lua needs to have to actually work. Am I correct?
    My concern, probably shared by other readers, is that only experienced Lua developers are intended audience of this and similar blogs. Those new to Corona still do not have a good source of information. It would be great if someone took initiative to focus on new users and explain how Sprites and other features work, vs. providing not too closely related extracts from different files and programs, that still may need major additional code to make it work.
    I would appreciate if someone could suggest good source of beginner level tutorials on Sprites and similar topics. I purchased Corona subscription, but do not see much difference in access to any good tutorial with or w/out subscription. Online blogs and tutorials are mostly bits and pieces from advanced developers exchanging tips and advice and marketing their services, but there is very little focus on helping others. Besides one person from Australia, all materials are very unstructured and confusing. Good fundamental teaching environment is still waiting to be introduced to Corona community.

    Reply
    • Brent Sorrentino

      Hi Leonard,
      What is the error you’re receiving when you test your project? It’s probably something small but important.

      I sympathize with your situation, but it’s a “fine line” with tutorials. This one, in particular, was viewed as far too simplistic by many Corona developers… a “re-hash” if you will. Most developers seem to crave something new and more advanced, especially in this weekly tutorial arena.

      For beginners, I can recommend Brian Burton’s excellent e-books. They are geared toward getting you up-and-running with Corona, and Dr. Burton is a trusted authority in the field. Yes, they are sold at a price, but it’s a reasonable price and worth the investment if you’re getting a headache from browsing endless forum posts, third-party blogs, and random code snippets.

      His website is linked below. If you have questions, please contact Dr. Burton personally. He is prompt to respond and will certainly respond to you whether his books might meet your particular needs as a new user to Corona.

      http://www.burtonsmediagroup.com/books/

      Best of luck!
      Brent Sorrentino

      Reply
  7. saiphan

    hey, i am having a problem with the coding for this tutorial.
    first thank you for uploading thise but i cannot see the code writen to add in “putting it together” the line is just empty.
    second:
    it gives me an error of a “bad argument” to ‘newImageSheet’ (table (options) expected).
    i tried to implement a local function named options and changed the “sheetData” to “options” too based on – graphics.newImageSheet() – on the website. but still giving me the same error. what do i do wrong? please help.

    Reply
    • saiphan

      ok nvm i managed to make it work using several other references on the site. thanks anyway. it seems that unlike as3 u need to declare the function before calling it what caused the problem to begin with.

      Reply
      • Brent Sorrentino

        Hello saiphan,
        Good to hear you figured it out. Also, thank you for telling me that the code example(s) were missing. Apparently something became corrupted in the previous Gists and they weren’t appearing. I replaced them, so now you can see the code examples specific to this tutorial.

        Best regards,
        Brent

        Reply
    • Brent Sorrentino

      Hi Jerry,
      Yes, TexturePacker should handle this fine, but you’ll need to take the data that it generates (without any of the “sourceX” or “sourceY” stuff, if you have it) and put that frame info into your sprite setup.

      Brent

      Reply
  8. Michael

    Fantastic work- as usual- folks! I just spent two days trying to force Lime and Tiled to work with sprites and then in 5 minutes got things working after reading this page! To infinity and beyond Corona!

    Reply

Leave a Reply

  • (Will Not Be Published)