Corona – Animation

Corona Animation Overview

Animation can be looked at in two different categories: Object animation and Frame animation.

Object animation would be defined as moving objects on the screen by setting the x, y or other properties. Frame based animation would be animation created by cycling through a series of images.

These two types of animation can be used together. For the first part of this discussion we will look at each separately.

Object animation

Object animation can be thought of as moving objects on the screen. This is traditionally called Sprite animation. I’ve not used this term here to avoid confusion, since Corona has a class that is also called Sprite.

Object Animation is based on the mechanic of changing the properties of an object over time. Any property that changes the way an object is displayed on the screen, if changed over time, will create animation.

There are two ways you can animate the properties of an object:

  • enterFrame event
  • transition

The enterFrame event can call on a handler function each frame. In Corona these enterFrame events are sent 30 times per second by default.

Using transition.to() animates the properties of an object from the current value to a target value automatically.

To really get an idea of how these work you should test them for yourself.

Frame based motion example – using enterFrame

-- make an object to move across the screen
local box = display.newRect( 0, 0, 50, 50 )

-- Position the box in the vertical center
box.y = display.contentCenterY

-- Define the enterframe handler
local function move_box( event )
box.x = box.x + 5
if box.x > display.contentWidth then
box.x = 0
end
end

-- Add an enterFrame listener
Runtime:addEventListener( "enterFrame", move_box )

Here each frame box.x is increased by 5 pixels. When the value is greater than the width of the screen box.x is set to 0. In this way the box travels from the left to right and starts over again on the left when it reaches the right edge.

Transition based animation – sung transition.to()

Transition based animation is based on the transition class. The transition class provides a few methods, we’ll use the to() method to animate the object to a location.

The transition.to() method requires two parameters: an object to animate and a table describing the properties to be animated. The object can be any display object. The table is a standard Lua table, the properties named in the table will animate to the values. For example {x=100} would animate the x property of your display object to 100. Try it for yourself.

-- Create an object
local box = display.newRect( 0, 0, 50, 50 )

-- Move the box out of view above the top of the screen
box.y = -50

-- Position the box in the horizontal center
box.x = display.contentCenterX

-- Create a transition
transition.to( box, {time=3000, y=display.contentCenterY} )

Here we’ve created an object and then applied a transition to that object. The transition.to() method requires two parameters the first is the object to animate. The second is a table describing the motion to create. The properties of this table determine the length of the animation, and which properties are animated. In this example I included two properties.

  • time=3000 — Sets the time in milliseconds, 3 secs in this example
  • y=display.contentCenterY — Animates the y value of box

Some thoughts on the two methods

In general, if the motion you are trying to create is going to loop a frame based animation, using enterFrame is probably your best choice. This applies to objects that are in constant motion.

For objects that are going to move from Point A to Point B, or change from one state to another and stop, the transition based animation is probably a better choice.

As general advice I would recommend against mixing the two methods. It is possible use both, but you run the risk of having both systems targeting the same properties which may cause problems. Also, keeping all of the motion within one system makes the job of programming easier to understand.

Frame based animation

Frame based animation is very similar to film. The motion is created by displaying a series of separate frames one after the next. There are a few way to create this type of motion. Here we’ll discuss what I would consider the two most useful options: Sprite Sheets and Video.

Sprite Sheets are a single image that contain multiple frames that make up an animated sequence. Sprite Sheets are used in many video games and provide a very efficient system.

Video is video, a single file containing a sequence of compressed images.

Sprite Sheets are good in situations where you have short sequences of images. They support one or more short looping or non-looping sequences well. Sprite Sheets are also good when you need random access to frames.

Video, on the other hand, is good when you have a long sequence of frames that you need to play. Video is also good when the animated area is large.

Working with Sprite Sheets

A Sprite Sheet is a single image that contains multiple frames. This is probably best described with a picture. 

This image used as a Sprite Sheet would have ten frames. Each image is 32 by 32 pixels.

The images in a Sprite Sheet do not need to be used in order. You can create multiple sequences from the same Sprite Sheet.

All in all, Sprite Sheets are very flexible and provide many more features than I’ve shown here. We’ll just take a  basic look at Sprite Sheets here to get a basic idea of how they work.

Note: the code for sprite sheets will change in an upcoming version of Corona

-- Include the sprite
require "sprite"

-- name the image file containing your sprites,
-- followed by the width and height of each sprite
local alien_sheet = sprite.newSpriteSheet("Alien-32-Red.png", 32, 32)
-- Create a set by naming the sheet and starting frame and number of frames
local alien_set = sprite.newSpriteSet(alien_sheet, 1, 10)

-- Now create new sprite, give it a name, set the starting frame, end frame,
-- and time between frames in ms, 0 means loop
sprite.add( alien_set, "Alien", 1, 10, 1000, 0 )

