One of the most powerful things about the Corona SDK is that any display object can be animated. This is a testament to the flexible graphics model that Corona offers.
Animations allow you to create visually-rich and engaging user experiences. Animations are accomplished by generating a sequence of frames that evolve smoothly from frame to frame. The term tween (short for inbetween) is a term describing the process in which such intermediate frames are generated. It is often used as shorthand to indicate that a property of an object will change during the animation, as in tweening the position.
The transition library allows you to easily create animations with only a single line of code by allowing you to tween one or more properties of a display object. For example, you can fadeout a display object by tweening its alpha property (the alpha property transitions from 1.0 to 0).
The simplest way to do this is to use the transition.to method which takes a display object as its first argument and a table containing the control parameters as its second. The control parameters specify the duration of the animation, an optional delay for when to start the animation, and the final values of properties for the display object. The intermediate values for a property are determined by an easing function that is also specified as a control parameter. By default this is a linear function.
Below are some examples of how to animate a square (see Transition2 in the sample code):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | local square = display.newRect( 0, 0, 100, 100 ) square:setFillColor( 255,255,255 ) local w,h = display.contentWidth, display.contentHeight local square = display.newRect( 0, 0, 100, 100 ) square:setFillColor( 255,255,255 ) local w,h = display.contentWidth, display.contentHeight -- (1) move square to bottom right corner; subtract half side-length -- b/c the local origin is at the square's center; fade out square transition.to( square, { time=1500, alpha=0, x=(w-50), y=(h-50) } ) -- (2) fade square back in after 2.5 seconds transition.to( square, { time=500, delay=2500, alpha=1.0 } ) |
In the first tween, notice that multiple values are changing: position and alpha. That’s because in the control parameters we specified the final values for the x, y, and alpha properties. For each property specified in the control parameters, the library looks at the current property value and gradually changes that property to the final value over the time period specified (1.5 seconds in this case). In the last tween, we use the delay control parameter to start the tween after the initial tween’s fadeout is complete.
Note that the transition library operates in a time-based manner.
Returns a tween that animates properties in the display object target over time. The final property values are specified in the params table. To customize the tween, you can optionally specify the following non-animating properties in params:
Similar to transition.to except the starting property values are specified in the params table and the final values are the corresponding property values in target prior to the call.
The transition library eliminates the need for manually animating objects. Compare the following code that causes a white rectangle to fade out in 1 second:
| Manual animation | Using transition |
|---|---|
local rect = display.newRect( 0, 0, 100, 100 ) rect:setFillColor(255,255,255) local tMax =1000+system.getTimer() local function fadeOut(event) local t = event.time if t < tMax then rect.alpha = 1 - t/tMax else rect.alpha = 0 -- done, so remove listener Runtime:removeEventListener( "enterFrame", fadeOut ) end end -- Add listener to begin animation Runtime:addEventListener( "enterFrame", fadeOut ) |
local rect = display.newRect( 0, 0, 100, 100 ) rect:setFillColor(255,255,255) transition.to( rect, {time=1000, alpha=0}) |
The easing library is a collection of interpolation functions used by the transition library:
You can create your own easing function to interpolate between a start and a final value. The arguments of the function are defined as:
The external movieclip library allows you to create animated sprites (sometimes called “movieclips”) from sequences of images, which can then be moved around the screen using the same techniques as any other Corona display object. Functions are available to play these animation frames, or partial sequences of these frames, in either the forward or reverse direction; to jump to specified frames; to skip to the next or previous frame; to automatically delete the animation on completion of a sequence; and to make the animation draggable, complete with press, drag, and release events.
This framework provides an quick and lightweight way to create animations, and also helps when porting existing Flash content to Corona.
The movieclip library is an external module, movieclip.lua, that can be included with your projects and loaded using the require command (see Loading external libraries for further details on require).
Note that while this library is based on the older sprite library, the library name has been changed to "movieclip", because the name "sprite" is now reserved for the sprite-sheet feature. A discussion of the differences is below.
For a sample project using the movieclip library, see the Graphics/Movieclip project in the Sample Code directory of the Corona SDK, or in the Sample Code section of the Ansca Mobile website.
Creates an animated sprite using an array of image filenames provided in the frames table:
myAnim = movieclip.newAnim{ "img1.png", "img2.png", "img3.png", "img4.png" }
Starts the animated sprite playing in the forward direction. When the end of the image set is reached, it will cycle back to the first image and continue playing. If a specific frame sequence has already been constructed, calling play() with no parameters will simply resume playing within that sequence.
Starts the animated sprite playing in the forward direction. When the frame of number given by endFrame is reached, it will cycle back to the frame number given by startFrame and continue playing.
myAnim:play{ startFrame=1, endFrame=6, loop=3, remove=true }
The loop parameter accepts a number of times to loop the sequence, with zero (the default) indicating that it should loop forever.
The remove parameter is a boolean flag, and if set to true, the movieclip will automatically delete itself when the given sequence is complete. This is useful for things like animated explosions and other single-use cases. Note that the object will only be garbage-collected if there are no other remaining references to it in your Lua code. The default value is false.
All parameters are optional, but startFrame and endFrame will default to the first and last images provided in newAnim(), so it is a good practice to provide both values explicitly to avoid unexpected behavior.
Passing any parameters to play() indicates that a new sequence should be constructed, and any current sequence information will be reset. Call play() with no parameters if you simply want to resume the current sequence.
Starts the animated sprite playing in the reverse direction. When the beginning of the image set is reached, it will cycle back to the last image and continue playing backwards. If a specific frame sequence has already been constructed, calling reverse() with no parameters will simply resume playing backwards within that sequence.
Starts the animated sprite playing in the reverse direction. When the frame of number given by endFrame is reached, it will cycle back to the frame number given by startFrame and continue playing.
The loop parameter accepts a number of times to loop the sequence, with zero (the default) indicating that it should loop forever.
The remove parameter is a boolean flag, and if set to true, the movieclip will automatically delete itself when the given sequence is complete. This is useful for things like animated explosions and other single-use cases. Note that the object will only be garbage-collected if there are no other remaining references to it in your Lua code. The default value is false.
All parameters are optional, but startFrame and endFrame will default to the last and first images provided in newAnim(), so it is a good practice to always provide both values explicitly to avoid unexpected behavior.
Passing any parameters to reverse() indicates that a new sequence should be constructed, and any current sequence information will be reset. Call reverse() with no parameters if you simply want to resume the current sequence.
Resets any animation sequence in progress, moves the animation to the next image in the total sequence, and stops. This can be used with a two-frame movieclip to make an image toggle between two states
Resets any animation sequence in progress, moves the animation to the previous image in the total sequence, and stops.
Stops the animation of the sprite at its current frame.
Jumps the animation to the specified frame, given either as a frame number or by an optional frame label (see setLabels function below).
Note: to “go to and play” from a specified frame, simply issue a stopAtFrame command followed by a play or reverse command. Both commands will execute before the display updates again, and so the transition will be immediate:
myAnim:stopAtFrame(4) myAnim:play() myAnim:stopAtFrame("label") myAnim:reverse()
Adds optional labels to an Anim object previously created, using a table to assign label names to selected frame numbers:
myAnim:setLabels{ firstLabel=1, anotherLabel=3 }
Turns any movieclip into a draggable object when drag is set as true. The limitX and limitY parameters limit the dragging to either the x or y axis, and the bounds parameter can be used to specify drag boundaries for the object as {left, top, width, height}.
The onPress, onDrag and onRelease parameters take the names of functions to be called when those events occur. All parameters are optional.
myAnim:setDrag{ drag=true, limitX=false, limitY=false, onPress=myPressFunction, onDrag=myDragFunction, onRelease=myReleaseFunction, bounds={ 10, 10, 200, 50 }}
To turn off the draggable property again, set "drag" to false:
myAnim:setDrag{ drag=false }
Note that the examples above with curly brackets are using the Lua “shorthand” notation for passing a table of named values directly to a function. In other words, the function is actually receiving a single value that happens to be a Lua table, like this: function( {values } ). The shorthand notation allows the outer parentheses to be eliminated, to make the code more readable.
Corona SDK includes a “sprite sheet” feature for constructing animated sprites by copying rectangular areas from a single large texture in memory.
Sprite sheets are a much more efficient use of texture memory, since OpenGL-ES allocates all textures in dimensions equal to the next power-of-two, no matter what the size of the source image is. For example, a single image with dimensions of 80x300 will require a texture memory allocation of 128x512 pixels.
Therefore, sprite sheets are recommended for complex character animation, or any case in which there are a large number of animation states.
However, sprite sheets will require more coding and are more complex to set up; for example, you must first construct a large sheet of animation frames. The movieclip library is easier to get started with, and can be more rapidly used to port Flash content, since movieclip frames can be exported from Flash as PNG sequences.
For more information about sprite sheets, see Advanced Animation Using Sprite Sheets.
Often you will need to create your own custom animations that are not feasible using the transition library. This is known as programmatic animation because you have to write custom code to produce the animation sequence.
To create such animations, you need to change the contents of the screen over time. In some environments, it’s natural to do this by changing properties of an object in loops such as a for or while loop. However, in Corona, you cannot produce animations using such loops because the screen is never updated within a block of code (see Screen Updates).
Instead, animations are produced by repeatedly calling listeners. These listeners modify the display objects on the screen and then exit, thus allowing the screen to be updated. Such listeners are known as "enterFrame" listeners because you register these listeners with the "enterFrame" event.
In the drawing cycle, "enterFrame" events are dispatched before the screen is updated providing your code the opportunity to modify the contents of the screen (see Drawing Cycle). With "enterFrame" events, you can produce all kinds of animations. In fact, the transition library is built on top of these events.
Below is an example of how to animate a bouncing ball:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | local xdirection,ydirection = 1,1 local xpos,ypos = display.contentWidth*0.5,display.contentHeight*0.5 local circle = display.newCircle( xpos, ypos, 20 ); circle:setFillColor(255,0,0,255); local function animate(event) xpos = xpos + ( 2.8 * xdirection ); ypos = ypos + ( 2.2 * ydirection ); if (xpos > display.contentWidth - 20 or xpos < 20) then xdirection = xdirection * -1; end if (ypos > display.contentHeight - 20 or ypos < 20) then ydirection = ydirection * -1; end circle:translate( xpos - circle.x, ypos - circle.y) end Runtime:addEventListener( "enterFrame", animate ); |
The listener function animate is called every time an "enterFrame" event occurs. It is responsible for changing the position of the ball and for ensuring that the ball “bounces” when it hits the edge of the screen.
Because "enterFrame" events occur at the global level, you register listeners for those events with the global Runtime object.
The "enterFrame" event occurs at a regular interval known as the frame rate, so your listeners will be called at the frame rate. However, if your listeners take too long to exit, then the actual frame rate will be less than the desired frame rate.
In the above example, the animation was done in a frame-based manner. If the actual frame rate were to slow down, the ball would appear to move more slowly as each and every intermediate frame got rendered; no intermediate frames would be skipped. If you were trying to synchronize the animation with sound, then this behavior would be extremely problematic.
The solution is time-based animation. We can transform the above example to be time-based by calculating how much time had passed between calls to our listener and changing the velocities appropriately. This would result in the following changes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | local xdirection,ydirection = 1,1 local xpos,ypos = display.contentWidth*0.5,display.contentHeight*0.5 local circle = display.newCircle( xpos, ypos, 20 ); circle:setFillColor(255,0,0,255); local tPrevious = system.getTimer() local function animate(event) local tDelta = event.time - tPrevious tPrevious = event.time xpos = xpos + ( 0.084*xdirection*tDelta ); ypos = ypos + ( 0.066*ydirection*tDelta ); if (xpos > display.contentWidth - 20 or xpos < 20) then xdirection = xdirection * -1; end if (ypos > display.contentHeight - 20 or ypos < 20) then ydirection = ydirection * -1; end circle:translate( xpos - circle.x, ypos - circle.y) end Runtime:addEventListener( "enterFrame", animate ); |
Notice how we leverage the fact that the "enterFrame" event contains a property storing the time in milliseconds. We compare that with the previous time to determine how far the ball should travel. In addition, our old x,y velocities (2.8, 2.2) implicitly assumed that time was measured in frames. The equivalent time in milliseconds is simply the frame rate. By default, that’s set to 30 fps or 33.3 milliseconds. So we can multiply the old velocities by (30/1000) to get the new time-based velocities.
The one problem to watch out for when doing time-based animation is that when the device suspends the app, you need to account for the “lost” time. In the simulator, you can simulate a suspend by using the keyboard shortcut ⌘↓ (command-down arrow) which corresponds to the Suspend/Resume menu item under Hardware. (Note a known issue is that clicking on the menu causes the app to pause as if it were suspended but doesn’t generate a suspend event).
In the bouncing ball animation, if the app is suspended for half a second, the ball may appear to jump across the screen. In the example above, the solution is to adjust tPrevious to account for this missing time:
1 2 3 4 5 6 7 8 9 10 11 12 13 | -- Add the following below the code in the previous example local tSuspend local function onSuspendResume( event ) if "applicationSuspend" == event.type then tSuspend = system.getTimer() elseif "applicationResume" == event.type then -- add missing time to tPrevious tPrevious = tPrevious + ( system.getTimer() - tSuspend ) end end Runtime:addEventListener( "system", onSuspendResume ); |
Screen Update and Drawing Cycle links still do not work :-(
Hmm, those links appear to be working for me today (Oct. 1st). Can you try refreshing the page in your browser, or clearing the cache?
Tim
I get a. "you are not authorized to access this page" page
Please try the links again (there was a wrong setting on the page that I just fixed)
http://developer.anscamobile.com/content/app-programming-guide#DrawingCycle
http://developer.anscamobile.com/content/app-programming-guide#ScreenUpdates
Thanks!
In in the animate a square example need to use :
local w,h = display.contentWidth, display.contentHeight
instead of:
local w,h = display.stageWidth, display.stageHeight
for working :)
Thanks, docs are updated with new syntax.
Btw, the stageHeight/width should continue to function normally, but they have been deprecated in favor of contentHeight/Width.
Tim
Any chance of having more example on more complex examples than transition 1 and 2 with transition.to( target, params ) like onStart, delta ?
transition to is a way cooler option to animate than basic enterframe. In flash world, people like grant skinner has understood that and released the wonderful gtween class.
Do you plan to develop the transition library in that way ?
Thx
The following links seem to be broken
Corona Game Edition (link to http://anscamobile.com/games/)
Game Edition: Sprite Sheets. (link to http://developer.anscamobile.com/content/game-edition-sprite-sheets)
@macfan, good catch, thanks! Corona Game Edition is now entirely integrated into Corona SDK, but apparently our documentation hasn't quite caught up yet.
It looks to me like the first code example of transition has redundant declarations for square and w,h local vars -- am I missing something here or is that really unnecessary?
I had the same question. I believe it is redundant.
I have a problem.
In my touch event listener, there is a conditional switch one the touch event:
1 2 3 | if "began" == event.phase then transition.to(myThingy, {xScale=destScale, yScale=destScale, time=700}) end |
The strange thing is that it definitely is getting inside the the conditional, but the transition does NOT play until the touch event's phase == "ended". That's exactly the opposite phase from when I want it to play :(
If it helps, myThingy is also having its x and y changed constantly during the "moved" phase, but NOT its xScale and yScale.
Hi,
I have a hard time trying to make a loop for a transition. Can some one help me? I made this code to test it out modifying the example at the top, basically I am trying to make the square go back and forth multiple times.
local square = display.newRect( 0, 0, 100, 100 )
square:setFillColor( 255,255,255 )
local w,h = display.contentWidth, display.contentHeight
transition.to( square, { time=1500, loopCount = 10, x=(w-275), y=(h-50) } )
transition.to( square, { time=1500, delay=1500, x=(w+-275), y=(h-427) } )
I did this once.
If I remember correctly, I had to put my transitions each inside their own function (preferably local).
like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ---square should be defined by this point. local loopPhase1 local loopPhase2 loopPhase1 = function() transition.to( square, { time=1500, loopCount = 10, x=(w-275), y=(h-50), onComplete=loopPhase2} ) end loopPhase2 = function() transition.to( square, { time=1500, delay=1500, x=(w+-275), y=(h-427), onComplete=loopPhase1 } ) end --to trigger it to start: loopPhase1() |
Order matters a lot with this setup. LUA can't find the functions, or they won't count as having any instruction code when the transitions' onComplete uses them as a callback, unless everything's been defined before it's asked for, and in the right scope.
That's why I declared local function names outside of their definitions: The code inside their definitions has to know about the other loopPhase function before we get to where it has its code laid out.
I'm not totally sure if the way I showed is exactly the way I got it to work, but that's how I remember it. If it doesn't work maybe I can try it out and fix it :) Should be okay though if I remembered right.
here is how I put it:
local square = display.newRect( 0, 0, 100, 100 )
square:setFillColor( 255,255,255 )
local w,h = display.contentWidth, display.contentHeight
local loopPhase1
local loopPhase2
loopPhase1 = function()
transition.to( square, { time=1500, loopCount = 10, x=(w-275), y=(h-50), onComplete=loopPhase2} )
end
loopPhase2 = function()
transition.to( square, { time=1500, delay=30, x=(w+-275), y=(h-427), onComplete=loopPhase1 } )
end
--to trigger it to start:
loopPhase1()
Thank you it works like a charm:)
Ah, very glad to hear it!
I remember struggling with that for a half hour or more and I'm happy that I was able to save you some trouble from my experiences in figuring out how to make it work :)
Is tjere a way to put in a png in place of the circle but have the same effects? Just wondering. Pretty new to the whole concept of lua and the language.
I can take your question at least 2 ways without clarification. Here are my 2 interpretations and answers:
1. You mean having the same collision shape as the circle but showing a png instead.
A: This is what happens anyway, as long as you are listening on the collision event of the physics body, not the png image.
2. You mean detecting collision on the pixel edges of the png image.
A: I don't think Corona supports this. To approximate collision with a complex shape (rather than a circle, box or triangle), you may attach multiple physics bodies to one image in various positions. All physics body shapes must be convex (they don't bend in at any place) but you can arrange several of them next to each other to form a hollow convex shape overall. See the Corona Physics API for more information on how to listen on collision events from multiple bodies belonging to the same image, and how to attach multiple physics body shapes to one image/sprite.
hope that answers your question. If not, give me a few more specifics on what you're trying to do and I'll see if I can help some more.
My question is in regards to the bouncing ball animation. I was just wondering if there was a way to replace the ball with a png image but still render the same effects. Or is that another code altoghether. Also i was looking for a good place that lists in detail lua language and defines it. Could recommend a place? Thanks!
If you mean the FrameAnimation2 example, then just use:
1 | local bouncyImg = display.newImage("myImage.png") |
I wasn't sure what you meant by the bouncing ball animation so that's my guess.
The behavior in this case isn't controlled by physics at all, or even transitions (I must have WAY overshot the mark in my attempt to be helpful!). All it does is increment or decrement the x and y position values on the images until they are less than 0 or more than the screen content width or height, then reverses the operation once they hit those implicit "walls".
If that is not the bouncing ball animation that you meant, let me know which one it is and I'll see if I can be more helpful :)
Its the one where it says "here's how to animate a bouncing ball" i think that will work. I dont really know:/ another question...if you have the time (by the way thanks for your rapid replies:) when insert this code to test it out it gives me an error that reads "eof expected near end" its on the end after "circle:translate( xpos - circle.x, ypos - circle.y) thanks again!
Scratch that:) i got it. Thanks for all of your help!:)
P.S. I also would like to know of an intuitive, easy-to-navigate, easy-to-understand LUA guide is out there in the Web. I've just been googling any time I have a question about data types, scoping, etc. Also I've been asking my partner programmer who's used LUA for a year more than I.
Oh good. I'm glad I could help :)
How would you simply make the Custom/Programmatic Animation stop? Thanks.
Documentation Suggestion: Correct me if I am wrong. The setReferencePoint for the X and Y values within the transition.to library is set to TopLeftReferencePoint. Would be handy to have this listed in the documentation.
example:
1 2 3 | local square = display.newRect( 100, 100, 100, 100 ) transition.to( square, { time=150, x=100, y=100 } ) |
The above will actually move the square (even though you are using the exact same values of its initial position) unless you change the reference point to top left.
Or maybe mention that we should stick to arithmetic when adding transition values to X and Y.
Cheers
Screen Update and Drawing Cycle links are not working.