Corona – Game Loop

The Game Loop is a system that organizes all of your game actions under a common timer. Imagine the heart of your game system sending out a message to all of it’s actor objects once each “tick”. Imagine a tick is a short time span between actions.

Corona, like many other software frameworks, redraws the screen at a fixed frame rate. Typically Corona projects run at 30 or 60 FPS. In other words, Corona redraws everything on the screen 60 times each second, if the frame rate is set to 60 FPS.

Corona provides an event to notify you of these updates. This event, named “enterFrame”, is fired just before each redraw. This is a perfect opportunity for your program to move objects on the screen. Imagine increasing the x property of an object by 1 each frame. The object would appear to move to the right, 60 pixels each second. The enterFrame event acts like a game loop to some extent.

The timer can also be used to create a game loop. The transition.to(), and related methods, can also be used to create animation. In this article I will focus on enterFrame.

The best way to learn is by doing. I encourage everyone to follow create these examples for themselves.

Below are a series of examples you can try for yourself.

Make a box and move it across the screen. Here we make a box. Position it on the right of the screen, about half way from the top. An enterFrame handler moves the box 1 pixel to the right each frame.


local box = display.newRect( 0, 0, 40, 40 )
box.x = 0
box.y = 200

local function on_frame( event )
	box.x = box.x + 1 
end 

Runtime:addEventListener( "enterFrame", on_frame )

make 10 boxes and move them across the screen.
This time we use a for loop to create 10 boxes.
Each box is added to an array. The enterFrame handler, on_frame,
loops through all of the boxes in box_array and moves each.


local box_array = {}

