Corona – Game Loop part 2

Here’s a follow up to the previous post on game loops. The game loop works with actor objects. Actors are stored in an array. The game loop sends it’s actors an update message each frame, and passes the delta time.

Here I have created a Lua module to handle the game and appropriately named it: game_loop. Using a module gives us three advantages.

First, the code is easily portable. You can copy this Lua file/Module into another project and use the game there.

Second, the code is isolated. This makes it easier to edit the game loop if necessary.

Last, Lua modules are only loaded once. If we load this file with require() in file A, and then load it again with require() in file B, file B sees the same game_loop with the same variable values. It doesn’t load a new unique instance. In effect Lua modules act like singleton objects. For the game loop this is perfect. We will only need one game loop, and many other modules will want to access it.

Here’s the game_loop module. Move all of the code here into a file named game_loop.lua.


---------------------------------------------------
--
-- game_loop.lua
--
---------------------------------------------------
local M = {}
---------------------------------------------------

local MSPF = 1000 / display.fps
local actors_array = {}
local last_ms = 0
local isRunning = false


function add( obj ) 
	if table.indexOf( actors_array, obj ) == nil then 
		actors_array[#actors_array+1] = obj
	end 
end 
M.add = add
	
function remove( obj )
	local index = table.indexOf( actors_array, obj )
	if index ~= nil then 
		table.remove( actors_array, index )
	end  
end 
M.remove = remove
	
function enterFrame( event )
	local ms = event.time
	local dt = MSPF / ( ms - last_ms )
	last_ms = ms
	
	for i = #actors_array, 1, -1 do 
		local actor = actors_array[i]
		actor:update( dt )
	end 
end 

	
function run()
	if isRunning == false then 
		Runtime:addEventListener( "enterFrame", enterFrame )
		isRunning = true
	end 
end 
M.run = run	


function pause()
	if isRunning then 
		Runtime:removeEventListener( "enterFrame", enterFrame )
		isRunning = false
	end 
end 
M.pause = pause
---------------------------------------------------
return M

Load game_loop.lua with require(“game_loop”). When using require() do not include the .lua file extension.

The game_loop object/table has four methods.

game_loop.add( obj ) — Adds obj to the game loop, obj must implement
the update(dt) method.

game_loop.remove( obj ) — Removes obj from the game loop.

game_loop.run() — Starts the game loop by adding an enterFrame listener.

game_loop.pause() — pauses the game loop by removing the enterFrame listener.

A game_loop keeps track of objects, we’ll refer to these as actors, in an array/table. Each frame game_loop sends each of the actors an update() message and passes the delta time (dt).

To use this in a game all actor objects must implement the update() method.
Actors are added to the game_loop using game_loop.add( actor ). When an actor
is to be removed from the display it should also be removed from the game_loop using

game_loop.remove( actor )

The game_loop can be stopped and started with:

game_loop.run() 
game_loop:pause()

To test this you can add the following code to main.lua in a new project. The code below first creates a new game loop. Touching the screen creates a new box. Boxes are added to the game loop that sends them update messages each frame. Touching a box removes it from the game loop, and then from the screen.

At the bottom I created two buttons to to run and pause the game loop.

-- Import the game loop module 
local game_loop = require("game_loop")
-- Start the game loop 
game_loop.run()

-- 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 




-- Tap a box to remove a box
local function remove_box( event )
	if event.phase == "began" then 
		-- Remove the box from the loop
		local box = event.target
		game_loop.remove( box )		-- Remove box from loop
		display.remove( box )	-- Remove box from display
	end 
	return true
end 


-- Tap the screen to add a box
local function add_box( event )
	if event.phase == "began" then
		local box = make_green_box() -- Make a new box
	
		box.x = event.x	-- Position box
		box.y = event.y
	
		box:addEventListener( "touch", remove_box ) -- Add a touch event 
	
		game_loop.add( box )	-- Add box to the game loop 
	end 
	return true
end 

Runtime:addEventListener( "touch", add_box )





local run_button = display.newRect( 31, 21, 60, 40 )
run_button:setFillColor( 0, 200/255, 0 )

local pause_button = display.newRect( 92, 21, 60, 40 )
pause_button:setFillColor( 200/255, 0, 0 )

local function touch_run( event )
	if event.phase == "began" then 
		game_loop.run()
	end 
	return true 
end 

local function touch_pause( event )
	if event.phase == "began" then 
		game_loop.pause()
	end 
	return true
end 

run_button:addEventListener( "touch", touch_run )
pause_button:addEventListener( "touch", touch_pause )

To expand on what’s here, you could imagine every actor in the actor_array as a table. These
actor tables could contain properties describing how the game_loop should treat each
actor. A simple idea might be to give each a type, and active property. As is the game
loop can be paused and run. Using this idea you could pause or run actors in groups.