25 August 2015
Tutorial: Animation with “enterFrame” listeners
In Corona there are two methods of animation: sprite animation and procedural animation. Sprite animation (guide) involves using multiple frames (images) displayed in a cycle which gives the illusion of movement within the object. In contrast, procedural animation involves using programming techniques to move, rotate, scale or otherwise change the state of the object.
The previous tutorial outlined procedural animation using transitions. This week, we’ll discuss how to manipulate objects using "enterFrame"
events.
Overview
When an app is running, Corona updates the screen either 30 times per second or 60 times per second, a concept known as frames per second or fps. You can control this rate via the fps
value inside the app’s config.lua
file (guide):
1 2 3 4 5 6 7 8 9 |
application = { content = { scale = "letterbox", width = 320, height = 480, fps = 60, -- 30 or 60 }, } |
As the app runs, every time (frame) when Corona prepares to update the screen, it generates an event called "enterFrame"
(reference). Using this event in tandem with an event listener, you can animate an object by changing some aspect of it each frame.
Basic setup
Consider a classic game like Space Invaders®. In this game, the aliens moved back and forth in a set pattern, and occasionally a special UFO would zoom across the top of the screen. Let’s illustrate how to move that UFO using an "enterFrame"
listener:
1 2 3 4 5 6 7 8 9 |
local ufo = display.newCircle( 340, 15, 10 ) local timeToMove = 5 -- animate over 5 seconds local fps = 60 local numberOfTicks = fps * timeToMove -- i.e. 300 ticks local finalDestination = -20 local distanceToMove = ufo.x - finalDestination -- i.e 360 points local pointsPerTick = distanceToMove / numberOfTicks -- 1.2 points per tick |
This setup is probably a bit more verbose than you would write, but it’s useful to explain the math involved. Timing is based on clock/frame “ticks,” so in a 60 fps game, there will be 60 events fired per second. If we want to move our UFO across a 320 point screen over the course of 5 seconds, we must calculate exactly how much to move it on each tick.
Points versus pixels
Corona uses a virtual “content area” size defined in config.lua
by width
and height
(see above). However, this will usually not “map” exactly to the pixel width on a real device. For example, the iPhone 6 screen is 750 pixels wide and the iPad Air screen is 1536 pixels wide. Thus, for frame-based animation of the UFO across the screen, it’s useful to convert each position change to points.
In this example, we have 300 total “ticks” to move the UFO across the screen, because the fps is 60 and we’ll move it over the time span of 5 seconds (60 × 5 = 300
). The UFO begins at an x position of 340, which is 20 points off the right side of the content area (320+20
), and its destination is an x position of -20, which is 20 points off the left side of the content area. Thus, the actual distance of movement will be 360 points (340 − -20 = 360
). Since we need to know how many points to move the object each tick, we simply divide the distance by the number of ticks (360/300)
to get a result of 1.2
.
Using the “enterFrame” event
Now that we have the calculations for moving the UFO, let’s write a function that will execute on every tick:
1 2 3 4 5 6 7 8 9 |
function ufo:enterFrame() self:translate( -pointsPerTick, 0 ) if self.x <= finalDestination then Runtime:removeEventListener( "enterFrame", self ) end end Runtime:addEventListener( "enterFrame", ufo ) |
Outside the function — in the final line of this example — we add the "enterFrame"
event listener to the Runtime and pass in the ufo
display object as the target. This will start the frame-based animation process.
For the function declaration, we “attach” the function to the UFO object using the :
operator so that the object gets passed in. This is known as the Lua object method, and by doing so, we can access the UFO object within the function as self
.
Inside the function, on each frame, we want to move the UFO to the left. Knowing the pointsPerTick
value of 1.2
, its simply a matter of subtracting that from the object’s x using the object:translate() API. The additional code checks when the UFO is past its final destination point of -20, at which point we remove the "enterFrame"
event listener, effectively stopping movement of the UFO.
Of course this performs very simple motion along the x axis only, which could easily be done via a transition, but frame-based animation can potentially be used for more complex scenarios like moving an object in a pattern, moving enemies back and forth repeatedly, scrolling game backgrounds, etc.
Delta time
Note that when working with frame-based animation, it might be necessary to account for “delta” time fluctuations. This is because, internally, frames are not guaranteed to fire precisely on time, and depending on how much other activity is occurring in your app, this imprecision can affect the overall animation. If greater precision is required, please refer to this tutorial which outlines how to utilize delta time.
Conclusion
As you can see, "enterFrame"
events provide yet another option for animating objects in Corona, in particular when the required animation is beyond the abilities of a single transition.
Thomas Vanden Abeele
Posted at 08:02h, 26 AugustA bit short, but cool to mention enterFrame events – the most powerful concept to grasp in game (code) design, in my opinion, and something every beginner should really work hard to master.