for i = 1, 10 do 
	local box = display.newRect( 0, 0, 40, 40 )
	box.x = 0
	box.y = math.random( 0, 480)
	box_array[#box_array+1] = box
end 

local function on_frame( event )
	for i = 1, #box_array do 
		local box = box_array[i]
		box.x = box.x + 1
	end
end 

Runtime:addEventListener( "enterFrame", on_frame )

This time make the boxes loop. Here we’ll check the position of each box and
move it to x = 0 when it’s x is greater than (>) 320.


local box_array = {}

for i = 1, 10 do 
	local box = display.newRect( 0, 0, 40, 40 )
	box.x = 0
	box.y = math.random( 0, 480)
	box_array[#box_array+1] = box
end 

local function on_frame( event )
	for i = 1, #box_array do 
		local box = box_array[i]
		box.x = box.x + 1
		if box.x > 320 then 
			box.x = 0
		end 
	end
end 

Runtime:addEventListener( "enterFrame", on_frame )

Give each box a different speed. Since display objects are tables new properties
can be added. Here each box is assigned a new property speed, and the speed of each
is assigned a random value between 1 and 3.
As the boxes are moved each box applies it’s own speed to it’s x so each moves
at a different rate.


local box_array = {}

for i = 1, 10 do 
	local box = display.newRect( 0, 0, 40, 40 )
	box.x = 0
	box.y = math.random( 0, 480)
	box.speed = math.random() * 3
	box_array[#box_array+1] = box
end 

local function on_frame( event )
	for i = 1, #box_array do 
		local box = box_array[i]
		box.x = box.x + box.speed
		if box.x > 320 then 
			box.x = 0
		end 
	end
end 

Runtime:addEventListener( "enterFrame", on_frame )

Give each box an update function. Here we’ve assigned each box a function
named update(). The enterFrame handler now calls the update on each box.
Notice the self reference. As we call update() on each box. The function
has a reference to the current box in self. This allows each box to set it’s
own x with it’s own speed.

Note that speed in this case is represented as pixels per frame. That is the object
will move the number of pixels as the value of speed each frame.


local box_array = {}

for i = 1, 10 do 
	local box = display.newRect( 0, 0, 40, 40 )
	box.x = 0
	box.y = math.random( 0, 480)
	box.speed = math.random() * 3
	
	function box:update()
		self.x = self.x + self.speed
		if self.x > 320 then 
			self.x = 0
		end 
	end 
	
	box_array[#box_array+1] = box
end 

local function on_frame( event )
	for i = 1, #box_array do 
		local box = box_array[i]
		box:update()
	end
end 

Runtime:addEventListener( "enterFrame", on_frame )

Make different kinds of boxes. Better to have a function to create a box of type.
Here both boxes implement the update() method.
Being able to make different types of objects that move in different ways is
very difficult using the earlier frame loop. Here it is easy since each box
implements that same method: update(). The frame loop itself doesn’t need to know
anything about the box or how it moves. The frame loop only needs to call the
object’s update(). Each object can implement update() to suit it’s needs. This
a programming concept called polymorphism.

 
local box_array = {}

-- make green boxes
local function make_green_box()
	local box = display.newRect( 0, 0, 40, 40 )
	box:setFillColor( 0, 1, 0 )
	box.x = 0
	box.y = math.random( 0, 480)
	box.speed = math.random() * 3
	
	function box:update()
		self.x = self.x + self.speed
		if self.x > 320 then 
			self.x = 0
		end 
	end 
	return box
end 

-- make red boxes
local function make_red_box()
	local box = display.newRect( 0, 0, 10, 20 )
	box:setFillColor( 1, 0, 0 )
	box.x = math.random( 0, 320 )
	box.y = 0
	box.speed = math.random() * 3 + 3
	
	function box:update()
		self.y = self.y + self.speed
		if self.y > 480 then 
			self.y = 0
		end 
	end 
	return box
end 

-- Make 10 green boxes
for i = 1, 10 do 
	box_array[#box_array+1] = make_green_box()
end 

-- Make 15 red boxes
for i = 1, 15 do 
	box_array[#box_array+1] = make_red_box()
end

local function on_frame( event )
	for i = 1, #box_array do 
		local box = box_array[i]
		box:update()
	end
end 

Runtime:addEventListener( "enterFrame", on_frame )

Using enterFrame to move objects across the screen, creates fairly smooth motion.
But it is not perfect. Imagine a scenario where the system is slowed by another
process. In a perfect world a frame rate of 30 fps would have 33.33 ms between
enterFrame events. The frame sometimes sped up or slowed down the time between
frames might go higher than this number at times. In those moments objects on the
screen would appear to move more slowly.
Prevent this type of slow down we can vary the speed of objects over time based on
the time between frames. Think about speed of objects become pixels per ms rather
pixels per frame.
The enterFrame event objects has a time property that holds the number of ms
since the app was started. Here’s a sample:

event.time

2014-05-04 20:21:58.234 Corona Simulator[359:507] 84.183
2014-05-04 20:21:58.264 Corona Simulator[359:507] 114.168
2014-05-04 20:21:58.296 Corona Simulator[359:507] 146.563
2014-05-04 20:21:58.330 Corona Simulator[359:507] 180.306
2014-05-04 20:21:58.363 Corona Simulator[359:507] 212.987

About 32ms each frame. 1000ms / 30fps = 33mspf

To find the number of ms for each frame subtract the event.time of the last frame
from event.time of the current frame. Delta is a word that describes an amount
of change. Here’s another sample:

dt

2014-05-04 20:29:10.910 Corona Simulator[359:507] 84.915 84.915
2014-05-04 20:29:10.938 Corona Simulator[359:507] 113.071 28.156
2014-05-04 20:29:10.972 Corona Simulator[359:507] 146.972 33.901
2014-05-04 20:29:11.005 Corona Simulator[359:507] 179.935 32.963
2014-05-04 20:29:11.039 Corona Simulator[359:507] 213.002 33.067

About 33 ms per frame.

The second number is the delta time. You can see the first frame was lot longer
than the other frames, starting the app probably has some overhead. The second frame
is shorter than it should be. After this the other frames settle in very close to
33 ms. This app doesn’t have much going on. If there was more happening we might see
longer frames appear every so often. In other words objects on the screen would
slow down or speed up.

To calculate delta as a function of the frame rate divide the difference by the
frame rate. Then multiply this number by the pixels per frame your object wants to move.

Think about it this way. If the frame rate of the project is 30, the number of ms per
frame is 1000 / 30 or 33 (really it’s 33.333)

If the delta time for a frame is 33,

33/33=1

Multiply this by the number of pixels per frame your object should move. If your
object was supposed to move 10 pixels per frame then 1 * 10ppf = 10 pixels.

Now a imagine a long frame where the computer has slowed for some reason, delta time
might be 40ms.

40/33 = 1.2

Multiply by the same 10 pixels per frame and you get 12. This frame the object moved
a little further to compensate for game lag.

We can incorporate this system by passing the delta time divided by the frame rate to
to each object as a parameter of update(). Now each object can use this value to handle
it own move in any way it chooses.

Note: This system is still imperfect! In extreme situations you might pass a large
number that might move an a larger distance. Imagine if delta passed 2, 5 or 10! Your
object might 20, 50 or 100 pixels in one frame. This can move an object through other
objects causing your game to miss a collision. Delta time should probably have a cap
to set a maximum value. In these cases the game will slow by the system won’t break.


local box_array = {}

-- make green boxes
local function make_green_box()
	local box = display.newRect( 0, 0, 40, 40 )
	box:setFillColor( 0, 1, 0 )
	box.x = 0
	box.y = math.random( 0, 480)
	box.speed = math.random() * 3 
	
	function box:update( dt ) -- receive dt here as a parameter. 
		self.x = self.x + self.speed * dt -- Use dt, multiply by pixels per frame (speed)
		if self.x > 320 then 
			self.x = 0
		end 
	end 
	return box
end 

-- Make 10 green boxes
for i = 1, 10 do 
	box_array[#box_array+1] = make_green_box()
end 


local last_ms = 0				-- Last time in ms
local mspf = 1000 / display.fps	-- milliseconds per frame
local function on_frame( event )
	-- 
	local ms = event.time
	local dt = ms - last_ms
	last_ms = ms
	
	-- print( event.time, dt )
	
	dt = dt / mspf -- divide by frame rate
	
	for i = 1, #box_array do 
		local box = box_array[i]
		box:update( dt )	-- Pass the delta time to update
	end
end 

Runtime:addEventListener( "enterFrame", on_frame )

Leave a Reply

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