-- Create a new Sprite in the display object so we can see it on the screen
local alien_sprite = sprite.newSprite( alien_set )

-- Position your sprite
alien_sprite.x = display.contentWidth / 2
alien_sprite.y = display.contentHeight / 2

-- Prep your sprite
alien_sprite:prepare("Alien")

-- Start the sprite playing
alien_sprite:play()

Here the sprite appears in the center of the screen. The object cycles through frames 1 to 10.

Note: the example below uses the newer image group feature. The example here was tested in CoronaSDK 2012.764. This was a prerelease and some of these features may change by the time this reaches release. 

-- Define option for the image sheet. This includes
-- width and height of each frame and the number of
-- frames in the sequence.
local options = { width = 32,
     height = 32,
  numFrames = 10 }

-- Make a new imagesheet, name the file and include the options.
local alien_sheet = graphics.newImageSheet( "Alien-32-Red.png", options )
-- consecutive frames
local sequenceData = {
    name="All Frames",
    start=1,
    count=10,
    time=1000,       -- Optional. In ms.  If not supplied, then sprite is frame-based.
    loopCount = 0,    -- Optional. Default is 0 (loop indefinitely)
    loopDirection = "bounce"    -- Optional. Values include: "forward","bounce"
}

-- Make a sprite to display using the imagesheet and sequence
local alien = display.newSprite( alien_sheet, sequenceData )

-- start the sprite playing
alien:play()

-- Center the object on the screen
alien.x = display.contentCenterX
alien.y = display.contentCenterY

This example is functionally similar to the first example.

Combining sprites and object animation – Sprite Sheets and enterFrame

Here’s a quick example that combines the Sprite Sheet above with the enterFrame based animation.

-- Include the sprite
require "sprite"

-- name the image file containing your sprites,
-- followed by the width and height of each sprite
local alien_sheet = sprite.newSpriteSheet("Alien-32-Red.png", 32, 32)

-- Create a set by naming the sheet and starting frame and number of frames
local alien_set = sprite.newSpriteSet(alien_sheet, 1, 10)

-- Now create new sprite, give it a name, set the starting frame, end frame,
-- and time between frames in ms, 0 means loop
sprite.add( alien_set, "Alien", 1, 5, 1000, 0 )
-- Add a new set
sprite.add( alien_set, "Alien Blink", 6, 4, 1000, 1 )

-- Create a new Sprite in the display object so we can see it on the screen
local alien_sprite = sprite.newSprite( alien_set )
-- Position your sprite
alien_sprite.x = display.contentWidth / 2
alien_sprite.y = display.contentHeight / 2

-- Prep your sprite
alien_sprite:prepare("Alien")

-- Start the sprite playing
alien_sprite:play()

-- Define a function to handler the enterFrame event
local function move_alien( event )
alien_sprite.x = alien_sprite.x + 2
if alien_sprite.x > display.contentWidth then
alien_sprite.x = 0
end
end

-- Add a listener for the enterFrame event
Runtime:addEventListener( "enterFrame", move_alien )

-- Define a function to handle touch events on the alien sprite
local function touch_alien( event )
if event.phase == "began" then
-- On a touch play the sequence Alien Blink
alien_sprite:prepare("Alien Blink")
alien_sprite:play()
end
end

-- Add a handler function for touch events
alien_sprite:addEventListener( "touch", touch_alien )

-- Define a listener for sprite events
local function alien_sprite_events( event )
-- If the Alien Blink sequence has ended
if event.phase == "end" and event.sprite.sequence == "Alien Blink" then
-- Start the regular Alien sequence
alien_sprite:prepare( "Alien" )
alien_sprite:play()
end
end

-- Add a listener for sprite events
alien_sprite:addEventListener( "sprite", alien_sprite_events )

In this example, the Alien sprite moves across the screen and cycles through all of the frames in its Sprite Sheet. Touching the alien starts a new sequence from the same Sprite Sheet. When the second sequence ends, the first sequence is restarted.

Creating a sequenced animation – transition.to() and delay

Sometimes you will want to make an object follow a sequence of motion. Think of this like choreographing an object. The easiest way to accomplish this is to combine transition.to() with the delay property.

The delay property sets a time to wait before the transition begins.

You can apply more than one transition to an object, assigning each a longer delay than the last. This will stagger each motion so they follow in sequence.

Here’s an example:

local box = display.newRect(0, 0, 50, 50)

box.x = display.contentCenterX
box.y = -0

-- Create a sequence of animation using delay.
transition.to( box, {time=1000, y=display.contentCenterY} )
transition.to( box, {time=1000, rotation=360, delay=1000} )
transition.to( box, {time=500, height=1, delay=2500} )
transition.to( box, {time=500, width=display.contentWidth, delay=3000} )
transition.to( box, {time=1000, height=display.contentHeight, delay=3500} )

Easing and transition

The transitions so far have looked very mechanical. That is because they have all been handled by the default linear easing. Linear easing changes properties at a constant rate of change. Think of the frame based example where the object moved 5 pixels each frame.

Linear looks good for alpha and color changes, but it looks mechanical and stiff for other properties. Think about objects in real life.  A car doesn’t travel at a constant rate from one stop to the next. Instead, it speeds up from a stop, then slows to stop again. The rate of change varies over time.

You can apply these ideas to your transitions using easing. Easing adds acceleration and deceleration to motion, which adds a lot of character and realism. Corona provides the following easing types:

  • easing.inExpo()
  • easing.outExpo()
  • easing.inOutExpo()
  • easing.inOutQuad()
  • easing.outQuad()
  • easing.inQuad()
  • easing.linear()

All of the easing types that begin with “in” will speed up and get faster over time. Easing that begins with “out” will slow down over time. When the name begins with inOut the motion will speed and slow down. The linear type has a constant rate with no acceleration or deceleration.

Assign an easing type to a transition by adding the transition property to the transition parameters. For example:

-- Easing
local box = display.newRect(0, 0, 50, 50)

box.x = display.contentCenterX
box.y = -0

-- Add easing to these transitions to add character to the motion
transition.to( box, {time=1000, y=display.contentCenterY, transition=easing.outExpo} )
transition.to( box, {time=1000, rotation=360, delay=1000, transition=easing.inOutExpo} )
transition.to( box, {time=500, height=1, delay=2500, transition=easing.inExpo} )
transition.to( box, {time=500, width=display.contentWidth, delay=3000, transition=easingoutExpo} )
transition.to( box, {time=1000, height=display.contentHeight, delay=3500, transition=easing.inOutExpo} )

Here the easing adds character to the motion. I used the same times and delays from the previous example, only adding easing. Overall, everything looks more interesting, but could look better by editing the times and delays.

Creating interactive animations

Often you will want to create animation that is triggered by user interaction. This is so open ended that I obviously can’t cover every possibility here. My goal, then, is to provide a few examples that cover general principles.

This example uses the ideas from earlier in this chapter. There are three elements: a shape rectangle to fill the background, a cloud image and a picture of a sheep.

The background shape fills the screen and provides a solid background color.

The cloud image animates in the background moving across the screen.


The sheep is the interactive element. It falls into to the center of the screen and waits for a tap. When tapped it falls off the bottom of the screen. After leaving the bottom of the screen it moves to the top and returns to center waiting for another tap.

-- Create three objects
local background = display.newRect( 0, 0, display.contentWidth, display.contentHeight )
local cloud = display.newImageRect( "cloud.png", 200, 131 )
local sheep = display.newImageRect( "sheep.png", 122, 131 )

-- Set initial starting properties for objects
background:setFillColor( 88, 222, 255 )

cloud.x = -100
cloud.y = math.random( 100, display.contentHeight - 100 )
cloud.alpha = math.random( 20, 80 ) / 100

sheep.x = display.contentCenterX
sheep.y = -100

-- Set up an enterframe handler for the cloud
local function on_enterframe( event )
	cloud.x = cloud.x + 2
	if cloud.x > display.contentWidth + 100 then
		cloud.x = -100
		cloud.y = math.random( 100, display.contentHeight - 100 )
		cloud.alpha = math.random( 20, 80 ) / 100
	end
end 

-- Add an event listener
Runtime:addEventListener( "enterFrame", on_enterframe )

-- Forward declaration for the functions below
local reset_sheep
local tap_sheep
local ready_sheep

-- Handler for tapping sheep
function tap_sheep( event )
	print("Tap sheep")
	sheep:removeEventListener( "tap", tap_sheep )
	transition.to( sheep, {time=1500, y=display.contentHeight + 100, rotation=360, transition=easing.inExpo, onComplete=reset_sheep } )
end 

-- This function to be called when the sheep animates onto the screen
function ready_sheep( target )
	print( "Ready sheep" )
	sheep.rotation = 0
	sheep:addEventListener( "tap", tap_sheep )
end 

-- This function called when the sheep is reset
function reset_sheep( target )
	print( "Reset Sheep" )
	sheep.y = -100
	sheep.rotation = 0
	transition.to( sheep, {time=2000,
						  y=display.contentCenterY,
					  delay=2000,
				   rotation=360,
				 transition=easing.outExpo,
				 onComplete=ready_sheep})
end

-- Animate the sheep onto the screen
reset_sheep()

Leave a Reply

Your email address will not be published. Required fields are marked *